- Hora exacta: tolerancia 0 (timestamp comprobante = timestamp email) - Remitente bloquea: si el email trae el nombre del remitente y no coincide, rechaza - Nuevo paso antes de mostrar datos bancarios: bot pide nombre completo del titular - Nombres aceptados se guardan en solicitudes_recarga.nombre_remitente - Próxima recarga: muestra botones con nombres anteriores confirmados o ingresa uno nuevo - Funciona igual en Telegram y web chat Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1699 lines
71 KiB
PHP
Executable File
1699 lines
71 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Livewire\Chat;
|
|
|
|
use App\Mail\ChatOtpMail;
|
|
use App\Models\Role;
|
|
use App\Models\Cuentas;
|
|
use App\Models\ChatContact;
|
|
use Illuminate\Support\Facades\DB;
|
|
use App\Models\ChatConversation;
|
|
use App\Models\ChatMessage;
|
|
use App\Models\Historiale;
|
|
use App\Models\Historial_cuenta;
|
|
use App\Models\Preferencial;
|
|
use App\Models\Promociones;
|
|
use App\Services\CuentasHelper;
|
|
use App\Models\TarifaPromo;
|
|
use App\Models\Usuario_promo;
|
|
use App\Models\recarga;
|
|
use App\Models\Saldo;
|
|
use App\Models\Servicio;
|
|
use App\Models\Tarifas;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Str;
|
|
use Livewire\Component;
|
|
use Livewire\WithFileUploads;
|
|
|
|
class PublicChat extends Component
|
|
{
|
|
use WithFileUploads;
|
|
|
|
// ── Paso actual ───────────────────────────────────────────
|
|
public string $paso = 'email'; // email | verificacion | registro | chat
|
|
public string $email = '';
|
|
public string $nombreRegistro = '';
|
|
public string $cedulaRegistro = '';
|
|
public string $celularRegistro = '';
|
|
public string $codigoIngresado = '';
|
|
public ?string $emailMascarado = null;
|
|
public ?int $contactIdPending = null; // contact.id mientras espera verificacion
|
|
public string $input = '';
|
|
public array $mensajes = [];
|
|
public ?int $convId = null;
|
|
public string $estadoConv = 'bot';
|
|
|
|
// ── Usuario identificado ──────────────────────────────────
|
|
public ?int $userId = null;
|
|
public ?string $nombreUsuario = null;
|
|
public ?float $saldoUsuario = null;
|
|
|
|
// ── Estado del wizard ─────────────────────────────────────
|
|
public string $flujo = '';
|
|
public array $flujoData = [];
|
|
|
|
// ── Upload comprobante ────────────────────────────────────
|
|
public $fotoComprobante = null;
|
|
public bool $procesandoImagen = false;
|
|
|
|
public function mount(): void
|
|
{
|
|
$userId = session('chat_user_id');
|
|
|
|
if (! $userId) {
|
|
return;
|
|
}
|
|
|
|
$user = User::with('saldo')->find($userId);
|
|
if (! $user) {
|
|
session()->forget(['chat_user_id', 'chat_conv_id']);
|
|
return;
|
|
}
|
|
|
|
$this->userId = $user->id;
|
|
$this->nombreUsuario = $user->name;
|
|
$this->saldoUsuario = $user->saldo?->valor ?? 0;
|
|
$this->paso = 'chat';
|
|
|
|
$convId = session('chat_conv_id');
|
|
if ($convId) {
|
|
$conv = ChatConversation::find($convId);
|
|
if ($conv) {
|
|
$this->convId = $conv->id;
|
|
$this->estadoConv = $conv->estado;
|
|
$this->cargarMensajes();
|
|
} else {
|
|
session()->forget('chat_conv_id');
|
|
}
|
|
}
|
|
}
|
|
|
|
protected $rules = [
|
|
'email' => 'required|email|max:100',
|
|
'nombreRegistro' => 'required|max:80',
|
|
'cedulaRegistro' => 'nullable|max:20',
|
|
'celularRegistro' => 'nullable|max:20',
|
|
'codigoIngresado' => 'nullable|digits:6',
|
|
];
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Paso 1: buscar por correo
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
public function iniciar(): void
|
|
{
|
|
$this->validate(['email' => 'required|email|max:100']);
|
|
|
|
$user = User::where('email', $this->email)->first();
|
|
|
|
if (! $user) {
|
|
$this->paso = 'registro';
|
|
return;
|
|
}
|
|
|
|
$contact = ChatContact::firstOrCreate(
|
|
['canal' => 'web', 'canal_id' => $user->email],
|
|
['nombre' => $user->name, 'telefono' => $user->email, 'user_id' => $user->id]
|
|
);
|
|
|
|
if (! $contact->user_id) {
|
|
$contact->update(['user_id' => $user->id]);
|
|
}
|
|
|
|
$this->contactIdPending = $contact->id;
|
|
$this->emailMascarado = $this->enmascararEmail($user->email);
|
|
$this->enviarCodigoOtp($contact->id, $user);
|
|
$this->paso = 'verificacion';
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Paso 2a: crear cuenta nueva
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
public function crearCuenta(): void
|
|
{
|
|
$this->validate([
|
|
'email' => 'required|email|max:100',
|
|
'nombreRegistro' => 'required|max:80',
|
|
'cedulaRegistro' => 'nullable|max:20',
|
|
'celularRegistro' => 'nullable|max:20',
|
|
]);
|
|
|
|
$rolCliente = Role::where('nombre', 'cliente')->first();
|
|
|
|
$user = User::create([
|
|
'name' => $this->nombreRegistro,
|
|
'email' => $this->email,
|
|
'password' => Hash::make(Str::random(16)),
|
|
'cedula' => $this->cedulaRegistro ?: null,
|
|
'celular' => $this->celularRegistro ?: null,
|
|
'estado' => 'activo',
|
|
'rol_id' => $rolCliente?->id,
|
|
]);
|
|
|
|
$contact = ChatContact::create([
|
|
'canal' => 'web',
|
|
'canal_id' => $user->email,
|
|
'nombre' => $user->name,
|
|
'telefono' => $user->email,
|
|
'user_id' => $user->id,
|
|
]);
|
|
|
|
$this->contactIdPending = $contact->id;
|
|
$this->emailMascarado = $this->enmascararEmail($user->email);
|
|
$this->enviarCodigoOtp($contact->id, $user);
|
|
$this->paso = 'verificacion';
|
|
}
|
|
|
|
public function rechazarRegistro(): void
|
|
{
|
|
$this->email = '';
|
|
$this->nombreRegistro = '';
|
|
$this->cedulaRegistro = '';
|
|
$this->celularRegistro = '';
|
|
$this->paso = 'email';
|
|
}
|
|
|
|
public function confirmarCierreSesion(): void
|
|
{
|
|
if (! $this->convId) {
|
|
return;
|
|
}
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => '¿Seguro que deseas cerrar sesión? Tendrás que volver a ingresar tu correo y código la próxima vez.',
|
|
'payload' => ['botones' => [
|
|
['label' => '✅ Sí, cerrar sesión', 'action' => 'sesion.cerrar.confirmar', 'data' => []],
|
|
['label' => '❌ No, quedarme', 'action' => 'menu.principal', 'data' => []],
|
|
]],
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
|
|
public function limpiarChat(): void
|
|
{
|
|
$this->mensajes = [];
|
|
$this->convId = null;
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
$this->estadoConv = 'bot';
|
|
session()->forget('chat_conv_id');
|
|
// paso, userId, nombreUsuario, saldoUsuario se mantienen — sesión intacta
|
|
}
|
|
|
|
public function cerrarSesion(): void
|
|
{
|
|
session()->forget(['chat_user_id', 'chat_conv_id']);
|
|
|
|
$this->email = '';
|
|
$this->nombreRegistro = '';
|
|
$this->cedulaRegistro = '';
|
|
$this->celularRegistro = '';
|
|
$this->codigoIngresado = '';
|
|
$this->emailMascarado = null;
|
|
$this->contactIdPending = null;
|
|
$this->userId = null;
|
|
$this->nombreUsuario = null;
|
|
$this->saldoUsuario = null;
|
|
$this->convId = null;
|
|
$this->estadoConv = 'bot';
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
$this->mensajes = [];
|
|
$this->input = '';
|
|
$this->paso = 'email';
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Paso 2b: verificar código OTP
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function enviarCodigoOtp(int $contactId, User $user): void
|
|
{
|
|
$codigo = (string) random_int(100000, 999999);
|
|
Cache::put("chat_otp_{$contactId}", $codigo, now()->addMinutes(10));
|
|
|
|
try {
|
|
Mail::to($user->email)->send(new ChatOtpMail($codigo, $user->name));
|
|
} catch (\Throwable $e) {
|
|
\Illuminate\Support\Facades\Log::warning('[ChatOTP] Error enviando correo: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function verificarCodigo(): void
|
|
{
|
|
$this->validate(['codigoIngresado' => 'required|digits:6']);
|
|
|
|
if (! $this->contactIdPending) {
|
|
$this->addError('codigoIngresado', 'Sesion expirada. Recarga la pagina.');
|
|
return;
|
|
}
|
|
|
|
$codigoGuardado = Cache::get("chat_otp_{$this->contactIdPending}");
|
|
|
|
if (! $codigoGuardado) {
|
|
$this->addError('codigoIngresado', 'El codigo expiro. Solicita uno nuevo.');
|
|
return;
|
|
}
|
|
|
|
if ($this->codigoIngresado !== $codigoGuardado) {
|
|
$this->addError('codigoIngresado', 'Codigo incorrecto. Intentalo de nuevo.');
|
|
return;
|
|
}
|
|
|
|
Cache::forget("chat_otp_{$this->contactIdPending}");
|
|
|
|
$contact = ChatContact::find($this->contactIdPending);
|
|
if (! $contact) {
|
|
$this->addError('codigoIngresado', 'Sesion invalida. Recarga la pagina.');
|
|
return;
|
|
}
|
|
|
|
$this->contactIdPending = null;
|
|
$this->codigoIngresado = '';
|
|
$this->abrirChat($contact);
|
|
}
|
|
|
|
public function reenviarCodigo(): void
|
|
{
|
|
if (! $this->contactIdPending) {
|
|
return;
|
|
}
|
|
|
|
$contact = ChatContact::find($this->contactIdPending);
|
|
if (! $contact || ! $contact->user_id) {
|
|
return;
|
|
}
|
|
|
|
$user = User::find($contact->user_id);
|
|
if ($user) {
|
|
$this->enviarCodigoOtp($this->contactIdPending, $user);
|
|
session()->flash('otp_reenviado', 'Codigo reenviado a tu correo.');
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Abrir chat (después de identificación)
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function abrirChat(ChatContact $contact): void
|
|
{
|
|
if ($contact->user_id) {
|
|
$user = User::with('saldo')->find($contact->user_id);
|
|
$this->userId = $user->id;
|
|
$this->nombreUsuario = $user->name;
|
|
$this->saldoUsuario = $user->saldo?->valor ?? 0;
|
|
}
|
|
|
|
$conv = $contact->activeConversation();
|
|
$esNueva = ! $conv;
|
|
|
|
if ($esNueva) {
|
|
$conv = ChatConversation::create([
|
|
'contact_id' => $contact->id,
|
|
'canal' => 'web',
|
|
'estado' => 'bot',
|
|
'ultimo_mensaje_at' => now(),
|
|
]);
|
|
$saludo = $this->nombreUsuario
|
|
? "¡Hola, {$this->nombreUsuario}! Bienvenido."
|
|
: "¡Hola! Bienvenido.";
|
|
$this->guardarMensajeBot($conv->id, $saludo);
|
|
} else {
|
|
// Conversación existente: el usuario acaba de verificarse por OTP
|
|
$saludo = $this->nombreUsuario
|
|
? "Bienvenido de nuevo, {$this->nombreUsuario}!"
|
|
: "Bienvenido de nuevo!";
|
|
$this->guardarMensajeBot($conv->id, $saludo);
|
|
}
|
|
|
|
$this->convId = $conv->id;
|
|
$this->estadoConv = $conv->estado;
|
|
$this->paso = 'chat';
|
|
|
|
session()->put('chat_user_id', $this->userId);
|
|
session()->put('chat_conv_id', $conv->id);
|
|
|
|
// Siempre muestra el menú al autenticarse (mount no llama a este método)
|
|
$this->mostrarMenuPrincipal($conv->id);
|
|
$this->cargarMensajes();
|
|
}
|
|
|
|
private function enmascararEmail(string $email): string
|
|
{
|
|
[$local, $dominio] = explode('@', $email, 2);
|
|
$n = min(4, mb_strlen($local));
|
|
$visible = mb_substr($local, 0, $n);
|
|
return $visible . str_repeat('*', max(0, mb_strlen($local) - $n)) . '@' . $dominio;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Envío de texto libre
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
public function enviar(): void
|
|
{
|
|
if (! $this->convId || trim($this->input) === '') {
|
|
return;
|
|
}
|
|
|
|
$texto = trim($this->input);
|
|
$this->input = '';
|
|
|
|
$this->guardarMensajeUsuario($this->convId, $texto);
|
|
|
|
$conv = ChatConversation::find($this->convId);
|
|
if (! $conv || $conv->estado === 'agente') {
|
|
$this->cargarMensajes();
|
|
return;
|
|
}
|
|
|
|
// Capturar nombre del titular bancario para validación de recarga
|
|
if ($this->flujo === 'pago.esperando_nombre') {
|
|
$nombre = trim($texto);
|
|
if (strlen($nombre) < 3) {
|
|
$this->guardarMensajeBot($this->convId, "Por favor escribe tu nombre completo (mínimo 3 caracteres).");
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
return;
|
|
}
|
|
$monto = (int) ($this->flujoData['monto'] ?? 0);
|
|
$this->flujoData['nombre_remitente'] = $nombre;
|
|
$this->flujo = '';
|
|
$this->mostrarTransferenciaDatos($monto);
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
return;
|
|
}
|
|
|
|
// Capturar monto personalizado de recarga
|
|
if ($this->flujo === 'recarga.monto_custom') {
|
|
$monto = (int) preg_replace('/\D/', '', $texto);
|
|
if ($monto >= 1000) {
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
$this->elegirMetodoRecarga($monto);
|
|
} else {
|
|
$this->guardarMensajeBot($this->convId, "El monto debe ser al menos $1.000. Escríbelo de nuevo:");
|
|
}
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
return;
|
|
}
|
|
|
|
$accion = $this->detectarIntencion($texto);
|
|
|
|
if ($accion) {
|
|
$this->clickBoton($accion['action'], $accion['data'] ?? []);
|
|
} else {
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => "No entendí bien tu mensaje. Puedes escribir cosas como:\n\"ver planes\", \"hacer recarga\", \"mis credenciales\", \"ver promociones\" o usar los botones:",
|
|
'payload' => ['botones' => [
|
|
['label' => 'Ver planes', 'action' => 'servicios.listar', 'data' => []],
|
|
['label' => 'Recargar saldo', 'action' => 'recarga.iniciar', 'data' => []],
|
|
['label' => 'Mis credenciales','action' => 'credenciales.listar','data' => []],
|
|
['label' => 'Menú principal', 'action' => 'menu.principal', 'data' => []],
|
|
]],
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Clic en botón
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
public function clickBoton(string $action, array $data = []): void
|
|
{
|
|
if (! $this->convId) {
|
|
return;
|
|
}
|
|
|
|
match (true) {
|
|
$action === 'menu.principal' => $this->mostrarMenuPrincipal($this->convId),
|
|
$action === 'servicios.listar' => $this->listarServicios(),
|
|
$action === 'servicios.ver' => $this->verServicio((int) ($data['id'] ?? 0)),
|
|
$action === 'compra.iniciar' => $this->iniciarCompra((int) ($data['tarifa_id'] ?? 0)),
|
|
$action === 'compra.pagar.saldo' => $this->pagarConSaldo(),
|
|
$action === 'compra.pagar.mp' => $this->pagarConMP(),
|
|
$action === 'promo.listar' => $this->listarPromociones(),
|
|
$action === 'promo.ver' => $this->verPromocion((int) ($data['id'] ?? 0)),
|
|
$action === 'promo.comprar.saldo' => $this->comprarPromoConSaldo((int) ($data['id'] ?? 0)),
|
|
$action === 'promo.comprar.mp' => $this->comprarPromoConMP((int) ($data['id'] ?? 0)),
|
|
$action === 'recarga.iniciar' => $this->mostrarFormRecarga(),
|
|
$action === 'recarga.otro_valor' => $this->solicitarMontoPersonalizado(),
|
|
$action === 'recarga.elegir_metodo' => $this->elegirMetodoRecarga((int) ($data['monto'] ?? 0)),
|
|
$action === 'recarga.mp' => $this->generarLinkRecargaMP((int) ($data['monto'] ?? 0)),
|
|
$action === 'recarga.transferencia' => $this->mostrarTransferencia((int) ($data['monto'] ?? 0)),
|
|
$action === 'credenciales.listar' => $this->listarCredenciales(),
|
|
$action === 'historial.ver' => $this->verHistorial(),
|
|
$action === 'perfil.ver' => $this->verPerfil(),
|
|
$action === 'asesor.solicitar' => $this->solicitarAsesor(),
|
|
$action === 'pago.aplicar' => $this->aplicarRecargaValidada($data),
|
|
$action === 'pago.reintentar' => $this->reintentarValidacionPago(),
|
|
$action === 'pago.nombre_previo' => $this->usarNombrePrevio($data['nombre'] ?? ''),
|
|
$action === 'pago.nombre_nuevo' => $this->pedirNombreNuevo(),
|
|
$action === 'sesion.cerrar' => $this->confirmarCierreSesion(),
|
|
$action === 'sesion.cerrar.confirmar' => $this->cerrarSesion(),
|
|
default => $this->mostrarMenuPrincipal($this->convId),
|
|
};
|
|
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Upload comprobante de pago
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
public function updatedFotoComprobante(): void
|
|
{
|
|
if (! $this->fotoComprobante || ! $this->convId) {
|
|
return;
|
|
}
|
|
|
|
if (\App\Models\ChatConfig::get('validacion_foto_habilitada', '0') !== '1') {
|
|
$this->guardarMensajeBot($this->convId, "El analisis de comprobantes no esta habilitado. Contacta a un asesor.");
|
|
$this->fotoComprobante = null;
|
|
$this->cargarMensajes();
|
|
return;
|
|
}
|
|
|
|
$this->procesandoImagen = true;
|
|
|
|
try {
|
|
// Verificar solicitud activa ANTES de cualquier llamada de API
|
|
$solicitud = $this->userId ? \App\Models\SolicitudRecarga::pendiente($this->userId) : null;
|
|
if (! $solicitud) {
|
|
$this->procesandoImagen = false;
|
|
$this->fotoComprobante = null;
|
|
$this->guardarMensajeBot($this->convId, "⚠️ No tienes ninguna solicitud de recarga activa. Primero inicia el proceso de recarga desde el menú.");
|
|
$this->cargarMensajes();
|
|
return;
|
|
}
|
|
|
|
$path = $this->fotoComprobante->store('comprobantes', 'public');
|
|
$url = asset('storage/' . $path);
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'usuario',
|
|
'tipo_ui' => 'imagen',
|
|
'contenido' => '[Comprobante de pago]',
|
|
'payload' => ['url' => $url],
|
|
'leido' => false,
|
|
]);
|
|
|
|
$this->guardarMensajeBot($this->convId, "⏳ Analizando tu comprobante...");
|
|
|
|
$base64 = base64_encode(file_get_contents($this->fotoComprobante->getRealPath()));
|
|
$mimeType = $this->fotoComprobante->getMimeType();
|
|
|
|
$datosPago = app(\App\Services\GeminiVisionService::class)->extraerPago($base64, $mimeType);
|
|
|
|
if (! $datosPago) {
|
|
$this->guardarMensajeBot($this->convId, "No pude leer el comprobante. Intenta con una foto mas clara.");
|
|
$this->cargarMensajes();
|
|
return;
|
|
}
|
|
|
|
// Verificar que el monto coincida con la solicitud
|
|
$valorIA = (int) ($datosPago['valor'] ?? 0);
|
|
if ($valorIA !== $solicitud->monto) {
|
|
$this->guardarMensajeBot($this->convId, "El monto del comprobante ($" . number_format($valorIA) . ") no coincide con tu solicitud de recarga ($" . number_format($solicitud->monto) . ").");
|
|
$this->cargarMensajes();
|
|
return;
|
|
}
|
|
|
|
$nombreParaValidar = $this->flujoData['nombre_remitente'] ?? $this->nombreUsuario;
|
|
|
|
$resultado = app(\App\Services\PagoValidadorService::class)->validar(
|
|
$datosPago, $this->userId, $nombreParaValidar, 'web'
|
|
);
|
|
|
|
$motivo = $resultado['motivo'] ?? '';
|
|
|
|
if ($resultado['estado'] === 'confirmado') {
|
|
$this->autoAplicarRecargaWeb($solicitud, $solicitud->monto);
|
|
return;
|
|
|
|
} elseif ($resultado['estado'] === 'ya_usado') {
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
$this->guardarMensajeBot($this->convId, "⛔ Comprobante ya utilizado. Este pago ya fue registrado anteriormente.");
|
|
|
|
} elseif ($motivo === 'sin_coincidencia' || $motivo === 'sin_correos') {
|
|
$this->flujo = 'pago.pendiente';
|
|
$this->flujoData = array_merge($this->flujoData, ['datosPago' => $datosPago]);
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'validacion',
|
|
'contenido' => 'Por ahora no encontramos tu pago de confirmacion. Puede demorar entre 1 y 2 minutos en llegar. Espera un momento y presiona Reintentar.',
|
|
'payload' => [
|
|
'estado' => 'pendiente',
|
|
'motivo' => 'Por ahora no encontramos tu pago de confirmacion. Puede demorar entre 1 y 2 minutos.',
|
|
'ia_datos' => $datosPago,
|
|
'botones' => [
|
|
['label' => '🔄 Reintentar', 'action' => 'pago.reintentar', 'data' => []],
|
|
['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []],
|
|
],
|
|
],
|
|
'leido' => true,
|
|
]);
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
return;
|
|
|
|
} else {
|
|
$motivoTexto = match ($motivo) {
|
|
'correo_deshabilitado' => 'Verificacion por correo no configurada',
|
|
'error_imap' => 'Error al leer correos IMAP',
|
|
'error_sistema' => 'Error interno, intenta de nuevo en unos minutos',
|
|
default => 'No se pudo verificar automaticamente',
|
|
};
|
|
$this->guardarMensajeBot($this->convId, "⚠️ No confirmado: {$motivoTexto}");
|
|
}
|
|
|
|
} catch (\Throwable $e) {
|
|
$this->guardarMensajeBot($this->convId, "Error analizando el comprobante. Por favor intenta de nuevo.");
|
|
} finally {
|
|
$this->procesandoImagen = false;
|
|
$this->fotoComprobante = null;
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Menu principal
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function mostrarMenuPrincipal(int $convId): void
|
|
{
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => 'En que te puedo ayudar?',
|
|
'payload' => ['botones' => [
|
|
['label' => 'Ver Servicios', 'action' => 'servicios.listar', 'data' => []],
|
|
['label' => 'Promociones', 'action' => 'promo.listar', 'data' => []],
|
|
['label' => 'Recargar Saldo', 'action' => 'recarga.iniciar', 'data' => []],
|
|
['label' => 'Mis Credenciales', 'action' => 'credenciales.listar', 'data' => []],
|
|
['label' => 'Historial', 'action' => 'historial.ver', 'data' => []],
|
|
['label' => 'Mi Perfil', 'action' => 'perfil.ver', 'data' => []],
|
|
['label' => 'Hablar con asesor', 'action' => 'asesor.solicitar', 'data' => []],
|
|
['label' => '🚪 Cerrar sesión', 'action' => 'sesion.cerrar', 'data' => []],
|
|
]],
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Servicios
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function listarServicios(): void
|
|
{
|
|
$rolId = $this->userId ? User::find($this->userId)?->rol_id : Role::where('nombre', 'cliente')->value('id');
|
|
$servicios = Servicio::where('estado', 'activo')
|
|
->whereHas('tarifas', fn ($q) => $q->where('estado', 'activo')->where('rol_id', $rolId))
|
|
->orderBy('ubicacion')
|
|
->get();
|
|
|
|
if ($servicios->isEmpty()) {
|
|
$this->guardarMensajeBot($this->convId, "No hay servicios disponibles en este momento.");
|
|
$this->volver('menu.principal');
|
|
return;
|
|
}
|
|
|
|
$cards = $servicios->map(fn ($s) => [
|
|
'imagen' => $s->img_inicio,
|
|
'titulo' => $s->nombre,
|
|
'detalle' => $s->por_tiempo ? 'Por tiempo' : 'Por pantallas',
|
|
'accion' => ['label' => 'Ver planes', 'action' => 'servicios.ver', 'data' => ['id' => $s->id]],
|
|
])->values()->all();
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'cards_slider',
|
|
'contenido' => 'Selecciona el servicio:',
|
|
'payload' => ['cards' => $cards],
|
|
'leido' => true,
|
|
]);
|
|
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
private function verServicio(int $id): void
|
|
{
|
|
$rolId = $this->userId ? User::find($this->userId)?->rol_id : Role::where('nombre', 'cliente')->value('id');
|
|
$servicio = Servicio::with(['tarifas' => fn ($q) => $q->where('estado', 'activo')->where('rol_id', $rolId)])->find($id);
|
|
|
|
if (! $servicio) {
|
|
$this->guardarMensajeBot($this->convId, "Servicio no encontrado.");
|
|
return;
|
|
}
|
|
|
|
$tarifas = $servicio->tarifas;
|
|
|
|
if ($tarifas->isEmpty()) {
|
|
$this->guardarMensajeBot($this->convId, "No hay planes para {$servicio->nombre} en este momento.");
|
|
$this->volver('servicios.listar');
|
|
return;
|
|
}
|
|
|
|
$botones = [];
|
|
foreach ($tarifas as $t) {
|
|
$precio = $this->efectivePrice($t->id, $this->userId, $t->valor);
|
|
$label = $servicio->por_tiempo
|
|
? "{$t->dias} dias - $" . number_format($precio)
|
|
: "{$t->pantallas} pantalla(s) / {$t->dias} dias - $" . number_format($precio);
|
|
$botones[] = ['label' => $label, 'action' => 'compra.iniciar', 'data' => ['tarifa_id' => $t->id]];
|
|
}
|
|
$botones[] = ['label' => 'Volver', 'action' => 'servicios.listar', 'data' => []];
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => "Planes de {$servicio->nombre}:",
|
|
'payload' => ['botones' => $botones],
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Compra de plan
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function iniciarCompra(int $tarifaId): void
|
|
{
|
|
$tarifa = Tarifas::with('servicio')->find($tarifaId);
|
|
if (! $tarifa) {
|
|
$this->guardarMensajeBot($this->convId, "Plan no encontrado.");
|
|
return;
|
|
}
|
|
|
|
if ($this->contarDisponibles($tarifa) < 1) {
|
|
$this->guardarMensajeBot($this->convId, "Sin cuentas disponibles para este plan. Intenta con otro.");
|
|
$this->volver('servicios.listar');
|
|
return;
|
|
}
|
|
|
|
$precio = $this->efectivePrice($tarifaId, $this->userId, $tarifa->valor);
|
|
|
|
$this->flujo = 'compra';
|
|
$this->flujoData = [
|
|
'tarifa_id' => $tarifaId,
|
|
'valor' => $precio,
|
|
'servicio' => $tarifa->servicio->nombre,
|
|
'dias' => $tarifa->dias,
|
|
'pantallas' => $tarifa->pantallas,
|
|
];
|
|
|
|
$detalle = $tarifa->servicio->por_tiempo
|
|
? "{$tarifa->dias} dias"
|
|
: "{$tarifa->pantallas} pantalla(s) / {$tarifa->dias} dias";
|
|
|
|
if ((float) $this->saldoUsuario >= $precio) {
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => "{$tarifa->servicio->nombre} - {$detalle}\nPrecio: $" . number_format($precio) . "\n\nElige como pagar:",
|
|
'payload' => ['botones' => [
|
|
['label' => 'Pagar con saldo ($' . number_format($precio) . ')', 'action' => 'compra.pagar.saldo', 'data' => []],
|
|
['label' => 'Volver', 'action' => 'servicios.listar', 'data' => []],
|
|
]],
|
|
'leido' => true,
|
|
]);
|
|
} else {
|
|
$falta = $precio - (float) $this->saldoUsuario;
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => "{$tarifa->servicio->nombre} - {$detalle}\nPrecio: $" . number_format($precio) . "\n\nSaldo actual: $" . number_format($this->saldoUsuario) . " — te faltan $" . number_format($falta) . "\n\nRecarga tu saldo para continuar:",
|
|
'payload' => ['botones' => [
|
|
['label' => 'Recargar saldo', 'action' => 'recarga.iniciar', 'data' => []],
|
|
['label' => 'Volver', 'action' => 'servicios.listar', 'data' => []],
|
|
]],
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function pagarConSaldo(): void
|
|
{
|
|
if (! $this->userId || $this->flujo !== 'compra') {
|
|
$this->guardarMensajeBot($this->convId, "Debes estar identificado para pagar con saldo.");
|
|
return;
|
|
}
|
|
|
|
$tarifa = Tarifas::with('servicio')->find($this->flujoData['tarifa_id'] ?? 0);
|
|
if (! $tarifa) {
|
|
$this->guardarMensajeBot($this->convId, "Ocurrio un error. Intenta de nuevo.");
|
|
return;
|
|
}
|
|
|
|
$user = User::with('saldo')->find($this->userId);
|
|
$saldo = $user->saldo;
|
|
$precio = (float) ($this->flujoData['valor'] ?? $tarifa->valor);
|
|
|
|
if (! $saldo || $saldo->valor < $precio) {
|
|
$this->guardarMensajeBot(
|
|
$this->convId,
|
|
"Saldo insuficiente. Tienes $" . number_format($saldo?->valor ?? 0) .
|
|
" y el plan cuesta $" . number_format($precio) . "."
|
|
);
|
|
$this->volver('menu.principal');
|
|
return;
|
|
}
|
|
|
|
$cuenta = $this->buscarCuentaDisponible($tarifa);
|
|
if (! $cuenta) {
|
|
$this->guardarMensajeBot($this->convId, "No hay cuentas disponibles en este momento. Intenta más tarde o contacta a un asesor.");
|
|
return;
|
|
}
|
|
|
|
$nuevoSaldo = $saldo->valor - $precio;
|
|
|
|
DB::transaction(function () use ($tarifa, $user, $saldo, $cuenta, $precio, $nuevoSaldo) {
|
|
$historial = Historiale::create([
|
|
'fecha_inicio' => now(),
|
|
'fecha_final' => now()->addDays($tarifa->dias),
|
|
'valor' => $precio,
|
|
'utilidad' => $tarifa->utilidad ?? 0,
|
|
'tipo_pago' => 'saldo',
|
|
'estado' => 'entregado',
|
|
'vendedor_id' => $this->userId,
|
|
'tarifa_id' => $tarifa->id,
|
|
'cliente_id' => $this->userId,
|
|
'nombre_cliente' => $user->name,
|
|
]);
|
|
Historial_cuenta::create(['historial_id' => $historial->id, 'cuenta_id' => $cuenta->id]);
|
|
$saldo->update(['valor' => $nuevoSaldo]);
|
|
});
|
|
|
|
$this->saldoUsuario = $nuevoSaldo;
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'credenciales',
|
|
'contenido' => 'Compra exitosa! Tus credenciales:',
|
|
'payload' => [
|
|
'servicio' => $tarifa->servicio->nombre,
|
|
'email' => $cuenta->correo,
|
|
'password' => $cuenta->password,
|
|
'perfil' => $tarifa->pantallas ?? null,
|
|
'vence' => now()->addDays($tarifa->dias)->format('d/m/Y'),
|
|
],
|
|
'leido' => true,
|
|
]);
|
|
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
private function pagarConMP(): void
|
|
{
|
|
if ($this->flujo !== 'compra') {
|
|
return;
|
|
}
|
|
|
|
$valor = (int) ($this->flujoData['valor'] ?? 0);
|
|
$servicio = $this->flujoData['servicio'] ?? 'Plan streaming';
|
|
$ref = 'CHAT-' . strtoupper(Str::random(12));
|
|
$url = $this->crearPreferenciaMP($servicio, $valor, $ref);
|
|
|
|
if (! $url) {
|
|
$this->guardarMensajeBot($this->convId, "No se pudo generar el link de pago. Intenta de nuevo o contacta a un asesor.");
|
|
return;
|
|
}
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'link',
|
|
'contenido' => "Link de pago por $" . number_format($valor) . " listo. Despues de pagar, envia el comprobante aqui.",
|
|
'payload' => ['url' => $url, 'label' => 'Pagar $' . number_format($valor) . ' con MercadoPago', 'nota' => 'Ref: ' . $ref],
|
|
'leido' => true,
|
|
]);
|
|
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Promociones
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function listarPromociones(): void
|
|
{
|
|
$rolId = $this->userId
|
|
? User::find($this->userId)?->rol_id
|
|
: Role::where('nombre', 'cliente')->value('id');
|
|
|
|
$promos = Promociones::where('visible', 'true')
|
|
->where(fn ($q) => $q->whereNull('fecha_limite')->orWhere('fecha_limite', '>=', now()))
|
|
->whereHas('tarifaPromo', fn ($q) => $q->where('rol_id', $rolId))
|
|
->with(['tarifaPromo' => fn ($q) => $q->where('rol_id', $rolId)])
|
|
->get();
|
|
|
|
if ($promos->isEmpty()) {
|
|
$this->guardarMensajeBot($this->convId, "No hay promociones activas en este momento.");
|
|
$this->volver('menu.principal');
|
|
return;
|
|
}
|
|
|
|
$cards = $promos->map(function ($p) {
|
|
$tp = $p->tarifaPromo->first();
|
|
$upric = $this->userId
|
|
? Usuario_promo::where('usuario_id', $this->userId)->where('promocion_id', $p->id)->first()
|
|
: null;
|
|
$precio = $upric ? $upric->precio : ($tp ? $tp->precio : $p->precio);
|
|
|
|
return [
|
|
'imagen' => $p->img_publicidad,
|
|
'titulo' => $p->nombre ?? 'Promocion especial',
|
|
'descripcion' => $p->descripcion ?? null,
|
|
'precio' => $precio,
|
|
'detalle' => $p->fecha_limite ? 'Hasta ' . Carbon::parse($p->fecha_limite)->format('d/m/Y') : null,
|
|
'accion' => ['label' => 'Comprar', 'action' => 'promo.ver', 'data' => ['id' => $p->id]],
|
|
];
|
|
})->values()->all();
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'cards_slider',
|
|
'contenido' => 'Promociones disponibles:',
|
|
'payload' => ['cards' => $cards],
|
|
'leido' => true,
|
|
]);
|
|
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
private function verPromocion(int $id): void
|
|
{
|
|
$rolId = $this->userId
|
|
? User::find($this->userId)?->rol_id
|
|
: Role::where('nombre', 'cliente')->value('id');
|
|
|
|
$promo = Promociones::with(['tarifaPromo' => fn ($q) => $q->where('rol_id', $rolId)])->find($id);
|
|
if (! $promo) {
|
|
$this->guardarMensajeBot($this->convId, "Promocion no encontrada.");
|
|
return;
|
|
}
|
|
|
|
$upric = $this->userId
|
|
? Usuario_promo::where('usuario_id', $this->userId)->where('promocion_id', $id)->first()
|
|
: null;
|
|
$tp = $promo->tarifaPromo->first();
|
|
$precio = $upric ? $upric->precio : ($tp ? $tp->precio : $promo->precio);
|
|
|
|
if ((float) $this->saldoUsuario >= $precio) {
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => ($promo->nombre ?? 'Promocion') . "\n$" . number_format($precio) . "\n\nElige como pagar:",
|
|
'payload' => ['botones' => [
|
|
['label' => 'Pagar con saldo ($' . number_format($precio) . ')', 'action' => 'promo.comprar.saldo', 'data' => ['id' => $id]],
|
|
['label' => 'Volver', 'action' => 'promo.listar', 'data' => []],
|
|
]],
|
|
'leido' => true,
|
|
]);
|
|
} else {
|
|
$falta = $precio - (float) $this->saldoUsuario;
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => ($promo->nombre ?? 'Promocion') . "\n$" . number_format($precio) . "\n\nSaldo actual: $" . number_format($this->saldoUsuario) . " — te faltan $" . number_format($falta) . "\n\nRecarga tu saldo para continuar:",
|
|
'payload' => ['botones' => [
|
|
['label' => 'Recargar saldo', 'action' => 'recarga.iniciar', 'data' => []],
|
|
['label' => 'Volver', 'action' => 'promo.listar', 'data' => []],
|
|
]],
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function comprarPromoConSaldo(int $promoId): void
|
|
{
|
|
if (! $this->userId) {
|
|
$this->guardarMensajeBot($this->convId, "No se pudo procesar la compra.");
|
|
return;
|
|
}
|
|
|
|
$user = User::with('saldo')->find($this->userId);
|
|
$rolId = $user->rol_id;
|
|
|
|
$promo = Promociones::with(['tarifaPromo' => fn ($q) => $q->where('rol_id', $rolId)])->find($promoId);
|
|
if (! $promo) {
|
|
$this->guardarMensajeBot($this->convId, "No se pudo procesar la compra.");
|
|
return;
|
|
}
|
|
|
|
$upric = Usuario_promo::where('usuario_id', $this->userId)->where('promocion_id', $promoId)->first();
|
|
$tp = $promo->tarifaPromo->first();
|
|
$precio = $upric ? $upric->precio : ($tp ? $tp->precio : $promo->precio);
|
|
$utilidad = $upric ? $upric->utilidad : ($tp ? $tp->utilidad : 0);
|
|
|
|
$saldo = $user->saldo;
|
|
|
|
if (! $saldo || $saldo->valor < $precio) {
|
|
$this->guardarMensajeBot($this->convId, "Saldo insuficiente ($" . number_format($saldo?->valor ?? 0) . "). La promo cuesta $" . number_format($precio) . ".");
|
|
return;
|
|
}
|
|
|
|
$cuenta = Cuentas::where('promocion_id', $promoId)->where('estado', 'activo')->first();
|
|
if (! $cuenta) {
|
|
$this->guardarMensajeBot($this->convId, "No hay cuentas disponibles para esta promocion.");
|
|
return;
|
|
}
|
|
|
|
$historial = Historiale::create([
|
|
'fecha_inicio' => now(),
|
|
'fecha_final' => now()->addDays(30),
|
|
'valor' => $precio,
|
|
'utilidad' => $utilidad,
|
|
'tipo_pago' => 'saldo',
|
|
'estado' => 'entregado',
|
|
'vendedor_id' => $this->userId,
|
|
'promocion_id' => $promoId,
|
|
'cliente_id' => $this->userId,
|
|
'nombre_cliente' => $user->name,
|
|
]);
|
|
|
|
$nuevoSaldo = $saldo->valor - $precio;
|
|
|
|
DB::transaction(function () use ($historial, $cuenta, $saldo, $nuevoSaldo) {
|
|
Historial_cuenta::create(['historial_id' => $historial->id, 'cuenta_id' => $cuenta->id]);
|
|
$cuenta->update(['estado' => 'ocupado']);
|
|
$saldo->update(['valor' => $nuevoSaldo]);
|
|
});
|
|
|
|
$this->saldoUsuario = $nuevoSaldo;
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'credenciales',
|
|
'contenido' => 'Promo activada! Tus credenciales:',
|
|
'payload' => [
|
|
'servicio' => $promo->nombre ?? 'Promocion',
|
|
'email' => $cuenta->correo,
|
|
'password' => $cuenta->password,
|
|
'vence' => now()->addDays(30)->format('d/m/Y'),
|
|
],
|
|
'leido' => true,
|
|
]);
|
|
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
private function comprarPromoConMP(int $promoId): void
|
|
{
|
|
$rolId = $this->userId
|
|
? User::find($this->userId)?->rol_id
|
|
: Role::where('nombre', 'cliente')->value('id');
|
|
|
|
$promo = Promociones::with(['tarifaPromo' => fn ($q) => $q->where('rol_id', $rolId)])->find($promoId);
|
|
if (! $promo) {
|
|
return;
|
|
}
|
|
|
|
$upric = $this->userId
|
|
? Usuario_promo::where('usuario_id', $this->userId)->where('promocion_id', $promoId)->first()
|
|
: null;
|
|
$tp = $promo->tarifaPromo->first();
|
|
$precio = $upric ? $upric->precio : ($tp ? $tp->precio : $promo->precio);
|
|
|
|
$ref = 'PROMO-' . strtoupper(Str::random(10));
|
|
$url = $this->crearPreferenciaMP($promo->nombre ?? 'Promocion streaming', (int) $precio, $ref);
|
|
|
|
if (! $url) {
|
|
$this->guardarMensajeBot($this->convId, "No se pudo generar el link de pago. Intenta de nuevo o contacta a un asesor.");
|
|
return;
|
|
}
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'link',
|
|
'contenido' => "Link de pago por $" . number_format($precio) . ". Envia el comprobante aqui al pagar.",
|
|
'payload' => ['url' => $url, 'label' => 'Pagar con MercadoPago', 'nota' => 'Ref: ' . $ref],
|
|
'leido' => true,
|
|
]);
|
|
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Recarga de saldo
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function mostrarFormRecarga(): void
|
|
{
|
|
$botones = [];
|
|
foreach ([20000, 50000, 100000, 200000, 500000] as $m) {
|
|
$botones[] = ['label' => '$' . number_format($m), 'action' => 'recarga.elegir_metodo', 'data' => ['monto' => $m]];
|
|
}
|
|
$botones[] = ['label' => 'Otro valor', 'action' => 'recarga.otro_valor', 'data' => []];
|
|
$botones[] = ['label' => 'Volver', 'action' => 'menu.principal', 'data' => []];
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => "Selecciona el monto a recargar:",
|
|
'payload' => ['botones' => $botones],
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
|
|
private function solicitarMontoPersonalizado(): void
|
|
{
|
|
$this->flujo = 'recarga.monto_custom';
|
|
$this->flujoData = [];
|
|
$this->guardarMensajeBot($this->convId, "Escribe el monto que deseas recargar (mínimo $1.000):");
|
|
}
|
|
|
|
private function elegirMetodoRecarga(int $monto): void
|
|
{
|
|
if ($monto <= 0) {
|
|
return;
|
|
}
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => "Recarga de $" . number_format($monto) . "\n¿Como quieres pagar?",
|
|
'payload' => ['botones' => [
|
|
['label' => 'MercadoPago', 'action' => 'recarga.mp', 'data' => ['monto' => $monto]],
|
|
['label' => 'Bre-B', 'action' => 'recarga.transferencia', 'data' => ['monto' => $monto]],
|
|
['label' => 'Volver', 'action' => 'recarga.iniciar', 'data' => []],
|
|
]],
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
|
|
private function mostrarTransferencia(int $monto): void
|
|
{
|
|
if ($monto <= 0) {
|
|
return;
|
|
}
|
|
|
|
$this->flujoData = ['monto' => $monto];
|
|
|
|
$nombresPrevios = $this->userId ? \App\Models\SolicitudRecarga::nombresPrevios($this->userId) : [];
|
|
|
|
if (! empty($nombresPrevios)) {
|
|
$this->flujo = 'pago.esperando_nombre';
|
|
|
|
$botones = array_map(fn($n) => ['label' => $n, 'action' => 'pago.nombre_previo', 'data' => ['nombre' => $n]], $nombresPrevios);
|
|
$botones[] = ['label' => '✏️ Usar otro nombre', 'action' => 'pago.nombre_nuevo', 'data' => []];
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => '👤 ¿Desde qué cuenta vas a transferir? Elige un nombre anterior o usa uno nuevo:',
|
|
'payload' => ['botones' => $botones],
|
|
'leido' => true,
|
|
]);
|
|
} else {
|
|
$this->flujo = 'pago.esperando_nombre';
|
|
$this->guardarMensajeBot($this->convId, "👤 Escribe tu nombre completo tal como aparece en tu cuenta bancaria o billetera:");
|
|
}
|
|
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
}
|
|
|
|
private function usarNombrePrevio(string $nombre): void
|
|
{
|
|
if (strlen(trim($nombre)) < 3) {
|
|
return;
|
|
}
|
|
$this->flujoData['nombre_remitente'] = trim($nombre);
|
|
$this->flujo = '';
|
|
$this->mostrarTransferenciaDatos((int) ($this->flujoData['monto'] ?? 0));
|
|
}
|
|
|
|
private function pedirNombreNuevo(): void
|
|
{
|
|
$this->flujo = 'pago.esperando_nombre';
|
|
$this->guardarMensajeBot($this->convId, "👤 Escribe tu nombre completo tal como aparece en tu cuenta bancaria:");
|
|
}
|
|
|
|
private function mostrarTransferenciaDatos(int $monto): void
|
|
{
|
|
$banco = \App\Models\ChatConfig::get('recarga_banco_nombre', '');
|
|
$clave = \App\Models\ChatConfig::get('recarga_banco_llave', '');
|
|
$titular = \App\Models\ChatConfig::get('recarga_banco_titular', '');
|
|
|
|
if (! $clave) {
|
|
$this->guardarMensajeBot($this->convId, "El pago por Bre-B no esta disponible en este momento. Usa MercadoPago o contacta a un asesor.");
|
|
return;
|
|
}
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'banco',
|
|
'contenido' => 'Datos para transferencia:',
|
|
'payload' => [
|
|
'banco' => $banco,
|
|
'clave' => $clave,
|
|
'titular' => $titular,
|
|
'monto' => $monto,
|
|
],
|
|
'leido' => true,
|
|
]);
|
|
|
|
$this->guardarMensajeBot($this->convId, "Cuando hayas transferido, envia el comprobante usando el icono de camara (📷) y el sistema lo validara automaticamente.\n\nTienes hasta 15 minutos despues de la transferencia para enviar el soporte.");
|
|
|
|
if ($this->userId) {
|
|
$nombre = $this->flujoData['nombre_remitente'] ?? null;
|
|
$solicitud = \App\Models\SolicitudRecarga::crear($this->userId, $monto, 'web', $nombre);
|
|
$this->flujo = 'pago.solicitud';
|
|
$this->flujoData = array_merge($this->flujoData, ['solicitud_id' => $solicitud->id, 'monto' => $monto]);
|
|
}
|
|
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
private function generarLinkRecargaMP(int $monto): void
|
|
{
|
|
if ($monto <= 0) {
|
|
$this->guardarMensajeBot($this->convId, "Monto invalido.");
|
|
return;
|
|
}
|
|
|
|
$ref = 'REC-' . strtoupper(Str::random(12));
|
|
$url = $this->crearPreferenciaMP('Recarga de saldo', $monto, $ref);
|
|
|
|
if (! $url) {
|
|
$this->guardarMensajeBot($this->convId, "No se pudo generar el link de recarga. Intenta de nuevo o contacta a un asesor.");
|
|
return;
|
|
}
|
|
|
|
if ($this->userId) {
|
|
$user = User::with('saldo')->find($this->userId);
|
|
$saldo = $user->saldo ?? Saldo::create(['usuario_id' => $this->userId, 'valor' => 0]);
|
|
recarga::create([
|
|
'monto' => $monto,
|
|
'reference' => $ref,
|
|
'status' => 'pendiente_mp',
|
|
'usuario_id' => $this->userId,
|
|
'saldo_id' => $saldo->id,
|
|
]);
|
|
}
|
|
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'link',
|
|
'contenido' => "Tu link de recarga por $" . number_format($monto) . " esta listo. Despues de pagar, envia el comprobante aqui.",
|
|
'payload' => ['url' => $url, 'label' => 'Recargar $' . number_format($monto) . ' con MercadoPago', 'nota' => 'Ref: ' . $ref],
|
|
'leido' => true,
|
|
]);
|
|
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
private function crearPreferenciaMP(string $titulo, int $valor, string $ref): ?string
|
|
{
|
|
$accessToken = config('services.mercadopago.token');
|
|
|
|
if (! $accessToken) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
$response = \Illuminate\Support\Facades\Http::withToken($accessToken)
|
|
->timeout(10)
|
|
->post('https://api.mercadopago.com/checkout/preferences', [
|
|
'items' => [[
|
|
'title' => $titulo,
|
|
'quantity' => 1,
|
|
'unit_price' => $valor,
|
|
'currency_id' => 'COP',
|
|
]],
|
|
'external_reference' => $ref,
|
|
'back_urls' => [
|
|
'success' => url('/chat'),
|
|
'failure' => url('/chat'),
|
|
'pending' => url('/chat'),
|
|
],
|
|
'auto_return' => 'approved',
|
|
'payment_methods' => [
|
|
'excluded_payment_types' => [['id' => 'ticket']],
|
|
],
|
|
]);
|
|
|
|
if ($response->successful()) {
|
|
return $response->json('init_point');
|
|
}
|
|
|
|
\Illuminate\Support\Facades\Log::warning('[ChatMP] Error creando preferencia: ' . $response->body());
|
|
return null;
|
|
|
|
} catch (\Throwable $e) {
|
|
\Illuminate\Support\Facades\Log::warning('[ChatMP] Excepcion: ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Credenciales
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function listarCredenciales(): void
|
|
{
|
|
if (! $this->userId) {
|
|
$this->guardarMensajeBot($this->convId, "No encontre tu cuenta. Contacta a un asesor para ver tus credenciales.");
|
|
$this->volver('menu.principal');
|
|
return;
|
|
}
|
|
|
|
$historiales = Historiale::where('cliente_id', $this->userId)
|
|
->where('estado', 'entregado')
|
|
->where('fecha_final', '>=', now())
|
|
->with('tarifa.servicio')
|
|
->orderBy('fecha_final', 'desc')
|
|
->limit(10)
|
|
->get();
|
|
|
|
if ($historiales->isEmpty()) {
|
|
$this->guardarMensajeBot($this->convId, "No tienes planes activos en este momento.");
|
|
$this->volver('menu.principal');
|
|
return;
|
|
}
|
|
|
|
$this->guardarMensajeBot($this->convId, "Tus planes activos:");
|
|
|
|
foreach ($historiales as $h) {
|
|
foreach ($h->cuentas as $c) {
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'credenciales',
|
|
'contenido' => '',
|
|
'payload' => [
|
|
'servicio' => $h->tarifa?->servicio?->nombre ?? 'Servicio',
|
|
'email' => $c->correo,
|
|
'password' => $c->password,
|
|
'perfil' => $h->tarifa?->pantallas ?? null,
|
|
'vence' => Carbon::parse($h->fecha_final)->format('d/m/Y'),
|
|
],
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
}
|
|
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Historial
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function verHistorial(): void
|
|
{
|
|
if (! $this->userId) {
|
|
$this->guardarMensajeBot($this->convId, "Inicia sesion para ver tu historial.");
|
|
$this->volver('menu.principal');
|
|
return;
|
|
}
|
|
|
|
$historiales = Historiale::where('cliente_id', $this->userId)
|
|
->with('tarifa.servicio')
|
|
->orderBy('created_at', 'desc')
|
|
->limit(10)
|
|
->get();
|
|
|
|
if ($historiales->isEmpty()) {
|
|
$this->guardarMensajeBot($this->convId, "No tienes compras registradas.");
|
|
$this->volver('menu.principal');
|
|
return;
|
|
}
|
|
|
|
$lineas = ["Tus ultimas compras:\n"];
|
|
foreach ($historiales as $h) {
|
|
$activo = Carbon::parse($h->fecha_final)->gte(now()) ? '[activo]' : '[vencido]';
|
|
$nombre = $h->tarifa?->servicio?->nombre ?? ($h->promocion_id ? 'Promo' : 'Plan');
|
|
$fecha = Carbon::parse($h->created_at)->format('d/m/Y');
|
|
$lineas[] = "{$activo} {$nombre} - $" . number_format($h->valor) . " ({$fecha})";
|
|
}
|
|
|
|
$this->guardarMensajeBot($this->convId, implode("\n", $lineas));
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Perfil
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function verPerfil(): void
|
|
{
|
|
if (! $this->userId) {
|
|
$this->guardarMensajeBot($this->convId, "No encontre tu cuenta. Contacta a un asesor.");
|
|
$this->volver('menu.principal');
|
|
return;
|
|
}
|
|
|
|
$user = User::with('saldo')->find($this->userId);
|
|
|
|
$this->guardarMensajeBot(
|
|
$this->convId,
|
|
"Tu perfil\nNombre: {$user->name}\nEmail: {$user->email}\nSaldo: $" . number_format($user->saldo?->valor ?? 0)
|
|
);
|
|
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Asesor
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function solicitarAsesor(): void
|
|
{
|
|
$conv = ChatConversation::find($this->convId);
|
|
if ($conv) {
|
|
$conv->update(['estado' => 'agente']);
|
|
$this->estadoConv = 'agente';
|
|
}
|
|
$this->guardarMensajeBot($this->convId, "Un asesor se comunicara contigo en breve. Por favor espera.");
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Aplicar recarga validada por IA
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function reintentarValidacionPago(): void
|
|
{
|
|
if ($this->flujo !== 'pago.pendiente' || empty($this->flujoData['datosPago'])) {
|
|
$this->guardarMensajeBot($this->convId, 'No hay ningun comprobante pendiente. Envia la foto de tu comprobante.');
|
|
return;
|
|
}
|
|
|
|
$solicitud = $this->userId ? \App\Models\SolicitudRecarga::pendiente($this->userId) : null;
|
|
if (! $solicitud) {
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
$this->guardarMensajeBot($this->convId, '⚠️ Tu solicitud de recarga expiro. Inicia una nueva recarga desde el menu.');
|
|
$this->cargarMensajes();
|
|
return;
|
|
}
|
|
|
|
$datosPago = $this->flujoData['datosPago'];
|
|
$resultado = app(\App\Services\PagoValidadorService::class)->validar($datosPago, $this->userId, $this->nombreUsuario, 'web');
|
|
$motivo = $resultado['motivo'] ?? '';
|
|
|
|
if ($resultado['estado'] === 'confirmado') {
|
|
$this->autoAplicarRecargaWeb($solicitud, $solicitud->monto);
|
|
return;
|
|
|
|
} elseif ($resultado['estado'] === 'ya_usado') {
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
$this->guardarMensajeBot($this->convId, '⛔ Comprobante ya utilizado. Este pago ya fue registrado anteriormente.');
|
|
|
|
} elseif ($motivo === 'sin_coincidencia' || $motivo === 'sin_correos') {
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'validacion',
|
|
'contenido' => 'Aun no encontramos tu pago.',
|
|
'payload' => [
|
|
'estado' => 'pendiente',
|
|
'motivo' => 'Aun no encontramos tu pago. Puede demorar entre 1 y 2 minutos en llegar.',
|
|
'ia_datos' => $datosPago,
|
|
'botones' => [
|
|
['label' => '🔄 Reintentar', 'action' => 'pago.reintentar', 'data' => []],
|
|
['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []],
|
|
],
|
|
],
|
|
'leido' => true,
|
|
]);
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
return;
|
|
|
|
} else {
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
$this->guardarMensajeBot($this->convId, '⚠️ No se pudo verificar el pago. Intenta de nuevo.');
|
|
}
|
|
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
}
|
|
|
|
private function autoAplicarRecargaWeb(\App\Models\SolicitudRecarga $solicitud, int $monto): void
|
|
{
|
|
$user = User::with('saldo')->find($this->userId);
|
|
$saldo = $user->saldo ?? Saldo::create(['usuario_id' => $this->userId, 'valor' => 0]);
|
|
$nuevoSaldo = $saldo->valor + $monto;
|
|
$saldo->update(['valor' => $nuevoSaldo]);
|
|
$this->saldoUsuario = $nuevoSaldo;
|
|
|
|
\App\Models\recarga::create([
|
|
'usuario_id' => $this->userId,
|
|
'saldo_id' => $saldo->id,
|
|
'monto' => $monto,
|
|
'valor_recarga' => $monto,
|
|
'status' => 'Confirmado',
|
|
'reference' => 'breb-chat-' . $solicitud->id,
|
|
]);
|
|
|
|
$solicitud->confirmar();
|
|
|
|
$this->flujo = '';
|
|
$this->flujoData = [];
|
|
|
|
$this->guardarMensajeBot(
|
|
$this->convId,
|
|
"✅ ¡Tu recarga de $" . number_format($monto) . " fue aplicada exitosamente!\nTu nuevo saldo es $" . number_format($nuevoSaldo) . "."
|
|
);
|
|
$this->cargarMensajes();
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function aplicarRecargaValidada(array $data): void
|
|
{
|
|
if (! $this->userId) {
|
|
$this->guardarMensajeBot($this->convId, "No puedo aplicar la recarga: no estas identificado.");
|
|
return;
|
|
}
|
|
|
|
$monto = (int) ($data['monto'] ?? 0);
|
|
if ($monto <= 0) {
|
|
$this->guardarMensajeBot($this->convId, "Monto invalido.");
|
|
return;
|
|
}
|
|
|
|
$user = User::with('saldo')->find($this->userId);
|
|
$saldo = $user->saldo ?? Saldo::create(['usuario_id' => $this->userId, 'valor' => 0]);
|
|
$nuevoSaldo = $saldo->valor + $monto;
|
|
$saldo->update(['valor' => $nuevoSaldo]);
|
|
$this->saldoUsuario = $nuevoSaldo;
|
|
|
|
$this->guardarMensajeBot(
|
|
$this->convId,
|
|
"Recarga de $" . number_format($monto) . " aplicada. Tu nuevo saldo: $" . number_format($nuevoSaldo) . "."
|
|
);
|
|
|
|
$this->volver('menu.principal');
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// ─────────────────────────────────────────────────────────
|
|
// Detección de intención: keywords primero, Gemini como fallback
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function detectarIntencion(string $texto): ?array
|
|
{
|
|
return $this->detectarIntencionKeyword($texto)
|
|
?? $this->detectarIntencionIA($texto);
|
|
}
|
|
|
|
private function detectarIntencionKeyword(string $texto): ?array
|
|
{
|
|
$t = mb_strtolower(trim($texto));
|
|
|
|
$patrones = [
|
|
// Recarga de saldo
|
|
'recarga.iniciar' => '/recarg|recargar|cargar\s*(saldo|cuenta|dinero|plata)|abonar|depositar|agregar\s*(saldo|dinero|plata)|quiero\s*poner\s*saldo/',
|
|
// Ver servicios / planes
|
|
'servicios.listar' => '/servicio|plan(es)?|cat[aá]logo|qu[eé]\s*(hay|tienen|tienes)|ver\s*plan|netflix|disney|hbo|prime|spotify|crunchyroll|paramount|streaming|aplicacion|aplicaci[oó]n/',
|
|
// Promociones
|
|
'promo.listar' => '/promo(ci[oó]n)?|oferta|descuento|especial/',
|
|
// Credenciales / acceso
|
|
'credenciales.listar' => '/credencial|contrase[ñn]a|clave|password|acceso|usuario|mi\s*cuenta|mis\s*datos\s*de\s*acceso|c[oó]mo\s*(entro|accedo|me\s*conecto)/',
|
|
// Historial de compras
|
|
'historial.ver' => '/historial|compra(s)?|pedido(s)?|lo\s*que\s*(compr[eé]|he\s*comprado)|mis\s*compras/',
|
|
// Perfil
|
|
'perfil.ver' => '/perfil|mis\s*datos|mi\s*informaci[oó]n|mis\s*datos\s*personales/',
|
|
// Asesor humano
|
|
'asesor.solicitar' => '/asesor|agente|persona|humano|soporte|ayuda\s*(de\s*una\s*persona)?|hablar\s*con\s*(alguien|una\s*persona|un\s*asesor)/',
|
|
// Menú / saludo
|
|
'menu.principal' => '/^(hola|buenas?|buenos?|hey|men[uú]|inicio|principal|volver|regresar|empezar|comenzar|start)[\s!.]*$/',
|
|
];
|
|
|
|
foreach ($patrones as $action => $pattern) {
|
|
if (preg_match($pattern, $t)) {
|
|
return ['action' => $action, 'data' => []];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function detectarIntencionIA(string $texto): ?array
|
|
{
|
|
try {
|
|
return app(\App\Services\GeminiIntentService::class)->detectar($texto);
|
|
} catch (\Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Cargar mensajes
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
public function cargarMensajes(): void
|
|
{
|
|
if (! $this->convId) {
|
|
return;
|
|
}
|
|
|
|
$this->mensajes = ChatMessage::where('conversation_id', $this->convId)
|
|
->orderBy('id')
|
|
->get()
|
|
->map(fn ($m) => [
|
|
'tipo' => $m->tipo,
|
|
'tipo_ui' => $m->tipo_ui ?? 'text',
|
|
'contenido' => $m->contenido,
|
|
'payload' => $m->payload ?? [],
|
|
'created_at' => $m->created_at->format('H:i'),
|
|
])
|
|
->toArray();
|
|
|
|
$this->dispatchBrowserEvent('scroll-chat');
|
|
}
|
|
|
|
// Llamado por wire:poll — igual que cargarMensajes pero sin forzar scroll
|
|
public function pollMensajes(): void
|
|
{
|
|
if (! $this->convId) {
|
|
return;
|
|
}
|
|
|
|
$this->mensajes = ChatMessage::where('conversation_id', $this->convId)
|
|
->orderBy('id')
|
|
->get()
|
|
->map(fn ($m) => [
|
|
'tipo' => $m->tipo,
|
|
'tipo_ui' => $m->tipo_ui ?? 'text',
|
|
'contenido' => $m->contenido,
|
|
'payload' => $m->payload ?? [],
|
|
'created_at' => $m->created_at->format('H:i'),
|
|
])
|
|
->toArray();
|
|
// No dispatches scroll-chat — el MutationObserver del JS maneja el badge
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// Helpers
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
private function guardarMensajeBot(int $convId, string $contenido): void
|
|
{
|
|
ChatMessage::create([
|
|
'conversation_id' => $convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'text',
|
|
'contenido' => $contenido,
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
|
|
private function guardarMensajeUsuario(int $convId, string $contenido): void
|
|
{
|
|
ChatMessage::create([
|
|
'conversation_id' => $convId,
|
|
'tipo' => 'usuario',
|
|
'tipo_ui' => 'text',
|
|
'contenido' => $contenido,
|
|
'leido' => false,
|
|
]);
|
|
}
|
|
|
|
private function contarDisponibles(Tarifas $tarifa): int
|
|
{
|
|
return CuentasHelper::contar($tarifa);
|
|
}
|
|
|
|
private function buscarCuentaDisponible(Tarifas $tarifa): ?Cuentas
|
|
{
|
|
return CuentasHelper::buscar($tarifa);
|
|
}
|
|
|
|
private function efectivePrice(int $tarifaId, ?int $userId, float $base): float
|
|
{
|
|
return CuentasHelper::precio($tarifaId, $userId, $base);
|
|
}
|
|
|
|
private function volver(string $action): void
|
|
{
|
|
ChatMessage::create([
|
|
'conversation_id' => $this->convId,
|
|
'tipo' => 'bot',
|
|
'tipo_ui' => 'buttons',
|
|
'contenido' => '',
|
|
'payload' => ['botones' => [
|
|
['label' => 'Menu principal', 'action' => $action, 'data' => []],
|
|
]],
|
|
'leido' => true,
|
|
]);
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.chat.public-chat');
|
|
}
|
|
}
|