diff --git a/app/Http/Livewire/ShowPromociones.php b/app/Http/Livewire/ShowPromociones.php index e1ecaff..096146c 100755 --- a/app/Http/Livewire/ShowPromociones.php +++ b/app/Http/Livewire/ShowPromociones.php @@ -234,178 +234,221 @@ class ShowPromociones extends Component $intento = 0; while ($intento < $maxIntentos) { + DB::beginTransaction(); + try { $intento++; - DB::transaction(function () { - $this->validate(); + $this->validate(); - // === Validaciones iniciales === - if ($this->cantidad_comprarPromo == 0) { - throw new \Exception('La cantidad debe ser mayor a 0', 400); - } + if ($this->cantidad_comprarPromo == 0) { + $this->alert('error', 'La cantidad debe ser mayor a 0', ['position' => 'top']); + DB::rollBack(); + return; + } - $costoTotal = $this->precioAhora * $this->cantidad_comprarPromo; - $saldoDisponible = auth()->user()->saldo->valor; + if (auth()->user()->saldo->valor < $this->precioAhora * $this->cantidad_comprarPromo) { + $this->alert('error', 'Saldo insuficiente', ['position' => 'top']); + DB::rollBack(); + return; + } - if ($saldoDisponible < $costoTotal) { - throw new \Exception('Saldo insuficiente', 400); - } + // === Crear o buscar usuario === + $usuario_existe = User::firstWhere('email', $this->email_comprarPromo); + if (is_null($usuario_existe)) { + $usuario = new User; + $usuario->name = $this->nombre_comprarPromo; + $usuario->email = $this->email_comprarPromo; + $usuario->password = bcrypt('123'); // se mantiene tal cual + $usuario->celular = $this->celular_comprarPromo; + $usuario->save(); - // === Crear o buscar usuario === - $usuario = User::firstWhere('email', $this->email_comprarPromo); + $usuario_id = $usuario->id; + } else { + $usuario_id = $usuario_existe->id; + } - if (!$usuario) { - $usuario = User::create([ - 'name' => $this->nombre_comprarPromo, - 'email' => $this->email_comprarPromo, - 'password' => bcrypt(Str::random(12)), // 🔒 contraseña aleatoria - 'celular' => $this->celular_comprarPromo, - ]); - } + // === Historial === + $historial = new Historiale(); + $historial->fecha_inicio = $this->fechaHoy; + $historial->fecha_final = $this->fechaFinal; + $historial->valor = $this->precioAhora * $this->cantidad_comprarPromo; + $historial->tipo_pago = 'manual'; + $historial->estado = 'pendiente'; + $historial->vendedor_id = Auth()->user()->id; + $historial->promocion_id = $this->consulta->id; + $historial->cliente_id = $usuario_id; + $historial->cantidad = $this->cantidad_comprarPromo; + $historial->nombre_cliente = $this->nombre_comprarPromo; + $historial->utilidad = $this->utilidad * $this->cantidad_comprarPromo; + $historial->save(); - // === Historial === - $historial = Historiale::create([ - 'fecha_inicio' => $this->fechaHoy, - 'fecha_final' => $this->fechaFinal, - 'valor' => $costoTotal, - 'tipo_pago' => 'manual', - 'estado' => 'pendiente', - 'vendedor_id' => Auth()->user()->id, - 'promocion_id' => $this->consulta->id, - 'cliente_id' => $usuario->id, - 'cantidad' => $this->cantidad_comprarPromo, - 'nombre_cliente' => $this->nombre_comprarPromo, - 'utilidad' => $this->utilidad * $this->cantidad_comprarPromo, - ]); + $utilidad_total = (int) $this->utilidad * (int) $this->cantidad_comprarPromo; + $this->historial = $historial; - $utilidad_total = (int) $this->utilidad * (int) $this->cantidad_comprarPromo; - $this->historial = $historial; + // === Asignar cuentas === + $this->n = []; + $this->f = []; - // === Asignar cuentas === - $this->n = []; - $this->f = []; + for ($p = 0; $p < (int) $this->cantidad_comprarPromo; $p++) { + foreach ($this->consulta->tarifas as $tarifa) { + $pantallas_tarifa = $tarifa->pantallas; + $servicio_id = $tarifa->servicio_id; + $pantalla_servicio = $tarifa->servicio->pantallas; + $pantalla_servicio_completa = $tarifa->servicio->completa; + $faltante = $pantalla_servicio - $pantallas_tarifa; - for ($p = 0; $p < (int) $this->cantidad_comprarPromo; $p++) { - foreach ($this->consulta->tarifas as $tarifa) { - $pantallas_tarifa = $tarifa->pantallas; - $servicio_id = $tarifa->servicio_id; - $pantalla_servicio = $tarifa->servicio->pantallas; - $pantalla_servicio_completa = $tarifa->servicio->completa; - $faltante = $pantalla_servicio - $pantallas_tarifa; + if ($pantalla_servicio_completa == $pantallas_tarifa) { + // Se mantienen props en $this (no locales) + $this->fecha_final_1 = Carbon::now()->addDays($tarifa->dias - 20); + $this->fecha_final_2 = Carbon::now()->addDays($tarifa->dias + 20); - if ($pantalla_servicio_completa == $pantallas_tarifa) { - $fecha_final_1 = Carbon::now()->addDays($tarifa->dias - 20); - $fecha_final_2 = Carbon::now()->addDays($tarifa->dias + 20); + $baseQuery = Cuentas::where('servicio_id', $servicio_id) + ->whereDate('inicio', '>=', $this->fecha) + ->whereDate('vencimiento', '>=', $this->fecha_final_1) + ->whereDate('vencimiento', '<=', $this->fecha_final_2) + ->orderBy('inicio', 'ASC') + ->withCount('historiales') + ->where('estado', 'pendiente'); - $baseQuery = Cuentas::where('servicio_id', $servicio_id) - ->whereDate('inicio', '>=', $this->fecha) - ->whereDate('vencimiento', '>=', $fecha_final_1) - ->whereDate('vencimiento', '<=', $fecha_final_2) - ->where('estado', 'pendiente') - ->withCount('historiales') - ->orderBy('inicio', 'ASC'); + $cuenta = Cuentas::fromSub($baseQuery, 'alias') + ->where('historiales_count', '=', 0) + ->lockForUpdate() // agregado: evita doble toma + ->first(); + } else { + $baseQuery = Cuentas::where('servicio_id', $servicio_id) + ->whereDate('inicio', '>=', $this->fecha) + ->orderBy('inicio', 'ASC') + ->withCount('historiales') + ->where('estado', 'activo'); - $cuenta = Cuentas::fromSub($baseQuery, 'alias') - ->where('historiales_count', '=', 0) - ->lockForUpdate() - ->first(); - } else { - $baseQuery = Cuentas::where('servicio_id', $servicio_id) - ->whereDate('inicio', '>=', $this->fecha) - ->where('estado', 'activo') - ->withCount('historiales') - ->orderBy('inicio', 'ASC'); + $cuenta = Cuentas::fromSub($baseQuery, 'alias') + ->where('historiales_count', '<=', (int) $faltante) + ->lockForUpdate() // agregado: evita doble toma + ->first(); + } - $cuenta = Cuentas::fromSub($baseQuery, 'alias') - ->where('historiales_count', '<=', (int) $faltante) - ->lockForUpdate() - ->first(); - } + if ($cuenta) { + $perfil = Historial_cuenta::where('cuenta_id', $cuenta->id)->max('perfil') ?? 0; + for ($y = 0; $y < (int) $pantallas_tarifa; $y++) { + $historial_cuenta = new Historial_cuenta(); + $historial_cuenta->historial_id = $historial->id; + $historial_cuenta->cuenta_id = $cuenta->id; + $historial_cuenta->perfil = (int) $perfil + (int) $y + 1; + $historial_cuenta->save(); - if ($cuenta) { - $perfil = Historial_cuenta::where('cuenta_id', $cuenta->id)->max('perfil') ?? 0; - - for ($y = 0; $y < (int) $pantallas_tarifa; $y++) { - $historial_cuenta = Historial_cuenta::create([ - 'historial_id' => $historial->id, - 'cuenta_id' => $cuenta->id, - 'perfil' => (int) $perfil + (int) $y + 1, - ]); - - if ($historial_cuenta->id) { - $this->n[] = $historial_cuenta->id; - } + if ($historial_cuenta->id) { + $this->n[] = $historial_cuenta->id; } - } else { - $this->f[] = $servicio_id; - throw new \Exception('No hay cuentas disponibles de ' . $tarifa->servicio->nombre, 409); } + } else { + $this->f[] = $servicio_id; + // Se mantiene alerta por cada servicio sin disponibilidad + $this->alert('warning', 'No hay cuentas disponibles de ' . $tarifa->servicio->nombre, ['position' => 'top']); } } + } - // === Cobro de saldo === - $descuento = Saldo::where('usuario_id', Auth::user()->id)->first(); - $saldo_anterior = $descuento->valor; - $nuevo_saldo = $saldo_anterior - $costoTotal; + // === Validación de disponibilidad === + if (count($this->f) > 0) { + DB::rollBack(); // no se descuenta saldo + $resultados = Servicio::whereIn('id', $this->f)->get(); - if (!$descuento->update(['valor' => $nuevo_saldo])) { - Historial_cuenta::whereIn('id', $this->n)->delete(); - throw new \Exception('Error en el cobro, comuníquese con soporte!', 500); + if ($resultados->count() > 0) { + $nombresServicios = $resultados->pluck('nombre')->toArray(); + $message = implode(', ', $nombresServicios); + + // Se mantiene EXACTAMENTE el mismo modal con confirm/deny + $this->alert('warning', 'No hay disponibilidad de ' . $message . ' en este momento.', [ + 'position' => 'center', + 'timer' => '60000', + 'toast' => false, + 'text' => '¿Te gustaría realizar la compra ahora y recibir tu pedido tan pronto como haya disponibilidad?.', + 'allowOutsideClick' => false, + 'allowEscapeKey' => false, + 'showConfirmButton' => true, + 'showCancelButton' => false, + 'showDenyButton' => true, + 'onConfirmed' => 'confirmarCompra', + 'onDenied' => 'denegarCompra', + 'denyButtonText' => 'No, abandonar.', + 'confirmButtonText' => 'Sí, comprar.', + 'customClass' => [ + 'confirmButton' => 'bg-green-500', + 'denyButton' => 'bg-red-500', + 'cancelButton' => 'bg-gray-500', + ] + ]); } + return; + } - // Activar historial + // === Cobro de saldo === + $descuento = Saldo::where('usuario_id', Auth::user()->id)->first(); + $saldo_anterior = $descuento->valor; + $nuevo_saldo = $saldo_anterior - ($this->precioAhora * $this->cantidad_comprarPromo); + + if ($descuento->update(['valor' => $nuevo_saldo])) { $historial->update(['estado' => 'activo']); // Registrar logs - Log_general::create([ - 'user_id' => Auth::user()->id, - 'detalle' => 'El usuario ' . Auth::user()->name . ' - ' . Auth::user()->email . - ' ha comprado la promoción ' . $this->consulta->nombre . ' - ' . $this->consulta->descripcion . - ' cantidad de ' . $this->historial->cantidad . - ' por un valor de $' . $this->historial->valor . - ', Saldo anterior: $' . $saldo_anterior . - ', Nuevo saldo disponible: $' . $nuevo_saldo, - 'tipo' => 'compra', - 'historial_id' => $historial->id, - ]); + $logsGeneral = new Log_general(); + $logsGeneral->user_id = Auth::user()->id; + $logsGeneral->detalle = 'El usuario ' . Auth::user()->name . ' - ' . Auth::user()->email . + ' ha comprado la promoción ' . $this->consulta->nombre . ' - ' . $this->consulta->descripcion . + ' cantidad de ' . $this->historial->cantidad . + ' por un valor de $' . $this->historial->valor . + ', Saldo anterior: $' . $saldo_anterior . + ', Nuevo saldo disponible: $' . $nuevo_saldo; + $logsGeneral->tipo = 'compra'; + $logsGeneral->historial_id = $historial->id; + $logsGeneral->save(); - // Utilidad para el padre + // utilidad para el padre if (Auth::user()->subvendedor_id && $utilidad_total > 0) { $subvendedorPadre = User::with('saldo')->find(Auth::user()->subvendedor_id); if ($subvendedorPadre && $subvendedorPadre->saldo) { $nuevoSaldo = $subvendedorPadre->saldo->valor + $utilidad_total; $subvendedorPadre->saldo->update(['valor' => $nuevoSaldo]); - Log_general::create([ - 'user_id' => Auth::user()->id, - 'detalle' => 'Compra generó utilidad de $' . $utilidad_total . - ' al usuario padre ' . $subvendedorPadre->name . ' - ' . $subvendedorPadre->email, - 'historial_id' => $historial->id, - 'tipo' => 'compra', - ]); + $logsGeneral = new Log_general(); + $logsGeneral->user_id = Auth::user()->id; + $logsGeneral->detalle = 'Compra generó utilidad de $' . $utilidad_total . + ' al usuario padre ' . $subvendedorPadre->name . ' - ' . $subvendedorPadre->email; + $logsGeneral->historial_id = $historial->id; + $logsGeneral->tipo = 'compra'; + $logsGeneral->save(); } } $this->modalComprarPromo = false; $this->credenciales($historial); - }, 3); // Laravel volverá a intentar hasta 3 veces si hay deadlocks - // ✅ si llega aquí es que todo salió bien - break; + DB::commit(); + break; // ✅ salir del while (ya se completó la compra) + } else { + Historial_cuenta::whereIn('id', $this->n)->delete(); + DB::rollBack(); + $this->alert('warning', 'Error en el cobro, comuníquese con soporte!', ['position' => 'top']); + return; + } } catch (QueryException $e) { - if (in_array($e->getCode(), ['23505', '1062'])) { // PostgreSQL o MySQL + DB::rollBack(); + + // Se mantiene 23505 y se agrega 1062 (no quita nada, solo compatibilidad) + if (in_array($e->getCode(), ['23505', '1062'])) { if ($intento >= $maxIntentos) { $this->alert('warning', 'Otra persona tomó la cuenta al mismo tiempo. Intenta nuevamente.', ['position' => 'top']); return; } - usleep(200000); // esperar 200ms antes de reintentar + usleep(200000); // 200ms y reintentar continue; } - $this->alert('error', 'Error en la compra: ' . $e->getMessage(), ['position' => 'top']); + $this->alert('error', 'Error inesperado en la compra: ' . $e->getMessage(), ['position' => 'top']); return; } catch (\Throwable $e) { + DB::rollBack(); $this->alert('error', 'Error inesperado: ' . $e->getMessage(), ['position' => 'top']); return; } @@ -413,6 +456,7 @@ class ShowPromociones extends Component } + public function confirmarCompra() { $descuento = Saldo::where('usuario_id', Auth::user()->id)->first();