This commit is contained in:
Lizandro Guarnizo
2026-04-24 21:40:10 -05:00
parent 877a0d393a
commit 148fa6b9f9
36 changed files with 3037 additions and 0 deletions
@@ -0,0 +1,71 @@
<?php
namespace App\Http\Livewire\Whatsapp;
use App\Models\WhatsappSystemConfig;
use Livewire\Component;
use Jantinnerezo\LivewireAlert\LivewireAlert;
class ShowConfiguracionBot extends Component
{
use LivewireAlert;
public string $whatsapp_token = '';
public string $phone_number_id = '';
public string $whatsapp_api_url = '';
public string $webhook_verify_token = '';
public string $bot_enabled = '1';
public string $welcome_message = '';
public string $default_no_match = '';
public string $advisor_message = '';
public string $business_hours_enabled = '0';
public string $business_hours_start = '';
public string $business_hours_end = '';
protected $rules = [
'whatsapp_token' => 'required|string',
'phone_number_id' => 'required|string',
'whatsapp_api_url' => 'required|url',
'webhook_verify_token' => 'required|string',
'welcome_message' => 'required|string|max:1000',
'default_no_match' => 'required|string|max:500',
'advisor_message' => 'required|string|max:500',
];
public function mount(): void
{
$keys = [
'whatsapp_token', 'phone_number_id', 'whatsapp_api_url',
'webhook_verify_token', 'bot_enabled', 'welcome_message',
'default_no_match', 'advisor_message', 'business_hours_enabled',
'business_hours_start', 'business_hours_end',
];
foreach ($keys as $key) {
$this->{$key} = WhatsappSystemConfig::get($key);
}
}
public function save(): void
{
$this->validate();
$keys = [
'whatsapp_token', 'phone_number_id', 'whatsapp_api_url',
'webhook_verify_token', 'bot_enabled', 'welcome_message',
'default_no_match', 'advisor_message', 'business_hours_enabled',
'business_hours_start', 'business_hours_end',
];
foreach ($keys as $key) {
WhatsappSystemConfig::set($key, $this->{$key});
}
$this->alert('success', 'Configuración guardada correctamente.');
}
public function render()
{
return view('livewire.whatsapp.show-configuracion-bot');
}
}
@@ -0,0 +1,112 @@
<?php
namespace App\Http\Livewire\Whatsapp;
use App\Models\WhatsappConversation;
use App\Models\WhatsappUser;
use Livewire\Component;
use Livewire\WithPagination;
use Illuminate\Support\Facades\Http;
use App\Models\WhatsappSystemConfig;
class ShowConversaciones extends Component
{
use WithPagination;
public ?int $selectedUserId = null;
public string $search = '';
public string $replyText = '';
public ?WhatsappUser $selectedUser = null;
protected $listeners = ['refreshChatList' => '$refresh'];
public function selectUser(int $userId): void
{
$this->selectedUserId = $userId;
$this->selectedUser = WhatsappUser::find($userId);
$this->replyText = '';
// Marcar mensajes como leídos
WhatsappConversation::where('user_id', $userId)
->where('direction', 'incoming')
->where('is_read', 0)
->update(['is_read' => 1]);
}
public function sendReply(): void
{
$this->validate(['replyText' => 'required|string|max:4096']);
if (! $this->selectedUser) {
return;
}
$token = WhatsappSystemConfig::get('whatsapp_token');
$phoneNumberId = WhatsappSystemConfig::get('phone_number_id');
$apiUrl = WhatsappSystemConfig::get('whatsapp_api_url', 'https://graph.facebook.com/v22.0/');
$response = Http::withToken($token)
->post("{$apiUrl}{$phoneNumberId}/messages", [
'messaging_product' => 'whatsapp',
'to' => $this->selectedUser->phone_number,
'type' => 'text',
'text' => ['body' => $this->replyText],
]);
if ($response->successful()) {
WhatsappConversation::create([
'user_id' => $this->selectedUserId,
'message_id' => $response->json('messages.0.id'),
'direction' => 'outgoing',
'message_type' => 'text',
'content' => $this->replyText,
'status' => 'sent',
]);
$this->replyText = '';
$this->dispatch('scrollToBottom');
} else {
session()->flash('error', 'Error al enviar el mensaje: ' . $response->body());
}
}
public function toggleBlockUser(): void
{
if (! $this->selectedUser) {
return;
}
$newStatus = $this->selectedUser->status === 'blocked' ? 'active' : 'blocked';
$this->selectedUser->update(['status' => $newStatus]);
$this->selectedUser->refresh();
}
public function render()
{
$users = WhatsappUser::when($this->search, function ($q) {
$q->where(function ($q2) {
$q2->where('name', 'like', '%' . $this->search . '%')
->orWhere('phone_number', 'like', '%' . $this->search . '%');
});
})
->withCount(['conversations as unread_count' => function ($q) {
$q->where('direction', 'incoming')->where('is_read', 0);
}])
->with('lastMessage')
->orderByDesc(
WhatsappConversation::select('created_at')
->whereColumn('user_id', 'whatsapp_users.id')
->orderByDesc('created_at')
->limit(1)
)
->paginate(20);
$messages = $this->selectedUserId
? WhatsappConversation::where('user_id', $this->selectedUserId)
->orderBy('created_at')
->get()
: collect();
return view('livewire.whatsapp.show-conversaciones', compact('users', 'messages'));
}
}
@@ -0,0 +1,50 @@
<?php
namespace App\Http\Livewire\Whatsapp;
use App\Models\WhatsappWebhookLog;
use Livewire\Component;
use Livewire\WithPagination;
class ShowLogsWebhook extends Component
{
use WithPagination;
public string $search = '';
public ?int $viewId = null;
public ?string $viewPayload = null;
public function viewLog(int $id): void
{
$log = WhatsappWebhookLog::findOrFail($id);
$this->viewId = $id;
$decoded = json_decode($log->payload, true);
$this->viewPayload = json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
public function clearView(): void
{
$this->viewId = null;
$this->viewPayload = null;
}
public function deleteLog(int $id): void
{
WhatsappWebhookLog::findOrFail($id)->delete();
}
public function clearAll(): void
{
WhatsappWebhookLog::truncate();
}
public function render()
{
$logs = WhatsappWebhookLog::when($this->search, fn($q) =>
$q->where('payload', 'like', "%{$this->search}%"))
->orderByDesc('created_at')
->paginate(25);
return view('livewire.whatsapp.show-logs-webhook', compact('logs'));
}
}
+194
View File
@@ -0,0 +1,194 @@
<?php
namespace App\Http\Livewire\Whatsapp;
use App\Models\WhatsappMenu;
use App\Models\WhatsappMenuOption;
use Livewire\Component;
use Jantinnerezo\LivewireAlert\LivewireAlert;
class ShowMenusBot extends Component
{
use LivewireAlert;
// Lista
public string $search = '';
// Form menú
public bool $showMenuForm = false;
public ?int $editMenuId = null;
public string $menuName = '';
public string $menuTitle = '';
public string $menuMessage = '';
public ?int $menuParentId = null;
public bool $menuIsMain = false;
public bool $menuIsActive = true;
public int $menuSortOrder = 0;
// Form opción
public bool $showOptionForm = false;
public ?int $editOptionId = null;
public ?int $optionForMenuId = null;
public int $optionNumber = 1;
public string $optionTitle = '';
public string $optionAction = 'message';
public string $optionValue = '';
public int $optionSort = 0;
public bool $optionIsActive = true;
// Vista detalle
public ?int $viewMenuId = null;
protected function rules(): array
{
return [
'menuName' => 'required|string|max:255',
'menuTitle' => 'required|string|max:255',
'menuMessage' => 'required|string',
];
}
public function openMenuForm(?int $id = null): void
{
$this->resetMenuForm();
if ($id) {
$menu = WhatsappMenu::findOrFail($id);
$this->editMenuId = $id;
$this->menuName = $menu->name;
$this->menuTitle = $menu->title;
$this->menuMessage = $menu->message;
$this->menuParentId = $menu->parent_id;
$this->menuIsMain = (bool) $menu->is_main;
$this->menuIsActive = (bool) $menu->is_active;
$this->menuSortOrder = $menu->sort_order;
}
$this->showMenuForm = true;
}
public function saveMenu(): void
{
$this->validate();
$data = [
'name' => $this->menuName,
'title' => $this->menuTitle,
'message' => $this->menuMessage,
'parent_id' => $this->menuParentId ?: null,
'is_main' => $this->menuIsMain,
'is_active' => $this->menuIsActive,
'sort_order' => $this->menuSortOrder,
];
if ($this->editMenuId) {
WhatsappMenu::findOrFail($this->editMenuId)->update($data);
$this->alert('success', 'Menú actualizado correctamente.');
} else {
WhatsappMenu::create($data);
$this->alert('success', 'Menú creado correctamente.');
}
$this->resetMenuForm();
}
public function deleteMenu(int $id): void
{
WhatsappMenu::findOrFail($id)->delete();
$this->alert('success', 'Menú eliminado.');
if ($this->viewMenuId === $id) {
$this->viewMenuId = null;
}
}
public function openOptionForm(?int $optionId = null, ?int $menuId = null): void
{
$this->resetOptionForm();
$this->optionForMenuId = $menuId;
if ($optionId) {
$opt = WhatsappMenuOption::findOrFail($optionId);
$this->editOptionId = $optionId;
$this->optionForMenuId = $opt->menu_id;
$this->optionNumber = $opt->option_number;
$this->optionTitle = $opt->title;
$this->optionAction = $opt->action_type;
$this->optionValue = $opt->action_value ?? '';
$this->optionSort = $opt->sort_order;
$this->optionIsActive = (bool) $opt->is_active;
}
$this->showOptionForm = true;
}
public function saveOption(): void
{
$this->validate([
'optionTitle' => 'required|string|max:255',
'optionForMenuId' => 'required|integer',
'optionNumber' => 'required|integer|min:1',
]);
$data = [
'menu_id' => $this->optionForMenuId,
'option_number' => $this->optionNumber,
'title' => $this->optionTitle,
'action_type' => $this->optionAction,
'action_value' => $this->optionValue,
'sort_order' => $this->optionSort,
'is_active' => $this->optionIsActive,
];
if ($this->editOptionId) {
WhatsappMenuOption::findOrFail($this->editOptionId)->update($data);
$this->alert('success', 'Opción actualizada.');
} else {
WhatsappMenuOption::create($data);
$this->alert('success', 'Opción creada.');
}
$this->resetOptionForm();
}
public function deleteOption(int $id): void
{
WhatsappMenuOption::findOrFail($id)->delete();
$this->alert('success', 'Opción eliminada.');
}
public function resetMenuForm(): void
{
$this->showMenuForm = false;
$this->editMenuId = null;
$this->menuName = '';
$this->menuTitle = '';
$this->menuMessage = '';
$this->menuParentId = null;
$this->menuIsMain = false;
$this->menuIsActive = true;
$this->menuSortOrder = 0;
}
public function resetOptionForm(): void
{
$this->showOptionForm = false;
$this->editOptionId = null;
$this->optionForMenuId = null;
$this->optionNumber = 1;
$this->optionTitle = '';
$this->optionAction = 'message';
$this->optionValue = '';
$this->optionSort = 0;
$this->optionIsActive = true;
}
public function render()
{
$menus = WhatsappMenu::when($this->search, fn($q) => $q->where('name', 'like', "%{$this->search}%")
->orWhere('title', 'like', "%{$this->search}%"))
->withCount('options')
->orderBy('sort_order')
->get();
$viewMenu = $this->viewMenuId ? WhatsappMenu::with('options')->find($this->viewMenuId) : null;
$allMenus = WhatsappMenu::orderBy('title')->get();
return view('livewire.whatsapp.show-menus-bot', compact('menus', 'viewMenu', 'allMenus'));
}
}
@@ -0,0 +1,113 @@
<?php
namespace App\Http\Livewire\Whatsapp;
use App\Models\WhatsappMessageTemplate;
use Livewire\Component;
use Jantinnerezo\LivewireAlert\LivewireAlert;
class ShowPlantillas extends Component
{
use LivewireAlert;
public string $search = '';
public string $filterStatus = '';
// Formulario
public bool $showForm = false;
public ?int $editId = null;
public string $name = '';
public string $templateName = '';
public string $languageCode = 'es';
public string $status = 'pending';
public string $bodyText = '';
public string $headerType = 'none';
public string $headerText = '';
public string $footerText = '';
protected function rules(): array
{
return [
'name' => 'required|string|max:255',
'templateName' => 'required|string|max:255|regex:/^[a-z0-9_]+$/',
'languageCode' => 'required|string|max:10',
'bodyText' => 'required|string',
];
}
public function openForm(?int $id = null): void
{
$this->resetForm();
if ($id) {
$tpl = WhatsappMessageTemplate::findOrFail($id);
$this->editId = $id;
$this->name = $tpl->name;
$this->templateName = $tpl->template_name;
$this->languageCode = $tpl->language_code;
$this->status = $tpl->status;
$this->bodyText = $tpl->body_text ?? '';
$this->headerType = $tpl->header_type;
$this->headerText = $tpl->header_text ?? '';
$this->footerText = $tpl->footer_text ?? '';
}
$this->showForm = true;
}
public function save(): void
{
$this->validate();
$data = [
'name' => $this->name,
'template_name' => $this->templateName,
'language_code' => $this->languageCode,
'status' => $this->status,
'body_text' => $this->bodyText,
'header_type' => $this->headerType,
'header_text' => $this->headerText,
'footer_text' => $this->footerText,
];
if ($this->editId) {
WhatsappMessageTemplate::findOrFail($this->editId)->update($data);
$this->alert('success', 'Plantilla actualizada.');
} else {
WhatsappMessageTemplate::create($data);
$this->alert('success', 'Plantilla creada.');
}
$this->resetForm();
}
public function delete(int $id): void
{
WhatsappMessageTemplate::findOrFail($id)->delete();
$this->alert('success', 'Plantilla eliminada.');
}
public function resetForm(): void
{
$this->showForm = false;
$this->editId = null;
$this->name = '';
$this->templateName = '';
$this->languageCode = 'es';
$this->status = 'pending';
$this->bodyText = '';
$this->headerType = 'none';
$this->headerText = '';
$this->footerText = '';
}
public function render()
{
$templates = WhatsappMessageTemplate::when($this->search, fn($q) =>
$q->where('name', 'like', "%{$this->search}%")
->orWhere('template_name', 'like', "%{$this->search}%"))
->when($this->filterStatus, fn($q) => $q->where('status', $this->filterStatus))
->orderByDesc('updated_at')
->paginate(15);
return view('livewire.whatsapp.show-plantillas', compact('templates'));
}
}
@@ -0,0 +1,100 @@
<?php
namespace App\Http\Livewire\Whatsapp;
use App\Models\WhatsappScheduledMessage;
use App\Models\WhatsappMessageTemplate;
use App\Models\WhatsappUser;
use Livewire\Component;
use Jantinnerezo\LivewireAlert\LivewireAlert;
use Illuminate\Support\Facades\Auth;
class ShowProgramados extends Component
{
use LivewireAlert;
public string $search = '';
public string $filterStatus = '';
// Formulario
public bool $showForm = false;
public string $phoneNumber = '';
public string $messageType = 'text';
public string $messageContent = '';
public ?int $templateId = null;
public string $scheduledDate = '';
public string $scheduledTime = '';
protected function rules(): array
{
return [
'phoneNumber' => 'required|string|max:20',
'scheduledDate' => 'required|date',
'scheduledTime' => 'required',
'messageType' => 'required|in:text,template',
'messageContent'=> 'required_if:messageType,text|nullable|string',
'templateId' => 'required_if:messageType,template|nullable|integer',
];
}
public function save(): void
{
$this->validate();
$template = $this->templateId ? WhatsappMessageTemplate::find($this->templateId) : null;
WhatsappScheduledMessage::create([
'phone_number' => preg_replace('/\D/', '', $this->phoneNumber),
'message_type' => $this->messageType,
'message_content' => $this->messageContent,
'template_id' => $this->templateId,
'template_name' => $template?->template_name,
'template_language' => $template?->language_code,
'scheduled_date' => $this->scheduledDate,
'scheduled_time' => $this->scheduledTime,
'status' => 'pending',
'created_by' => Auth::id(),
]);
$this->alert('success', 'Mensaje programado correctamente.');
$this->resetForm();
}
public function cancel(int $id): void
{
WhatsappScheduledMessage::findOrFail($id)->update(['status' => 'cancelled']);
$this->alert('info', 'Mensaje cancelado.');
}
public function delete(int $id): void
{
WhatsappScheduledMessage::findOrFail($id)->delete();
$this->alert('success', 'Registro eliminado.');
}
public function resetForm(): void
{
$this->showForm = false;
$this->phoneNumber = '';
$this->messageType = 'text';
$this->messageContent = '';
$this->templateId = null;
$this->scheduledDate = '';
$this->scheduledTime = '';
}
public function render()
{
$messages = WhatsappScheduledMessage::when($this->search, fn($q) =>
$q->where('phone_number', 'like', "%{$this->search}%")
->orWhere('message_content', 'like', "%{$this->search}%"))
->when($this->filterStatus, fn($q) => $q->where('status', $this->filterStatus))
->orderByDesc('scheduled_date')
->orderByDesc('scheduled_time')
->paginate(20);
$templates = WhatsappMessageTemplate::where('status', 'approved')->get();
return view('livewire.whatsapp.show-programados', compact('messages', 'templates'));
}
}