diff --git a/app/Http/Livewire/Chat/PublicChat.php b/app/Http/Livewire/Chat/PublicChat.php index 661e794..49c4b97 100755 --- a/app/Http/Livewire/Chat/PublicChat.php +++ b/app/Http/Livewire/Chat/PublicChat.php @@ -375,6 +375,24 @@ class PublicChat extends Component 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); @@ -446,6 +464,8 @@ class PublicChat extends Component $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), @@ -518,8 +538,10 @@ class PublicChat extends Component return; } + $nombreParaValidar = $this->flujoData['nombre_remitente'] ?? $this->nombreUsuario; + $resultado = app(\App\Services\PagoValidadorService::class)->validar( - $datosPago, $this->userId, $this->nombreUsuario, 'web' + $datosPago, $this->userId, $nombreParaValidar, 'web' ); $motivo = $resultado['motivo'] ?? ''; @@ -1109,6 +1131,51 @@ class PublicChat extends Component 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', ''); @@ -1134,11 +1201,11 @@ class PublicChat extends Component $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."); - // Registrar solicitud pendiente if ($this->userId) { - $solicitud = \App\Models\SolicitudRecarga::crear($this->userId, $monto, 'web'); + $nombre = $this->flujoData['nombre_remitente'] ?? null; + $solicitud = \App\Models\SolicitudRecarga::crear($this->userId, $monto, 'web', $nombre); $this->flujo = 'pago.solicitud'; - $this->flujoData = ['solicitud_id' => $solicitud->id, 'monto' => $monto]; + $this->flujoData = array_merge($this->flujoData, ['solicitud_id' => $solicitud->id, 'monto' => $monto]); } $this->volver('menu.principal'); diff --git a/app/Models/SolicitudRecarga.php b/app/Models/SolicitudRecarga.php index 2a4405a..41374b6 100644 --- a/app/Models/SolicitudRecarga.php +++ b/app/Models/SolicitudRecarga.php @@ -10,22 +10,22 @@ class SolicitudRecarga extends Model public $timestamps = false; - protected $fillable = ['user_id', 'monto', 'canal', 'estado']; + protected $fillable = ['user_id', 'monto', 'canal', 'estado', 'nombre_remitente']; protected $casts = ['created_at' => 'datetime']; - public static function crear(int $userId, int $monto, string $canal): static + public static function crear(int $userId, int $monto, string $canal, ?string $nombreRemitente = null): static { - // Expirar cualquier solicitud pendiente anterior del mismo usuario static::where('user_id', $userId) ->where('estado', 'pendiente') ->update(['estado' => 'expirada']); return static::create([ - 'user_id' => $userId, - 'monto' => $monto, - 'canal' => $canal, - 'estado' => 'pendiente', + 'user_id' => $userId, + 'monto' => $monto, + 'canal' => $canal, + 'estado' => 'pendiente', + 'nombre_remitente' => $nombreRemitente, ]); } @@ -38,6 +38,19 @@ class SolicitudRecarga extends Model ->first(); } + public static function nombresPrevios(int $userId): array + { + return static::where('user_id', $userId) + ->where('estado', 'confirmada') + ->whereNotNull('nombre_remitente') + ->orderByDesc('id') + ->limit(10) + ->pluck('nombre_remitente') + ->unique() + ->values() + ->toArray(); + } + public function confirmar(): void { $this->update(['estado' => 'confirmada']); diff --git a/app/Services/PagoValidadorService.php b/app/Services/PagoValidadorService.php index 8eb49d2..7ba0288 100755 --- a/app/Services/PagoValidadorService.php +++ b/app/Services/PagoValidadorService.php @@ -82,12 +82,11 @@ class PagoValidadorService } } - // ── 3. Hora (±2 min) — la hora del comprobante y del correo deben ser la misma - // El correo puede demorar en llegar al inbox, pero el timestamp interno registra - // el momento exacto de la transacción, igual que el comprobante. + // ── 3. Hora exacta — el timestamp del comprobante y del correo deben coincidir + // El correo llega tarde al inbox pero su contenido registra la hora exacta de la transacción. if ($horaIA && ($datosCorreo['hora'] ?? '')) { - if (! $this->horaProxima($horaIA, $datosCorreo['hora'], 2)) { - Log::info("[PagoValidador] Correo #{$i}: hora fuera de rango (ia={$horaIA} correo={$datosCorreo['hora']})"); + if (! $this->horaProxima($horaIA, $datosCorreo['hora'], 0)) { + Log::info("[PagoValidador] Correo #{$i}: hora no coincide (ia={$horaIA} correo={$datosCorreo['hora']})"); continue; } } @@ -117,10 +116,16 @@ class PagoValidadorService return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'error_sistema']; } - // ── 6. Remitente (informativo, no bloquea) ────────────── - $coincideRemitente = $this->remitenteCoincide($datosCorreo['remitente'] ?? '', $nombreUsuario ?? ''); + // ── 6. Remitente (bloquea si el correo trae el campo y no coincide) ── + $remitenteCorreo = $datosCorreo['remitente'] ?? ''; + if ($remitenteCorreo && $nombreUsuario) { + if (! $this->remitenteCoincide($remitenteCorreo, $nombreUsuario)) { + Log::info("[PagoValidador] Correo #{$i}: remitente no coincide (correo={$remitenteCorreo} usuario={$nombreUsuario})"); + continue; + } + } - Log::info("[PagoValidador] Correo #{$i}: CONFIRMADO (remitente_coincide=" . ($coincideRemitente ? 'SI' : 'NO') . ")"); + Log::info("[PagoValidador] Correo #{$i}: CONFIRMADO (remitente={$remitenteCorreo})"); try { PagoConfirmado::marcar($emailHash, [ @@ -137,9 +142,8 @@ class PagoValidadorService } return [ - 'estado' => 'confirmado', - 'correo' => $datosCorreo, - 'coincide_remitente' => $coincideRemitente, + 'estado' => 'confirmado', + 'correo' => $datosCorreo, ]; } diff --git a/app/Services/TelegramBotService.php b/app/Services/TelegramBotService.php index b9551c5..bb14b23 100755 --- a/app/Services/TelegramBotService.php +++ b/app/Services/TelegramBotService.php @@ -97,7 +97,7 @@ class TelegramBotService $step = $state['step'] ?? 'await_email'; // Guardar último texto para que handleFreeText pueda pasarlo a Gemini - if (! in_array($step, ['await_email', 'await_otp', 'await_nombre', 'await_celular', 'await_amount'])) { + if (! in_array($step, ['await_email', 'await_otp', 'await_nombre', 'await_celular', 'await_amount', 'await_remitente_nombre'])) { $state['data']['last_text'] = $text; $this->setState($chatId, $state); } @@ -107,8 +107,9 @@ class TelegramBotService 'await_otp' => $this->processOtp($chatId, $text), 'await_nombre' => $this->processNombre($chatId, $text), 'await_celular' => $this->processCelular($chatId, $text), - 'await_amount' => $this->processCustomAmount($chatId, $text), - default => $this->handleFreeText($chatId, $state), + 'await_amount' => $this->processCustomAmount($chatId, $text), + 'await_remitente_nombre' => $this->processRemitenteNombre($chatId, $text), + default => $this->handleFreeText($chatId, $state), }; } @@ -144,7 +145,8 @@ class TelegramBotService 'rec_custom' => $this->askCustomAmount($chatId), 'rec_amount' => $this->chooseRechargeMethod($chatId, (int) ($parts[1] ?? 0)), 'rec_mp' => $this->rechargeMP($chatId, (int) ($parts[1] ?? 0)), - 'rec_breb' => $this->rechargeBreb($chatId, (int) ($parts[1] ?? 0)), + 'rec_breb' => $this->askRemitenteNombre($chatId, (int) ($parts[1] ?? 0)), + 'breb_nom' => $this->seleccionarNombreRemitente($chatId, (string) ($parts[1] ?? 'nuevo')), 'creds' => $this->showCredentials($chatId), 'history' => $this->showHistory($chatId), 'profile' => $this->showProfile($chatId), @@ -170,7 +172,7 @@ class TelegramBotService } $usuarioId = $state['user_id'] ?? null; - $nombreUsuario = $usuarioId ? (User::find($usuarioId)?->name ?? '') : ''; + $nombreUsuario = $state['data']['nombre_remitente'] ?? ($usuarioId ? (User::find($usuarioId)?->name ?? '') : ''); // Verificar solicitud de recarga activa ANTES de cualquier llamada de API $solicitud = $usuarioId ? SolicitudRecarga::pendiente($usuarioId) : null; @@ -1210,6 +1212,93 @@ class TelegramBotService ); } + private function askRemitenteNombre(string $chatId, int $monto): void + { + $state = $this->getState($chatId); + $state['data']['monto'] = $monto; + $this->setState($chatId, $state); + + $userId = $state['user_id'] ?? null; + $nombresPrevios = $userId ? SolicitudRecarga::nombresPrevios((int) $userId) : []; + + if (empty($nombresPrevios)) { + $state['step'] = 'await_remitente_nombre'; + $this->setState($chatId, $state); + $this->send($chatId, "👤 Escribe tu *nombre completo* tal como aparece en tu cuenta bancaria o billetera:"); + return; + } + + // Guardar nombres en state por índice (callback_data tiene límite de 64 chars) + $state['data']['nombres_previos'] = $nombresPrevios; + $this->setState($chatId, $state); + + $keyboard = []; + foreach ($nombresPrevios as $i => $nombre) { + $keyboard[] = [['text' => $nombre, 'callback_data' => "breb_nom|{$i}"]]; + } + $keyboard[] = [['text' => '✏️ Usar otro nombre', 'callback_data' => 'breb_nom|nuevo']]; + + $this->sendWithKeyboard($chatId, "👤 ¿Desde qué cuenta vas a transferir?\nElige un nombre anterior o escribe uno nuevo:", $keyboard); + } + + private function seleccionarNombreRemitente(string $chatId, string $indiceONuevo): void + { + $state = $this->getState($chatId); + $monto = (int) ($state['data']['monto'] ?? 0); + + if ($monto <= 0) { + $this->send($chatId, "Ocurrió un error. Por favor inicia el proceso de recarga de nuevo."); + $this->showMainMenu($chatId); + return; + } + + if ($indiceONuevo === 'nuevo') { + $state['step'] = 'await_remitente_nombre'; + $this->setState($chatId, $state); + $this->send($chatId, "👤 Escribe tu *nombre completo* tal como aparece en tu cuenta bancaria o billetera:"); + return; + } + + $nombres = $state['data']['nombres_previos'] ?? []; + $nombre = $nombres[(int) $indiceONuevo] ?? null; + + if (! $nombre) { + $state['step'] = 'await_remitente_nombre'; + $this->setState($chatId, $state); + $this->send($chatId, "No encontré ese nombre. Escríbelo de nuevo:"); + return; + } + + $state['step'] = 'menu'; + $state['data']['nombre_remitente'] = $nombre; + $this->setState($chatId, $state); + $this->rechargeBreb($chatId, $monto); + } + + private function processRemitenteNombre(string $chatId, string $texto): void + { + $nombre = trim($texto); + if (strlen($nombre) < 3) { + $this->send($chatId, "Por favor escribe tu nombre completo (mínimo 3 caracteres)."); + return; + } + + $state = $this->getState($chatId); + $monto = (int) ($state['data']['monto'] ?? 0); + + if ($monto <= 0) { + $this->send($chatId, "Ocurrió un error. Por favor inicia el proceso de recarga de nuevo."); + $this->showMainMenu($chatId); + return; + } + + $state['step'] = 'menu'; + $state['data']['nombre_remitente'] = $nombre; + $this->setState($chatId, $state); + + $this->rechargeBreb($chatId, $monto); + } + private function rechargeBreb(string $chatId, int $monto): void { $banco = ChatConfig::get('recarga_banco_nombre', ''); @@ -1233,10 +1322,10 @@ class TelegramBotService $text .= "\n⚠️ Después de transferir, envía la *foto del comprobante* aquí y el sistema lo validará automáticamente." . "\n_Tienes hasta 15 minutos después de la transferencia para enviar el soporte._"; - // Registrar solicitud de recarga pendiente if ($userId = ($this->getState($chatId)['user_id'] ?? null)) { - $solicitud = SolicitudRecarga::crear($userId, $monto, 'telegram'); - $state = $this->getState($chatId); + $state = $this->getState($chatId); + $nombre = $state['data']['nombre_remitente'] ?? null; + $solicitud = SolicitudRecarga::crear($userId, $monto, 'telegram', $nombre); $state['data']['solicitud_recarga_id'] = $solicitud->id; $this->setState($chatId, $state); } diff --git a/database/migrations/2026_07_19_000002_add_nombre_remitente_to_solicitudes_recarga.php b/database/migrations/2026_07_19_000002_add_nombre_remitente_to_solicitudes_recarga.php new file mode 100644 index 0000000..65d7442 --- /dev/null +++ b/database/migrations/2026_07_19_000002_add_nombre_remitente_to_solicitudes_recarga.php @@ -0,0 +1,22 @@ +string('nombre_remitente', 200)->nullable()->after('canal'); + }); + } + + public function down(): void + { + Schema::table('solicitudes_recarga', function (Blueprint $table) { + $table->dropColumn('nombre_remitente'); + }); + } +};