fix: account lookup now uses real availability logic (servicio_id + pantallas)

- 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 <noreply@anthropic.com>
This commit is contained in:
Lizandro
2026-07-14 13:46:36 +00:00
co-authored by Claude Sonnet 4.6
parent 26953c9dd5
commit b4624db056
+123 -25
View File
@@ -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) {