token = WhatsappSystemConfig::get('whatsapp_token'); $this->phoneNumberId = WhatsappSystemConfig::get('phone_number_id'); $this->apiUrl = WhatsappSystemConfig::get('whatsapp_api_url', 'https://graph.facebook.com/v22.0/'); } // ────────────────────────────────────────────────────────── // Punto de entrada principal // ────────────────────────────────────────────────────────── public function handle(WhatsappUser $user, string $texto, string $tipo = 'text'): void { // Usuario bloqueado → ignorar if ($user->status === 'blocked') { return; } // Atendido por asesor → solo almacenar, no responder if ($user->in_service) { return; } // Bot deshabilitado para este usuario → ignorar if (! $user->bot_enabled) { return; } // Bot deshabilitado globalmente → ignorar if (WhatsappSystemConfig::get('bot_enabled', '1') !== '1') { return; } // Medios (imagen, video, audio, documento, etc.) → aviso y salir if (! in_array($tipo, ['text', 'interactive'])) { $msg = 'Recibimos tu ' . $tipo . '. Un asesor te contactará pronto.'; $this->saveOutgoing($user->id, $msg); $this->sendText($user->phone_number, $msg); return; } $texto = trim($texto); // ── Comandos especiales ────────────────────────────── if ($this->isMenuCommand($texto)) { $this->enviarMenuPrincipal($user); return; } if ($this->isBackCommand($texto)) { $this->irAtras($user); return; } if ($this->isAdvisorCommand($texto)) { $this->transferirAsesor($user); return; } // ── Primera visita → bienvenida ────────────────────── if (! $user->welcome_sent_at) { $this->enviarBienvenida($user); return; } // ── Navegación por menú ────────────────────────────── $this->procesarSeleccion($user, $texto); } // ────────────────────────────────────────────────────────── // Comandos // ────────────────────────────────────────────────────────── private function isMenuCommand(string $texto): bool { return in_array(mb_strtolower($texto), ['menu', 'menú', 'inicio', 'start', 'hola', 'hi']); } private function isBackCommand(string $texto): bool { return in_array(mb_strtolower($texto), ['atras', 'atrás', 'volver', 'back', 'regresar']); } private function isAdvisorCommand(string $texto): bool { return in_array(mb_strtolower($texto), ['asesor', 'agente', 'humano', 'ayuda']); } // ────────────────────────────────────────────────────────── // Flujos de respuesta // ────────────────────────────────────────────────────────── private function enviarBienvenida(WhatsappUser $user): void { $msg = WhatsappSystemConfig::get( 'welcome_message', '¡Hola! Bienvenido. Escribe *menu* para ver las opciones disponibles.' ); $user->update(['welcome_sent_at' => now(), 'current_menu_id' => null]); $this->saveOutgoing($user->id, $msg); $this->sendText($user->phone_number, $msg); // Mostrar menú principal después del saludo $this->enviarMenuPrincipal($user); } private function enviarMenuPrincipal(WhatsappUser $user): void { $menu = WhatsappMenu::where('is_main', 1)->where('is_active', 1)->first(); if (! $menu) { $msg = WhatsappSystemConfig::get('default_no_match', 'No hay opciones disponibles en este momento.'); $this->saveOutgoing($user->id, $msg); $this->sendText($user->phone_number, $msg); return; } $user->update(['current_menu_id' => $menu->id]); $texto = $this->formatMenu($menu); $this->saveOutgoing($user->id, $texto); $this->sendText($user->phone_number, $texto); } private function irAtras(WhatsappUser $user): void { $menuActual = $user->current_menu_id ? WhatsappMenu::find($user->current_menu_id) : null; $menuPadre = ($menuActual && $menuActual->parent_id) ? WhatsappMenu::find($menuActual->parent_id) : null; if ($menuPadre && $menuPadre->is_active) { $user->update(['current_menu_id' => $menuPadre->id]); $texto = $this->formatMenu($menuPadre); $this->saveOutgoing($user->id, $texto); $this->sendText($user->phone_number, $texto); } else { $this->enviarMenuPrincipal($user); } } private function transferirAsesor(WhatsappUser $user): void { $msg = WhatsappSystemConfig::get( 'advisor_message', 'Un asesor se comunicará contigo en breve. Por favor espera.' ); $user->update([ 'advisor_requested' => true, 'in_service' => false, 'current_menu_id' => null, ]); $this->saveOutgoing($user->id, $msg); $this->sendText($user->phone_number, $msg); } private function procesarSeleccion(WhatsappUser $user, string $texto): void { if (! $user->current_menu_id) { $this->enviarMenuPrincipal($user); return; } $menu = WhatsappMenu::find($user->current_menu_id); if (! $menu || ! $menu->is_active) { $this->enviarMenuPrincipal($user); return; } // Solo aceptar selección numérica $numero = is_numeric($texto) ? (int) $texto : null; $opcion = $numero ? $menu->options()->where('option_number', $numero)->where('is_active', 1)->first() : null; if (! $opcion) { $noMatch = WhatsappSystemConfig::get('default_no_match', 'Opción no reconocida. Escribe el número de la opción deseada.'); $fullMsg = $noMatch . "\n\n" . $this->formatMenu($menu); $this->saveOutgoing($user->id, $fullMsg); $this->sendText($user->phone_number, $fullMsg); return; } match ($opcion->action_type) { 'submenu' => $this->irASubmenu($user, $opcion), 'message' => $this->responderMensaje($user, $opcion), 'advisor' => $this->transferirAsesor($user), 'template' => $this->enviarPlantilla($user, $opcion), default => $this->responderMensaje($user, $opcion), }; } private function irASubmenu(WhatsappUser $user, object $opcion): void { $subMenu = WhatsappMenu::find($opcion->action_value); if (! $subMenu || ! $subMenu->is_active) { $msg = WhatsappSystemConfig::get('default_no_match', 'Opción no disponible en este momento.'); $this->saveOutgoing($user->id, $msg); $this->sendText($user->phone_number, $msg); return; } $user->update(['current_menu_id' => $subMenu->id]); $texto = $this->formatMenu($subMenu); $this->saveOutgoing($user->id, $texto); $this->sendText($user->phone_number, $texto); } private function responderMensaje(WhatsappUser $user, object $opcion): void { $texto = (string) ($opcion->action_value ?? ''); $this->saveOutgoing($user->id, $texto); $this->sendText($user->phone_number, $texto); // Volver al menú principal tras la respuesta $menuPrincipal = WhatsappMenu::where('is_main', 1)->where('is_active', 1)->first(); if ($menuPrincipal) { $user->update(['current_menu_id' => $menuPrincipal->id]); $textoMenu = $this->formatMenu($menuPrincipal); $this->saveOutgoing($user->id, $textoMenu); $this->sendText($user->phone_number, $textoMenu); } } private function enviarPlantilla(WhatsappUser $user, object $opcion): void { $templateName = (string) ($opcion->action_value ?? ''); $payload = [ 'messaging_product' => 'whatsapp', 'to' => $user->phone_number, 'type' => 'template', 'template' => [ 'name' => $templateName, 'language' => ['code' => 'es'], ], ]; $this->saveOutgoing($user->id, "[Plantilla: {$templateName}]"); $this->callApi('messages', $payload); } // ────────────────────────────────────────────────────────── // Helpers de formato // ────────────────────────────────────────────────────────── private function formatMenu(WhatsappMenu $menu): string { $lines = [$menu->message]; foreach ($menu->options()->where('is_active', 1)->get() as $op) { $lines[] = "{$op->option_number}. {$op->title}"; } return implode("\n", $lines); } // ────────────────────────────────────────────────────────── // Envío a la API de WhatsApp Cloud // ────────────────────────────────────────────────────────── public function sendText(string $to, string $body): bool { return $this->callApi('messages', [ 'messaging_product' => 'whatsapp', 'to' => $to, 'type' => 'text', 'text' => ['body' => $body, 'preview_url' => false], ]); } public function markRead(string $messageId): void { $this->callApi('messages', [ 'messaging_product' => 'whatsapp', 'status' => 'read', 'message_id' => $messageId, ]); } private function callApi(string $endpoint, array $payload): bool { if (! $this->token || ! $this->phoneNumberId) { Log::warning('[WhatsApp] Token o phone_number_id no configurados.'); return false; } try { $response = Http::withToken($this->token) ->timeout(10) ->post("{$this->apiUrl}{$this->phoneNumberId}/{$endpoint}", $payload); if (! $response->successful()) { Log::error('[WhatsApp] API error: ' . $response->body()); } return $response->successful(); } catch (\Throwable $e) { Log::error('[WhatsApp] Excepción al llamar API: ' . $e->getMessage()); return false; } } private function saveOutgoing(int $userId, string $content): void { WhatsappConversation::create([ 'user_id' => $userId, 'direction' => 'outgoing', 'message_type' => 'text', 'content' => $content, 'status' => 'sent', ]); } }