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
+9 -5
View File
@@ -26,15 +26,19 @@ class ValidarPagoTelegramJob implements ShouldQueue
public function __construct(
private string $chatId,
private array $datosPago,
private int $intento = 1
private int $intento = 1,
private string $canal = 'telegram'
) {}
public function handle(): void
{
$cacheKey = "tgbot_state_{$this->chatId}";
// Telegram conserva su prefijo histórico de caché; los demás canales usan el suyo.
$cacheKey = $this->canal === 'telegram'
? "tgbot_state_{$this->chatId}"
: "{$this->canal}bot_state_{$this->chatId}";
$state = Cache::get($cacheKey, []);
$usuarioId = $state['user_id'] ?? null;
$bot = new TelegramBotService();
$bot = new TelegramBotService($this->canal);
if (! $usuarioId) {
return;
@@ -49,7 +53,7 @@ class ValidarPagoTelegramJob implements ShouldQueue
$nombreUsuario = $state['data']['nombre_remitente'] ?? null;
$resultado = app(PagoValidadorService::class)->validar(
$this->datosPago, $usuarioId, $nombreUsuario, 'telegram'
$this->datosPago, $usuarioId, $nombreUsuario, $this->canal
);
if ($resultado['estado'] === 'confirmado') {
@@ -68,7 +72,7 @@ class ValidarPagoTelegramJob implements ShouldQueue
// No encontrado — reintentar o agotar
if ($this->intento < self::MAX_INTENTOS) {
Log::info("[ValidarPagoTelegramJob] chatId={$this->chatId}: intento {$this->intento}/" . self::MAX_INTENTOS . " — reintentando en 20s");
self::dispatch($this->chatId, $this->datosPago, $this->intento + 1)
self::dispatch($this->chatId, $this->datosPago, $this->intento + 1, $this->canal)
->delay(now()->addSeconds(20));
} else {
Log::info("[ValidarPagoTelegramJob] chatId={$this->chatId}: intentos agotados, mostrando botón manual");