feat(whatsapp): mismo motor, menú y configuración que Telegram y el chat web

WhatsApp dejaba de usar su bot propio (árbol de menús sin login ni compras) y
pasa al motor compartido, así los tres canales quedan iguales.

- TelegramBotService recibe el canal por constructor; flujos, menús, Gemini,
  OCR, validación de pagos y datos bancarios salen de ChatConfig sin duplicar
- WhatsApp no tiene teclados inline: las opciones se envían numeradas y la
  respuesta numérica se traduce al callback equivalente
- estado en caché con prefijo por canal (Telegram conserva el suyo para no
  cerrar las sesiones abiertas)
- descarga de comprobantes y audios por la Graph API de Meta
- respuesta manual del asesor y aviso de vencimiento salen también por WhatsApp
- la pantalla de WhatsApp queda sólo con credenciales; el resto se administra
  en Chat / Bot

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-26 23:24:21 -05:00
co-authored by Claude Opus 5
parent e3ad0a423a
commit 5bc1ec14ca
10 changed files with 294 additions and 105 deletions
@@ -6,7 +6,7 @@ use App\Models\WhatsappConversation;
use App\Models\WhatsappSystemConfig;
use App\Models\WhatsappUser;
use App\Models\WhatsappWebhookLog;
use App\Services\WhatsappBotService;
use App\Services\TelegramBotService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
@@ -195,13 +195,39 @@ class WhatsappWebhookController extends Controller
'is_read' => false,
]);
$botService = new WhatsappBotService();
// Interruptor del módulo: con el bot apagado sólo se registra el mensaje.
if (WhatsappSystemConfig::get('bot_enabled', '1') !== '1') {
return;
}
// Mismo motor que Telegram y el chat web: idénticos menús, flujos y configuración.
$bot = new TelegramBotService('whatsapp');
// Confirmar lectura a WhatsApp
$botService->markRead($messageId);
$bot->waMarkRead($messageId);
// Procesar con el motor del bot
$botService->handle($user, $texto ?? '', $type);
switch ($type) {
case 'image':
case 'document':
if ($mediaId) {
$bot->handlePhoto($from, $mediaId);
return;
}
$bot->handleText($from, $texto ?? '', $nombre);
return;
case 'audio':
case 'voice':
if ($mediaId) {
$bot->handleVoice($from, $mediaId, $nombre);
return;
}
$bot->handleText($from, $texto ?? '', $nombre);
return;
default:
$bot->handleText($from, $texto ?? '', $nombre);
}
}
// ──────────────────────────────────────────────────────────
@@ -83,10 +83,13 @@ class ShowConversaciones extends Component
$conv->update(['ultimo_mensaje_at' => now()]);
// Enviar via Telegram si el canal es telegram
// Entregar por el canal del cliente
if ($conv->canal === 'telegram') {
$engine = new ChatBotEngine();
$engine->enviarTelegram($conv->contact->canal_id, $this->replyText);
} elseif ($conv->canal === 'whatsapp') {
(new \App\Services\TelegramBotService('whatsapp'))
->send($conv->contact->canal_id, $this->replyText);
}
$this->replyText = '';
@@ -16,12 +16,6 @@ class ShowConfiguracionBot extends Component
public string $webhook_verify_token = '';
public string $app_secret = '';
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',
@@ -29,18 +23,13 @@ class ShowConfiguracionBot extends Component
'whatsapp_api_url' => 'required|url',
'webhook_verify_token' => 'required|string',
'app_secret' => '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', 'app_secret', 'bot_enabled', 'welcome_message',
'default_no_match', 'advisor_message', 'business_hours_enabled',
'business_hours_start', 'business_hours_end',
'webhook_verify_token', 'app_secret', 'bot_enabled',
];
foreach ($keys as $key) {
@@ -54,9 +43,7 @@ class ShowConfiguracionBot extends Component
$keys = [
'whatsapp_token', 'phone_number_id', 'whatsapp_api_url',
'webhook_verify_token', 'app_secret', 'bot_enabled', 'welcome_message',
'default_no_match', 'advisor_message', 'business_hours_enabled',
'business_hours_start', 'business_hours_end',
'webhook_verify_token', 'app_secret', 'bot_enabled',
];
foreach ($keys as $key) {