From b4624db0561498f452a82c70f2e217e70142bf4c Mon Sep 17 00:00:00 2001 From: Lizandro Date: Tue, 14 Jul 2026 13:46:36 +0000 Subject: [PATCH] fix: account lookup now uses real availability logic (servicio_id + pantallas) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace tarifa_id+estado='ocupado' query with the same logic used in ShowServicios: filter by servicio_id, date range, historiales_count - Full-account plans (pantallas == completa): look for estado='pendiente' accounts without assigned profiles - Partial plans (screen sharing): look for estado='activo' accounts with remaining slots (historiales_count <= pantallas_servicio - pantallas) - Wrap purchase in DB::transaction to avoid race conditions - Remove incorrect cuenta->update(['estado' => 'ocupado']) — availability is now tracked via historial_cuentas count, not a flag - Use effective price (Preferencial override) for saldo deduction Co-Authored-By: Claude Sonnet 4.6 --- app/Http/Livewire/Chat/PublicChat.php | 148 +++++++++++++++++++++----- 1 file changed, 123 insertions(+), 25 deletions(-) diff --git a/app/Http/Livewire/Chat/PublicChat.php b/app/Http/Livewire/Chat/PublicChat.php index 3c8f0fa..242feba 100644 --- a/app/Http/Livewire/Chat/PublicChat.php +++ b/app/Http/Livewire/Chat/PublicChat.php @@ -6,6 +6,7 @@ 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; @@ -593,8 +594,7 @@ class PublicChat extends Component return; } - $disponibles = Cuentas::where('tarifa_id', $tarifaId)->where('estado', 'activo')->count(); - if ($disponibles < 1) { + if ($this->contarDisponibles($tarifa) < 1) { $this->guardarMensajeBot($this->convId, "Sin cuentas disponibles para este plan. Intenta con otro."); $this->volver('servicios.listar'); return; @@ -614,7 +614,7 @@ class PublicChat extends Component $detalle = $tarifa->servicio->por_tiempo ? "{$tarifa->dias} dias" : "{$tarifa->pantallas} pantalla(s) / {$tarifa->dias} dias"; - $saldoInsuficiente = ! $this->userId || ($this->saldoUsuario < $precio); + $saldoInsuficiente = ! $this->userId || ((float) $this->saldoUsuario < $precio); ChatMessage::create([ 'conversation_id' => $this->convId, @@ -646,42 +646,46 @@ class PublicChat extends Component return; } - $user = User::with('saldo')->find($this->userId); - $saldo = $user->saldo; + $user = User::with('saldo')->find($this->userId); + $saldo = $user->saldo; + $precio = (float) ($this->flujoData['valor'] ?? $tarifa->valor); - if (! $saldo || $saldo->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($tarifa->valor) . "." + " y el plan cuesta $" . number_format($precio) . "." ); $this->volver('menu.principal'); return; } - $cuenta = Cuentas::where('tarifa_id', $tarifa->id)->where('estado', 'activo')->first(); + $cuenta = $this->buscarCuentaDisponible($tarifa); if (! $cuenta) { - $this->guardarMensajeBot($this->convId, "No hay cuentas disponibles en este momento."); + $this->guardarMensajeBot($this->convId, "No hay cuentas disponibles en este momento. Intenta más tarde o contacta a un asesor."); return; } - $historial = Historiale::create([ - 'fecha_inicio' => now(), - 'fecha_final' => now()->addDays($tarifa->dias), - 'valor' => $tarifa->valor, - '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, - ]); + $nuevoSaldo = $saldo->valor - $precio; - Historial_cuenta::create(['historial_id' => $historial->id, 'cuenta_id' => $cuenta->id]); - $cuenta->update(['estado' => 'ocupado']); - $saldo->update(['valor' => $saldo->valor - $tarifa->valor]); - $this->saldoUsuario = $saldo->valor - $tarifa->valor; + 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, @@ -1284,6 +1288,100 @@ 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); + } + + 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; + } + private function efectivePrice(int $tarifaId, ?int $userId, float $base): float { if ($userId) {