From 1f25c316030d337c072b540ae3f1e4b01fe36dd4 Mon Sep 17 00:00:00 2001 From: Lizandro Date: Sat, 18 Jul 2026 16:02:54 +0000 Subject: [PATCH] Fix "sin cuentas disponibles" and wrong pricing in bot/chat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs fixed: 1. TelegramBotService queried Cuentas by tarifa_id + estado=activo which never matched. Now uses CuentasHelper::contar/buscar — same date-range + historial-count logic as the web (pendiente for full accounts, activo + slot check for per-screen accounts). 2. efectivePrice in TelegramBotService and PublicChat used the Preferencial table (legacy) instead of Usuario_tarifa (used by the web admin panel). CuentasHelper::precio checks Usuario_tarifa first, Preferencial as fallback. 3. payWithSaldo compared saldo against tarifa->valor (base price) instead of the effective price stored in state. Also registered the purchase with the wrong amount. Extract shared logic to CuentasHelper (contar, buscar, precio) to avoid duplication between TelegramBotService and PublicChat. Co-Authored-By: Claude Sonnet 4.6 --- app/Http/Livewire/Chat/PublicChat.php | 97 +----------------- app/Services/CuentasHelper.php | 136 ++++++++++++++++++++++++++ app/Services/TelegramBotService.php | 33 +++---- 3 files changed, 153 insertions(+), 113 deletions(-) create mode 100644 app/Services/CuentasHelper.php diff --git a/app/Http/Livewire/Chat/PublicChat.php b/app/Http/Livewire/Chat/PublicChat.php index 0b2f49e..91e47c3 100755 --- a/app/Http/Livewire/Chat/PublicChat.php +++ b/app/Http/Livewire/Chat/PublicChat.php @@ -13,6 +13,7 @@ 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; @@ -1411,107 +1412,17 @@ class PublicChat extends Component private function contarDisponibles(Tarifas $tarifa): int { - $servicio = $tarifa->servicio; - $fechaBase = now()->subDays(8)->format('Y-m-d'); - $fechaMin = now()->addDays($tarifa->dias - 15)->format('Y-m-d'); - $fechaMax = now()->addDays($tarifa->dias + 15)->format('Y-m-d'); - - if ((int) $tarifa->pantallas === (int) $servicio->completa) { - return Cuentas::where('servicio_id', $servicio->id) - ->whereDate('vencimiento', '>=', $fechaMin) - ->whereDate('vencimiento', '<=', $fechaMax) - ->where('estado', 'pendiente') - ->doesntHave('perfil') - ->count(); - } - - $suma = $servicio->pantallas - $tarifa->pantallas; - $baseQ = Cuentas::select('*') - ->where('servicio_id', $servicio->id) - ->whereDate('inicio', '>=', $fechaBase) - ->whereDate('vencimiento', '>=', $fechaMin) - ->whereDate('vencimiento', '<=', $fechaMax) - ->where('estado', 'activo') - ->withCount('historiales'); - - $cuentas = Cuentas::fromSub($baseQ, 'alias') - ->where('historiales_count', '<=', $suma) - ->get(); - - if ($cuentas->isEmpty()) { - return 0; - } - - $tomadas = Historial_cuenta::whereIn('cuenta_id', $cuentas->pluck('id')) - ->whereNotNull('historial_id') - ->count(); - - return (int) floor((($cuentas->count() * $servicio->pantallas) - $tomadas) / $tarifa->pantallas); + return CuentasHelper::contar($tarifa); } private function buscarCuentaDisponible(Tarifas $tarifa): ?Cuentas { - $servicio = $tarifa->servicio; - $fechaBase = now()->subDays(8)->format('Y-m-d'); - $fechaMin = now()->addDays($tarifa->dias - 15)->format('Y-m-d'); - $fechaMax = now()->addDays($tarifa->dias + 15)->format('Y-m-d'); - - if ((int) $tarifa->pantallas === (int) $servicio->completa) { - return Cuentas::where('servicio_id', $servicio->id) - ->whereDate('vencimiento', '>=', $fechaMin) - ->whereDate('vencimiento', '<=', $fechaMax) - ->where('estado', 'pendiente') - ->doesntHave('perfil') - ->orderBy('inicio', 'ASC') - ->first(); - } - - $suma = $servicio->pantallas - $tarifa->pantallas; - $baseQ = Cuentas::select('*') - ->where('servicio_id', $servicio->id) - ->whereDate('inicio', '>=', $fechaBase) - ->whereDate('vencimiento', '>=', $fechaMin) - ->whereDate('vencimiento', '<=', $fechaMax) - ->where('estado', 'activo') - ->withCount('historiales') - ->orderBy('inicio', 'ASC'); - - $cuenta = Cuentas::fromSub($baseQ, 'alias') - ->where('historiales_count', '<=', $suma) - ->orderBy('inicio', 'ASC') - ->lockForUpdate() - ->first(); - - if (! $cuenta) { - $suma2 = $servicio->pantallas - $tarifa->pantallas; - $baseQ2 = Cuentas::select('*') - ->where('servicio_id', $servicio->id) - ->whereDate('inicio', '>=', $fechaBase) - ->whereDate('vencimiento', '>=', $fechaMin) - ->whereDate('vencimiento', '<=', $fechaMax) - ->withCount('historiales') - ->where('estado', 'activo') - ->orderBy('inicio', 'ASC'); - - $cuenta = Cuentas::fromSub($baseQ2, 'alias') - ->where('historiales_count', '<=', $suma2) - ->orderBy('inicio', 'ASC') - ->lockForUpdate() - ->first(); - } - - return $cuenta; + return CuentasHelper::buscar($tarifa); } private function efectivePrice(int $tarifaId, ?int $userId, float $base): float { - if ($userId) { - $pref = Preferencial::where('usuario_id', $userId)->where('tarifa_id', $tarifaId)->first(); - if ($pref) { - return (float) $pref->valor; - } - } - return (float) $base; + return CuentasHelper::precio($tarifaId, $userId, $base); } private function volver(string $action): void diff --git a/app/Services/CuentasHelper.php b/app/Services/CuentasHelper.php new file mode 100644 index 0000000..ec2d07b --- /dev/null +++ b/app/Services/CuentasHelper.php @@ -0,0 +1,136 @@ +servicio; + $fechaBase = now()->subDays(8)->format('Y-m-d'); + $fechaMin = now()->addDays($tarifa->dias - 15)->format('Y-m-d'); + $fechaMax = now()->addDays($tarifa->dias + 15)->format('Y-m-d'); + + // Cuenta completa + if ((int) $tarifa->pantallas === (int) $servicio->completa) { + return Cuentas::where('servicio_id', $servicio->id) + ->whereDate('vencimiento', '>=', $fechaMin) + ->whereDate('vencimiento', '<=', $fechaMax) + ->where('estado', 'pendiente') + ->doesntHave('perfil') + ->count(); + } + + // Cuenta por pantalla + $suma = $servicio->pantallas - $tarifa->pantallas; + $baseQ = Cuentas::select('*') + ->where('servicio_id', $servicio->id) + ->whereDate('inicio', '>=', $fechaBase) + ->whereDate('vencimiento', '>=', $fechaMin) + ->whereDate('vencimiento', '<=', $fechaMax) + ->where('estado', 'activo') + ->withCount('historiales'); + + $cuentas = Cuentas::fromSub($baseQ, 'alias') + ->where('historiales_count', '<=', $suma) + ->get(); + + if ($cuentas->isEmpty()) { + return 0; + } + + $tomadas = Historial_cuenta::whereIn('cuenta_id', $cuentas->pluck('id')) + ->whereNotNull('historial_id') + ->count(); + + return (int) floor((($cuentas->count() * $servicio->pantallas) - $tomadas) / $tarifa->pantallas); + } + + /** + * Busca la primera cuenta disponible para una tarifa. + */ + public static function buscar(Tarifas $tarifa): ?Cuentas + { + $servicio = $tarifa->servicio; + $fechaBase = now()->subDays(8)->format('Y-m-d'); + $fechaMin = now()->addDays($tarifa->dias - 15)->format('Y-m-d'); + $fechaMax = now()->addDays($tarifa->dias + 15)->format('Y-m-d'); + + // Cuenta completa + if ((int) $tarifa->pantallas === (int) $servicio->completa) { + return Cuentas::where('servicio_id', $servicio->id) + ->whereDate('vencimiento', '>=', $fechaMin) + ->whereDate('vencimiento', '<=', $fechaMax) + ->where('estado', 'pendiente') + ->doesntHave('perfil') + ->orderBy('inicio', 'ASC') + ->first(); + } + + // Cuenta por pantalla — intento normal + $suma = $servicio->pantallas - $tarifa->pantallas; + $baseQ = Cuentas::select('*') + ->where('servicio_id', $servicio->id) + ->whereDate('inicio', '>=', $fechaBase) + ->whereDate('vencimiento', '>=', $fechaMin) + ->whereDate('vencimiento', '<=', $fechaMax) + ->where('estado', 'activo') + ->withCount('historiales') + ->orderBy('inicio', 'ASC'); + + $cuenta = Cuentas::fromSub($baseQ, 'alias') + ->where('historiales_count', '<=', $suma) + ->orderBy('inicio', 'ASC') + ->lockForUpdate() + ->first(); + + // Segundo intento sin filtro de inicio + if (! $cuenta) { + $baseQ2 = Cuentas::select('*') + ->where('servicio_id', $servicio->id) + ->whereDate('vencimiento', '>=', $fechaMin) + ->whereDate('vencimiento', '<=', $fechaMax) + ->where('estado', 'activo') + ->withCount('historiales') + ->orderBy('inicio', 'ASC'); + + $cuenta = Cuentas::fromSub($baseQ2, 'alias') + ->where('historiales_count', '<=', $suma) + ->orderBy('inicio', 'ASC') + ->lockForUpdate() + ->first(); + } + + return $cuenta; + } + + /** + * Precio efectivo para un usuario y tarifa. + * Prioridad: Usuario_tarifa (web) → Preferencial (legacy) → precio base. + */ + public static function precio(int $tarifaId, ?int $userId, float $base): float + { + if ($userId) { + $ut = Usuario_tarifa::where('usuario_id', $userId)->where('tarifa_id', $tarifaId)->first(); + if ($ut) { + return (float) $ut->precio; + } + + $pref = Preferencial::where('usuario_id', $userId)->where('tarifa_id', $tarifaId)->first(); + if ($pref) { + return (float) $pref->valor; + } + } + + return (float) $base; + } +} diff --git a/app/Services/TelegramBotService.php b/app/Services/TelegramBotService.php index e0ce88c..5c4d5c4 100755 --- a/app/Services/TelegramBotService.php +++ b/app/Services/TelegramBotService.php @@ -13,6 +13,7 @@ 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; @@ -649,7 +650,7 @@ class TelegramBotService $buttons = []; foreach ($tarifas as $t) { - $precio = $this->efectivePrice($t->id, $state['user_id'], $t->valor); + $precio = CuentasHelper::precio($t->id, $state['user_id'], $t->valor); $label = $servicio->por_tiempo ? "{$t->dias} días - \$" . number_format($precio) : "{$t->pantallas} pantalla(s) / {$t->dias} días - \$" . number_format($precio); @@ -672,8 +673,7 @@ class TelegramBotService return; } - $disponibles = Cuentas::where('tarifa_id', $tarifaId)->where('estado', 'activo')->count(); - if ($disponibles < 1) { + if (CuentasHelper::contar($tarifa) < 1) { $this->send($chatId, "Sin cuentas disponibles para este plan. Intenta con otro."); $this->listServices($chatId); return; @@ -681,7 +681,7 @@ class TelegramBotService $user = User::with('saldo')->find($state['user_id']); $saldo = $user->saldo?->valor ?? 0; - $valor = $this->efectivePrice($tarifaId, $state['user_id'], $tarifa->valor); + $valor = CuentasHelper::precio($tarifaId, $state['user_id'], $tarifa->valor); $state['data'] = ['flow' => 'compra', 'tarifa_id' => $tarifaId, 'valor' => $valor, 'servicio' => $tarifa->servicio->nombre, 'servicio_id' => $tarifa->servicio_id]; $this->setState($chatId, $state); @@ -722,13 +722,15 @@ class TelegramBotService return; } - $saldo = $user->saldo; - if (! $saldo || $saldo->valor < $tarifa->valor) { - $this->send($chatId, "Saldo insuficiente (\$" . number_format($saldo?->valor ?? 0) . "). El plan cuesta \$" . number_format($tarifa->valor) . "."); + $precio = (float) ($data['valor'] ?? CuentasHelper::precio($tarifa->id, $state['user_id'], $tarifa->valor)); + $saldo = $user->saldo; + + if (! $saldo || $saldo->valor < $precio) { + $this->send($chatId, "Saldo insuficiente (\$" . number_format($saldo?->valor ?? 0) . "). El plan cuesta \$" . number_format($precio) . "."); return; } - $cuenta = Cuentas::where('tarifa_id', $tarifa->id)->where('estado', 'activo')->first(); + $cuenta = CuentasHelper::buscar($tarifa); if (! $cuenta) { $this->send($chatId, "No hay cuentas disponibles en este momento. Intenta más tarde."); return; @@ -737,7 +739,7 @@ class TelegramBotService $historial = Historiale::create([ 'fecha_inicio' => now(), 'fecha_final' => now()->addDays($tarifa->dias), - 'valor' => $tarifa->valor, + 'valor' => $precio, 'utilidad' => $tarifa->utilidad ?? 0, 'tipo_pago' => 'saldo', 'estado' => 'entregado', @@ -749,7 +751,7 @@ class TelegramBotService Historial_cuenta::create(['historial_id' => $historial->id, 'cuenta_id' => $cuenta->id]); $cuenta->update(['estado' => 'ocupado']); - $nuevoSaldo = $saldo->valor - $tarifa->valor; + $nuevoSaldo = $saldo->valor - $precio; $saldo->update(['valor' => $nuevoSaldo]); $text = "✅ *¡Compra exitosa!*\n\n"; @@ -760,6 +762,7 @@ class TelegramBotService $text .= "📺 Perfil: {$tarifa->pantallas}\n"; } $text .= "📅 Vence: " . now()->addDays($tarifa->dias)->format('d/m/Y') . "\n"; + $text .= "💰 Pagado: *\$" . number_format($precio) . "*\n"; $text .= "\nSaldo restante: *\$" . number_format($nuevoSaldo) . "*"; $this->send($chatId, $text); @@ -1395,16 +1398,6 @@ class TelegramBotService // ─── Helpers ────────────────────────────────────────────── - private function efectivePrice(int $tarifaId, ?int $userId, float $base): float - { - if ($userId) { - $pref = Preferencial::where('usuario_id', $userId)->where('tarifa_id', $tarifaId)->first(); - if ($pref) { - return (float) $pref->valor; - } - } - return (float) $base; - } private function maskEmail(string $email): string {