# Módulo: Fale Conosco (GAP)

## 1. O que é o módulo

Gerencia a página de contato institucional da SUCESU. Possui no portal:

- **Formulário de contato** com campos: Nome, E-mail, Telefone, Assunto, Mensagem e reCAPTCHA.
- **Informações de contato** lado a lado: Telefone, Endereço completo, Horário de funcionamento (08h–17h).
- **Mapa Google Maps** embedado abaixo do formulário com a localização da sede (SRTVS Quadra 701, Ed. Assis Chateaubriand, Brasília/DF).

No CMS, deve permitir:
- Visualizar mensagens recebidas (caixa de entrada)
- Responder ou arquivar mensagens
- Configurar os dados de contato (telefone, endereço, horário, email, coordenadas do mapa)
- Configurar template de resposta automática (e-mail de confirmação)

## 2. Estrutura de dados

### Mensagem de Contato (`mensagem_contato`)
- `id` (UUID, PK)
- `name` (string, 255) — nome do remetente
- `email` (string, 255) — e-mail do remetente
- `phone` (string, 50, nullable) — telefone
- `subject` (string, 255) — assunto
- `message` (text) — mensagem
- `is_read` (boolean, default false) — lida/não lida
- `is_archived` (boolean, default false) — arquivada
- `replied_at` (timestamp, nullable) — data da resposta
- `reply_body` (text, nullable) — conteúdo da resposta enviada
- `ip_address` (string, 45, nullable) — IP do remetente
- `user_agent` (text, nullable) — user agent
- `created_at`, `updated_at`, `deleted_at`

### Configuração de Contato (`contato_config`)
Modelo chave-valor (ou uma única linha) para os dados de contato exibidos no portal:
- `phone` (string, 255)
- `email` (string, 255)
- `address` (text)
- `zipcode` (string, 20, nullable)
- `po_box` (string, 50, nullable) — Caixa Postal
- `business_hours` (string, 255) — ex: "8:00 AM – 17:00 PM"
- `map_embed_url` (text) — URL do iframe do Google Maps
- `map_coordinates` (string, 100, nullable) — lat,lng
- `auto_reply_subject` (string, 255, nullable)
- `auto_reply_body` (text, nullable)
- `recipient_email` (string, 255) — e-mail que recebe as notificações

## 3. Dependências

- **Módulos:** Nenhum específico
- **Serviços:** `ActivityLoggerService`, serviço de e-mail (Mail do Laravel 5.5)
- **Pacotes:** Nenhum adicional (pode-se usar o Mail nativo do Laravel 5.5 com Markdown)
- **Recaptcha:** Google reCAPTCHA (v2 ou v3) — adicionar `google/recaptcha` via Composer ou implementar validação com cURL

## 4. Tabelas necessárias

### `v2_mensagens_contato`

| Coluna | Tipo | Restrições |
|---|---|---|
| id | uuid | PK |
| name | varchar(255) | NOT NULL |
| email | varchar(255) | NOT NULL |
| phone | varchar(50) | NULL |
| subject | varchar(255) | NOT NULL |
| message | text | NOT NULL |
| is_read | boolean | DEFAULT false |
| is_archived | boolean | DEFAULT false |
| replied_at | timestamp | NULL |
| reply_body | text | NULL |
| ip_address | varchar(45) | NULL |
| user_agent | text | NULL |
| created_at | timestamp | NULL |
| updated_at | timestamp | NULL |
| deleted_at | timestamp | NULL |

### `v2_contato_configs`

| Coluna | Tipo | Restrições |
|---|---|---|
| id | uuid | PK |
| key | varchar(255) | UNIQUE, NOT NULL |
| value | text | NULL |
| created_at | timestamp | NULL |
| updated_at | timestamp | NULL |

## 5. Arquivos necessários

```
V2/Modules/Contato/FaleConosco/
├── Controllers/
│   ├── FaleConoscoCmsController.php       # Caixa de entrada e configurações (CMS)
│   └── FaleConoscoPortalController.php    # Formulário público e exibição
├── Models/
│   ├── MensagemContato.php
│   └── ContatoConfig.php
├── Requests/
│   ├── ContatoStoreRequest.php            # Validação do formulário público
│   └── ContatoReplyRequest.php            # Validação da resposta no CMS
├── Mail/
│   └── ContatoReply.php                   # Mailable para resposta
├── Views/
│   ├── cms/
│   │   ├── inbox.blade.php                # Caixa de entrada (lista de mensagens)
│   │   ├── show.blade.php                 # Visualizar mensagem
│   │   └── config.blade.php               # Configurações de contato
│   └── portal/
│       └── index.blade.php                # Página fale-conosco
├── Migrations/
│   ├── 2024_01_01_000007_create_v2_mensagens_contato_table.php
│   └── 2024_01_01_000008_create_v2_contato_configs_table.php
├── Routes/
│   ├── cms.php
│   └── web.php
└── ModuleProvider.php
```

**Total estimado: 16 arquivos**

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

### Migrações

```php
// v2_mensagens_contato
Schema::create('v2_mensagens_contato', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->string('name', 255);
    $table->string('email', 255);
    $table->string('phone', 50)->nullable();
    $table->string('subject', 255);
    $table->text('message');
    $table->boolean('is_read')->default(false);
    $table->boolean('is_archived')->default(false);
    $table->timestamp('replied_at')->nullable();
    $table->text('reply_body')->nullable();
    $table->string('ip_address', 45)->nullable();
    $table->text('user_agent')->nullable();
    $table->timestamps();
    $table->softDeletes();
});

// v2_contato_configs
Schema::create('v2_contato_configs', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->string('key', 255)->unique();
    $table->text('value')->nullable();
    $table->timestamps();
});

// down(): Schema::rename('v2_mensagens_contato', 'backup_v2_mensagens_contato');
```

### Models

```php
class MensagemContato extends Model
{
    public $incrementing = false;
    protected $keyType = 'string';
    protected $fillable = [
        'name', 'email', 'phone', 'subject', 'message',
        'is_read', 'is_archived', 'replied_at', 'reply_body',
        'ip_address', 'user_agent',
    ];
    protected $casts = [
        'is_read' => 'boolean',
        'is_archived' => 'boolean',
        'replied_at' => 'datetime',
    ];
}

class ContatoConfig extends Model
{
    public $incrementing = false;
    protected $keyType = 'string';
    protected $fillable = ['key', 'value'];
}
```

### Helper para config

```php
function contatoConfig($key, $default = null) {
    $config = ContatoConfig::where('key', $key)->first();
    return $config ? $config->value : $default;
}
```

### Controller (Portal)

- `index()`: apenas renderiza a view com o formulário. Os dados de contato são injetados via view composer ou diretamente com o helper `contatoConfig()`.
- `store()`: valida com `ContatoStoreRequest` (nome obrigatório, email válido, assunto obrigatório, mensagem obrigatória, reCAPTCHA), salva a mensagem, envia e-mail de notificação para o `recipient_email` e e-mail de auto-resposta para o remetente. Flash message de sucesso.

### Controller (CMS)

- `inbox()`: lista mensagens não arquivadas, ordenadas por data decrescente, com paginação. Badge de não lidas.
- `show($id)`: exibe a mensagem completa, marca como lida automaticamente.
- `reply($id)`: abre formulário de resposta, envia e-mail via `ContatoReply` Mailable, registra `replied_at` e `reply_body`.
- `archive($id)`: marca como arquivada.
- `config()`: exibe formulário com todos os campos de configuração. Salva como pares chave-valor na `v2_contato_configs`.

### ModuleProvider

```php
return [
    'title'          => 'Fale Conosco',
    'description'    => 'Gerenciamento de mensagens e configurações de contato.',
    'icon'           => 'bi bi-envelope',
    'position'       => 7,
    'active_pattern' => ['cms/fale-conosco*'],
    'route'          => 'v2.fale-conosco.cms.inbox',
    'subModulesLevel1' => [
        [
            'title'    => 'Caixa de Entrada',
            'icon'     => 'bi bi-inbox',
            'route'    => 'v2.fale-conosco.cms.inbox',
            'position' => 1,
        ],
        [
            'title'    => 'Configurações',
            'icon'     => 'bi bi-gear',
            'route'    => 'v2.fale-conosco.cms.config',
            'position' => 2,
        ],
    ],
];
```

### Rotas

- **web.php:**
  ```php
  Route::get('/fale-conosco', 'FaleConoscoPortalController@index')->name('v2.fale-conosco.portal.index');
  Route::post('/fale-conosco', 'FaleConoscoPortalController@store')->name('v2.fale-conosco.portal.store');
  ```

- **cms.php:** prefix `cms/fale-conosco`, middleware `CmsAuthenticate`
  ```php
  Route::get('/', 'FaleConoscoCmsController@inbox')->name('v2.fale-conosco.cms.inbox');
  Route::get('/{id}', 'FaleConoscoCmsController@show')->name('v2.fale-conosco.cms.show');
  Route::post('/{id}/reply', 'FaleConoscoCmsController@reply')->name('v2.fale-conosco.cms.reply');
  Route::post('/{id}/archive', 'FaleConoscoCmsController@archive')->name('v2.fale-conosco.cms.archive');
  Route::get('/config', 'FaleConoscoCmsController@config')->name('v2.fale-conosco.cms.config');
  Route::post('/config', 'FaleConoscoCmsController@saveConfig')->name('v2.fale-conosco.cms.config.save');
  ```

### Observações sobre reCAPTCHA

Adicionar validação no `ContatoStoreRequest`:

```php
public function rules()
{
    return [
        'name' => 'required|string|max:255',
        'email' => 'required|email|max:255',
        'phone' => 'nullable|string|max:50',
        'subject' => 'required|string|max:255',
        'message' => 'required|string',
        'g-recaptcha-response' => 'required|string',
    ];
}

public function withValidator($validator)
{
    $validator->after(function ($validator) {
        $recaptcha = $this->input('g-recaptcha-response');
        $response = \Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
            'secret' => config('services.recaptcha.secret'),
            'response' => $recaptcha,
        ]);
        if (!$response->json('success')) {
            $validator->errors()->add('g-recaptcha-response', 'reCAPTCHA inválido.');
        }
    });
}
```

### Mapa

Na view portal, usar o `map_embed_url` configurado no CMS para renderizar o iframe do Google Maps. Valor padrão:

```
https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3839.093734052232!2d-47.8956582252074!3d-15.799010784841979!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x935a3b236a36384b%3A0x41a0b3393a875874!2sCentro%20Empresarial%20Assis%20Chateaubriand!5e0!3m2!1spt-BR!2sbr
```

### Seed dos dados de contato

Criar seeder que insere os valores padrão na `v2_contato_configs`:

| key | value |
|---|---|
| phone | (81) 99602-4167 |
| email | contato@sucesu.org.br |
| address | SRTVS Quadra 701, Conjunto L nº 38, Bloco 01, Sala nº 533, Ed. Assis Chateaubriand, Asa Sul, Brasília/DF |
| zipcode | 70.340-906 |
| po_box | 333 |
| business_hours | 8:00 AM – 17:00 PM |
| recipient_email | contato@sucesu.org.br |

## 7. Detalhamento de Implementação V2

### a) ModuleProvider.php

```php
<?php

namespace V2\Modules\Contato\FaleConosco;

use V2\Core\Contracts\ModuleProviderInterface;

class ModuleProvider implements ModuleProviderInterface
{
    public function __construct()
    {
        \View::composer('v2-fale-conosco::portal.*', function ($view) {
            $configs = \V2\Modules\Contato\FaleConosco\Models\ContatoConfig::pluck('value', 'key')->toArray();
            $view->with('contatoConfig', $configs);
        });
    }

    public function getConfig(): array
    {
        return [
            'title'          => 'Fale Conosco',
            'description'    => 'Gerenciamento de mensagens e configurações de contato.',
            'icon'           => 'bi bi-envelope',
            'position'       => 7,
            'active_pattern' => ['cms/fale-conosco*'],
            'route'          => 'v2.fale-conosco.cms.inbox',
            'subModulesLevel1' => [
                [
                    'title'    => 'Caixa de Entrada',
                    'icon'     => 'bi bi-inbox',
                    'route'    => 'v2.fale-conosco.cms.inbox',
                    'position' => 1,
                ],
                [
                    'title'    => 'Configurações',
                    'icon'     => 'bi bi-gear',
                    'route'    => 'v2.fale-conosco.cms.config',
                    'position' => 2,
                ],
            ],
        ];
    }
}
```

### b) Migrations (safe-rollback)

**Migration 1 — Mensagens de Contato:**

```php
<?php

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

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

        try {
            Schema::create('v2_mensagens_contato', function (Blueprint $table) {
                $table->uuid('id')->primary();
                $table->string('name', 255);
                $table->string('email', 255);
                $table->string('phone', 50)->nullable();
                $table->string('subject', 255);
                $table->text('message');
                $table->boolean('is_read')->default(false);
                $table->boolean('is_archived')->default(false);
                $table->timestamp('replied_at')->nullable();
                $table->text('reply_body')->nullable();
                $table->string('ip_address', 45)->nullable();
                $table->text('user_agent')->nullable();
                $table->string('created_by', 36)->nullable();
                $table->timestamps();
                $table->softDeletes();
                $table->index('is_read', 'idx_v2_msg_contato_read');
                $table->index('is_archived', 'idx_v2_msg_contato_archived');
                $table->index('created_at', 'idx_v2_msg_contato_created');
            });

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

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

**Migration 2 — Configurações de Contato:**

```php
<?php

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

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

        try {
            Schema::create('v2_contato_configs', function (Blueprint $table) {
                $table->uuid('id')->primary();
                $table->string('key', 255)->unique();
                $table->text('value')->nullable();
                $table->timestamps();
                $table->index('key', 'idx_v2_contato_configs_key');
            });

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

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

### c) Models

**MensagemContato.php:**

```php
<?php

namespace V2\Modules\Contato\FaleConosco\Models;

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

class MensagemContato extends Model
{
    use SoftDeletes;

    protected $table = 'v2_mensagens_contato';

    public $incrementing = false;

    protected $keyType = 'string';

    protected $fillable = [
        'name', 'email', 'phone', 'subject', 'message',
        'is_read', 'is_archived', 'replied_at', 'reply_body',
        'ip_address', 'user_agent',
    ];

    protected $casts = [
        'is_read'     => 'boolean',
        'is_archived' => 'boolean',
        'replied_at'  => 'datetime',
        'metadata'    => 'array',
    ];

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

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

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

    public function scopeUnread($query)
    {
        return $query->where('is_read', false);
    }

    public function scopeInbox($query)
    {
        return $query->where('is_archived', false);
    }

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

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

    public function markAsRead()
    {
        if (!$this->is_read) {
            $this->is_read = true;
            $this->save();
        }
    }
}
```

**ContatoConfig.php:**

```php
<?php

namespace V2\Modules\Contato\FaleConosco\Models;

use Illuminate\Database\Eloquent\Model;

class ContatoConfig extends Model
{
    protected $table = 'v2_contato_configs';

    public $incrementing = false;

    protected $keyType = 'string';

    protected $fillable = ['key', 'value'];

    protected $casts = ['metadata' => 'array'];

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

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

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

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

```php
<?php

namespace V2\Modules\Contato\FaleConosco\Controllers;

use V2\Core\Controllers\BaseController;
use Illuminate\Http\Request;
use V2\Modules\Contato\FaleConosco\Models\MensagemContato;
use V2\Modules\Contato\FaleConosco\Models\ContatoConfig;
use V2\Services\ActivityLogger\ActivityLoggerService;
use V2\Services\SEO\Services\SEOService;
use Sentinel;
use Mail;

class FaleConoscoCmsController 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 inbox(Request $request)
    {
        $query = MensagemContato::inbox();

        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('subject', 'ilike', '%' . $searchTerm . '%');
            });
        }

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

        $unreadCount = MensagemContato::inbox()->unread()->count();
        $items = $query->recent()->paginate(20);

        return view('v2-fale-conosco::cms.inbox', compact('items', 'unreadCount'));
    }

    public function show($id)
    {
        $model = MensagemContato::findOrFail($id);
        $model->markAsRead();

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

        return view('v2-fale-conosco::cms.show', compact('model'));
    }

    public function reply(Request $request, $id)
    {
        $model = MensagemContato::findOrFail($id);

        $data = $request->validate([
            'reply_body' => 'required|string',
        ]);

        $model->reply_body = $data['reply_body'];
        $model->replied_at = now();
        $model->save();

        // Enviar e-mail de resposta
        try {
            Mail::send([], [], function ($message) use ($model) {
                $message->to($model->email, $model->name)
                        ->subject('Re: ' . $model->subject)
                        ->setBody($model->reply_body, 'text/html');
            });
        } catch (\Exception $e) {
            \Log::error('Falha ao enviar resposta de contato: ' . $e->getMessage());
        }

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

        flash('Resposta enviada com sucesso!')->success();
        return redirect()->route('v2.fale-conosco.cms.show', ['id' => $model->id]);
    }

    public function archive($id)
    {
        $model = MensagemContato::findOrFail($id);
        $model->is_archived = true;
        $model->save();

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

        flash('Mensagem arquivada com sucesso!')->success();
        return redirect()->route('v2.fale-conosco.cms.inbox');
    }

    public function config()
    {
        $configs = ContatoConfig::pluck('value', 'key')->toArray();
        return view('v2-fale-conosco::cms.config', compact('configs'));
    }

    public function saveConfig(Request $request)
    {
        $data = $request->validate([
            'phone'           => 'nullable|string|max:255',
            'email'           => 'nullable|email|max:255',
            'address'         => 'nullable|string',
            'zipcode'         => 'nullable|string|max:20',
            'po_box'          => 'nullable|string|max:50',
            'business_hours'  => 'nullable|string|max:255',
            'map_embed_url'   => 'nullable|string',
            'map_coordinates' => 'nullable|string|max:100',
            'auto_reply_subject' => 'nullable|string|max:255',
            'auto_reply_body' => 'nullable|string',
            'recipient_email' => 'nullable|email|max:255',
        ]);

        foreach ($data as $key => $value) {
            ContatoConfig::updateOrCreate(
                ['key' => $key],
                ['value' => $value]
            );
        }

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

        flash('Configurações salvas com sucesso!')->success();
        return redirect()->route('v2.fale-conosco.cms.config');
    }
}
```

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

```php
<?php

namespace V2\Modules\Contato\FaleConosco\Controllers;

use V2\Core\Controllers\BaseController;
use Illuminate\Http\Request;
use V2\Modules\Contato\FaleConosco\Models\MensagemContato;
use V2\Modules\Contato\FaleConosco\Models\ContatoConfig;
use V2\Services\SEO\Services\SEOService;
use Sentinel;

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

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

    public function index()
    {
        $this->seo->setTitle('Fale Conosco — ' . config('app.name'));
        $this->seo->setDescription('Entre em contato com a SUCESU Nacional.');

        $configs = ContatoConfig::pluck('value', 'key')->toArray();

        return view('v2-fale-conosco::portal.index', compact('configs'));
    }

    public function store(Request $request)
    {
        $data = $request->validate([
            'name'    => 'required|string|max:255',
            'email'   => 'required|email|max:255',
            'phone'   => 'nullable|string|max:50',
            'subject' => 'required|string|max:255',
            'message' => 'required|string',
            'g-recaptcha-response' => 'required|string',
        ]);

        // Validar reCAPTCHA
        $recaptcha = $data['g-recaptcha-response'];
        $response = \Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
            'secret'   => config('services.recaptcha.secret'),
            'response' => $recaptcha,
        ]);
        if (!$response->json('success')) {
            return redirect()->back()->withErrors(['g-recaptcha-response' => 'reCAPTCHA inválido.']);
        }

        $model = new MensagemContato();
        $model->id = v2_uuid();
        $model->name = $data['name'];
        $model->email = $data['email'];
        $model->phone = $data['phone'] ?? null;
        $model->subject = $data['subject'];
        $model->message = $data['message'];
        $model->ip_address = $request->ip();
        $model->user_agent = $request->header('User-Agent');
        $model->save();

        // Notificar administrador
        $recipientEmail = ContatoConfig::where('key', 'recipient_email')->value('value');
        if (!empty($recipientEmail)) {
            try {
                Mail::send([], [], function ($message) use ($model, $recipientEmail) {
                    $message->to($recipientEmail)
                            ->subject('[Contato SUCESU] ' . $model->subject)
                            ->setBody(
                                "Nome: {$model->name}\nE-mail: {$model->email}\nTelefone: {$model->phone}\n\n{$model->message}",
                                'text/plain'
                            );
                });
            } catch (\Exception $e) {
                \Log::error('Falha notificação contato: ' . $e->getMessage());
            }
        }

        // Auto-resposta
        $autoReplySubject = ContatoConfig::where('key', 'auto_reply_subject')->value('value');
        $autoReplyBody = ContatoConfig::where('key', 'auto_reply_body')->value('value');
        if (!empty($autoReplyBody)) {
            try {
                Mail::send([], [], function ($message) use ($model, $autoReplySubject, $autoReplyBody) {
                    $message->to($model->email, $model->name)
                            ->subject($autoReplySubject ?? 'Recebemos sua mensagem — SUCESU')
                            ->setBody($autoReplyBody, 'text/html');
                });
            } catch (\Exception $e) {
                \Log::error('Falha auto-resposta contato: ' . $e->getMessage());
            }
        }

        flash('Mensagem enviada com sucesso! Em breve entraremos em contato.')->success();
        return redirect()->route('v2.fale-conosco.portal.index');
    }
}
```

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

```php
<?php

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

    Route::get('/', [
        'as'   => 'v2.fale-conosco.cms.inbox',
        'uses' => 'FaleConoscoCmsController@inbox',
    ]);

    Route::get('/{id}', [
        'as'   => 'v2.fale-conosco.cms.show',
        'uses' => 'FaleConoscoCmsController@show',
    ]);

    Route::post('/{id}/reply', [
        'as'   => 'v2.fale-conosco.cms.reply',
        'uses' => 'FaleConoscoCmsController@reply',
    ]);

    Route::post('/{id}/archive', [
        'as'   => 'v2.fale-conosco.cms.archive',
        'uses' => 'FaleConoscoCmsController@archive',
    ]);

    Route::get('/config', [
        'as'   => 'v2.fale-conosco.cms.config',
        'uses' => 'FaleConoscoCmsController@config',
    ]);

    Route::post('/config', [
        'as'   => 'v2.fale-conosco.cms.config.save',
        'uses' => 'FaleConoscoCmsController@saveConfig',
    ]);
});
```

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

```php
<?php

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

    Route::get('/fale-conosco', [
        'as'   => 'v2.fale-conosco.portal.index',
        'uses' => 'FaleConoscoPortalController@index',
    ]);

    Route::post('/fale-conosco', [
        'as'   => 'v2.fale-conosco.portal.store',
        'uses' => 'FaleConoscoPortalController@store',
    ]);
});
```

### h) View CMS — Caixa de Entrada (cms/inbox.blade.php)

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

@section('title', 'Fale Conosco')
@section('header-title', 'Fale Conosco')
@section('header-description', 'Caixa de entrada de mensagens')

@section('content')
    <div class="card">
        <div class="card-header d-flex justify-content-between align-items-center">
            <div>
                <span class="badge bg-info me-2">{{ $unreadCount }} não lida(s)</span>
                <form method="GET" class="d-inline-flex gap-2 align-items-center">
                    <input type="text" name="search" class="form-control form-control-sm"
                           placeholder="Buscar..." value="{{ request('search') }}">
                    <select name="filter_read" class="form-select form-select-sm">
                        <option value="">Todas</option>
                        <option value="0" {{ request('filter_read') === '0' ? 'selected' : '' }}>Não lidas</option>
                        <option value="1" {{ request('filter_read') === '1' ? 'selected' : '' }}>Lidas</option>
                    </select>
                    <button type="submit" class="btn btn-sm btn-outline-secondary">
                        <i class="bi bi-search"></i>
                    </button>
                </form>
            </div>
        </div>
        <div class="card-body">
            @if(count($items) > 0)
                <table class="table table-hover">
                    <thead>
                        <tr>
                            <th>Status</th>
                            <th>Nome</th>
                            <th>Assunto</th>
                            <th>Data</th>
                            <th>Ações</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach($items as $item)
                        <tr class="{{ !$item->is_read ? 'fw-bold' : '' }}">
                            <td>
                                <span class="badge bg-{{ $item->is_read ? 'secondary' : 'primary' }}">
                                    {{ $item->is_read ? 'Lida' : 'Nova' }}
                                </span>
                            </td>
                            <td>{{ $item->name }}<br><small class="text-muted">{{ $item->email }}</small></td>
                            <td>{{ $item->subject }}</td>
                            <td>{{ $item->created_at->format('d/m/Y H:i') }}</td>
                            <td>
                                <a href="{{ route('v2.fale-conosco.cms.show', ['id' => $item->id]) }}"
                                   class="btn btn-sm btn-outline-primary">
                                    <i class="bi bi-eye"></i>
                                </a>
                                <form action="{{ route('v2.fale-conosco.cms.archive', ['id' => $item->id]) }}"
                                      method="POST" class="d-inline">
                                    {{ csrf_field() }}
                                    <button type="submit" class="btn btn-sm btn-outline-warning"
                                            onclick="return confirm('Arquivar esta mensagem?');">
                                        <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">Nenhuma mensagem encontrada.</p>
                </div>
            @endif
        </div>
    </div>
@endsection
```

### i) View Portal (portal/index.blade.php)

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

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

@section('content')
    <section class="contato-section section-padding fix">
        <div class="container">
            <div class="row">
                <div class="col-lg-6">
                    <h2>Fale Conosco</h2>
                    <p>Envie sua mensagem e entraremos em contato o mais breve possível.</p>

                    @if(session('success'))
                        <div class="alert alert-success">{{ session('success') }}</div>
                    @endif

                    <form method="POST" action="{{ route('v2.fale-conosco.portal.store') }}">
                        {{ csrf_field() }}
                        <div class="mb-3">
                            <label class="form-label">Nome *</label>
                            <input type="text" name="name" class="form-control" required maxlength="255">
                        </div>
                        <div class="mb-3">
                            <label class="form-label">E-mail *</label>
                            <input type="email" name="email" class="form-control" required maxlength="255">
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Telefone</label>
                            <input type="text" name="phone" class="form-control" maxlength="50">
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Assunto *</label>
                            <input type="text" name="subject" class="form-control" required maxlength="255">
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Mensagem *</label>
                            <textarea name="message" class="form-control" rows="5" required></textarea>
                        </div>
                        <div class="mb-3">
                            <div class="g-recaptcha" data-sitekey="{{ config('services.recaptcha.key') }}"></div>
                            @error('g-recaptcha-response')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                        <button type="submit" class="btn btn-primary">Enviar Mensagem</button>
                    </form>
                </div>
                <div class="col-lg-5 offset-lg-1">
                    <h2>Informações de Contato</h2>
                    <div class="contato-info mt-4">
                        @if(!empty($configs['phone']))
                            <p><strong>Telefone:</strong> {{ $configs['phone'] }}</p>
                        @endif
                        @if(!empty($configs['email']))
                            <p><strong>E-mail:</strong> {{ $configs['email'] }}</p>
                        @endif
                        @if(!empty($configs['address']))
                            <p><strong>Endereço:</strong> {{ $configs['address'] }}</p>
                        @endif
                        @if(!empty($configs['zipcode']))
                            <p><strong>CEP:</strong> {{ $configs['zipcode'] }}</p>
                        @endif
                        @if(!empty($configs['business_hours']))
                            <p><strong>Horário:</strong> {{ $configs['business_hours'] }}</p>
                        @endif
                    </div>
                </div>
            </div>

            @if(!empty($configs['map_embed_url']))
                <div class="row mt-5">
                    <div class="col-12">
                        <div class="map-container">
                            {!! $configs['map_embed_url'] !!}
                        </div>
                    </div>
                </div>
            @endif
        </div>
    </section>
@endsection

@push('scripts')
    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
@endpush
```

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

- **ActivityLoggerService**: Injetado no `FaleConoscoCmsController` para auditoria (leitura, resposta, arquivamento, configuração).
- **SEOService**: Injetado em ambos os controllers para metadados de página.
- **Mail (Laravel 5.5)**: Envio de notificação ao administrador e auto-resposta ao remetente via `Mail::send()` raw.
- **reCAPTCHA**: Google reCAPTCHA v2. Validado no `store()` do PortalController via cURL para `siteverify`.
- **ViewComposer**: No `ModuleProvider`, injeta `$contatoConfig` em todas as views do portal com as configurações de contato.
- **ContatoConfig**: Modelo chave-valor para configurações. Usar `ContatoConfig::pluck('value', 'key')->toArray()` para obter todas.
- **Permissões**: `@v2can('FaleConosco', 'write')` nas views CMS para controle de acesso.
