diff --git a/app/Http/Controllers/MenuController.php b/app/Http/Controllers/MenuController.php index 86abec5..5795220 100755 --- a/app/Http/Controllers/MenuController.php +++ b/app/Http/Controllers/MenuController.php @@ -42,6 +42,10 @@ class MenuController extends Controller { return view('menu/tarifas'); } + public function showgestionrangos() + { + return view('menu/gestion-rangos'); + } public function showclientes() { return view('menu/clientes'); diff --git a/app/Http/Livewire/ShareCodeComponent.php b/app/Http/Livewire/ShareCodeComponent.php index 3b06132..3f8b40e 100644 --- a/app/Http/Livewire/ShareCodeComponent.php +++ b/app/Http/Livewire/ShareCodeComponent.php @@ -7,6 +7,13 @@ use Livewire\Component; class ShareCodeComponent extends Component { + public $user; + + public function mount() + { + $this->user = Auth::user(); + } + public function render() { $referrals = Auth::user()->children() diff --git a/app/Http/Livewire/ShowGestionRangos.php b/app/Http/Livewire/ShowGestionRangos.php new file mode 100644 index 0000000..8bbe8bc --- /dev/null +++ b/app/Http/Livewire/ShowGestionRangos.php @@ -0,0 +1,132 @@ + 'required|string|max:100', + 'cashback' => 'required|numeric|min:0', + 'bono_equipo' => 'required|numeric|min:0', + 'tope_bono_equipo' => 'required|numeric|min:0', + 'compra_personal_min' => 'required|numeric|min:0', + 'directos_activos_min' => 'required|integer|min:0', + 'niveles_min' => 'required|integer|min:0', + 'volumen_equipo_min' => 'required|numeric|min:0', + 'volumen_por_nivel_min' => 'required|numeric|min:0', + 'logo' => 'nullable|image|max:2048', + ]; + + protected $messages = [ + 'nombre.required' => 'El nombre del rango es obligatorio.', + 'nombre.string' => 'El nombre debe ser una cadena de texto válida.', + 'nombre.max' => 'El nombre no puede tener más de 100 caracteres.', + + 'cashback.required' => 'El porcentaje de cashback es obligatorio.', + 'cashback.numeric' => 'El cashback debe ser un valor numérico.', + 'cashback.min' => 'El cashback no puede ser menor a 0.', + + 'bono_equipo.required' => 'El porcentaje de bono de equipo es obligatorio.', + 'bono_equipo.numeric' => 'El bono de equipo debe ser un valor numérico.', + 'bono_equipo.min' => 'El bono de equipo no puede ser menor a 0.', + + 'tope_bono_equipo.required' => 'El tope del bono de equipo es obligatorio.', + 'tope_bono_equipo.numeric' => 'El tope del bono de equipo debe ser un valor numérico.', + 'tope_bono_equipo.min' => 'El tope del bono de equipo no puede ser menor a 0.', + + 'compra_personal_min.required' => 'La compra personal mínima es obligatoria.', + 'compra_personal_min.numeric' => 'La compra personal mínima debe ser un número.', + 'compra_personal_min.min' => 'La compra personal mínima no puede ser menor a 0.', + + 'directos_activos_min.required' => 'El número de directos activos mínimos es obligatorio.', + 'directos_activos_min.integer' => 'Los directos activos deben ser un número entero.', + 'directos_activos_min.min' => 'Los directos activos no pueden ser menores a 0.', + + 'niveles_min.required' => 'El número de niveles mínimos es obligatorio.', + 'niveles_min.integer' => 'Los niveles mínimos deben ser un número entero.', + 'niveles_min.min' => 'Los niveles mínimos no pueden ser menores a 0.', + + 'volumen_equipo_min.required' => 'El volumen mínimo de equipo es obligatorio.', + 'volumen_equipo_min.numeric' => 'El volumen mínimo de equipo debe ser un número.', + 'volumen_equipo_min.min' => 'El volumen mínimo de equipo no puede ser menor a 0.', + + 'volumen_por_nivel_min.required' => 'El volumen por nivel mínimo es obligatorio.', + 'volumen_por_nivel_min.numeric' => 'El volumen por nivel mínimo debe ser un número.', + 'volumen_por_nivel_min.min' => 'El volumen por nivel mínimo no puede ser menor a 0.', + + 'logo.image' => 'El archivo del logo debe ser una imagen válida (JPG, PNG, etc.).', + 'logo.max' => 'El logo no puede superar los 2 MB.', + ]; + + public function render() + { + $this->ranks = Rank::orderBy('volumen_equipo_min')->get(); + + return view('livewire.show-gestion-rangos'); + } + + public function crear() + { + $this->resetExcept('ranks'); + $this->modal = true; + } + + public function editar($id) + { + $rank = Rank::findOrFail($id); + $this->fill($rank->toArray()); + $this->rank_id = $rank->id; + $this->modal = true; + } + + public function guardar() + { + $this->validate(); + + $rutaLogo = null; + + if ($this->logo) { + $rutaLogo = $this->logo->store('photos'); + } + + Rank::updateOrCreate( + ['id' => $this->rank_id], + [ + 'nombre' => $this->nombre, + 'cashback' => $this->cashback, + 'bono_equipo' => $this->bono_equipo, + 'tope_bono_equipo' => $this->tope_bono_equipo, + 'compra_personal_min' => $this->compra_personal_min, + 'directos_activos_min' => $this->directos_activos_min, + 'niveles_min' => $this->niveles_min, + 'volumen_equipo_min' => $this->volumen_equipo_min, + 'volumen_por_nivel_min' => $this->volumen_por_nivel_min, + 'logo' => $rutaLogo ?? $this->logo_url, + ] + ); + + $this->modal = false; + $this->resetExcept('ranks'); + } + + public function eliminar($id) + { + Rank::findOrFail($id)->delete(); + } +} diff --git a/app/Http/Livewire/ShowRangos.php b/app/Http/Livewire/ShowRangos.php new file mode 100644 index 0000000..1b6fa49 --- /dev/null +++ b/app/Http/Livewire/ShowRangos.php @@ -0,0 +1,13 @@ +hasMany(User::class, 'rank_id'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 9b024df..06c117e 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -23,11 +23,11 @@ class User extends Authenticatable 'rol_id', 'subvendedor_id', 'subvendedor', + 'ip_address', 'referral_code', 'parent_id', - 'rank_id', ]; @@ -126,10 +126,10 @@ class User extends Authenticatable return $this->hasMany(User::class, 'parent_id')->orderBy('created_at', 'desc'); } - // public function rank() - // { - // return $this->belongsTo(Rank::class); - // } + public function rank() + { + return $this->belongsTo(Rank::class, 'rank_id'); + } protected static function boot() { diff --git a/app/View/Components/InputField.php b/app/View/Components/InputField.php new file mode 100644 index 0000000..5ac5de9 --- /dev/null +++ b/app/View/Components/InputField.php @@ -0,0 +1,26 @@ +label = $label; + $this->model = $model; + $this->type = $type; + $this->step = $step; + } + + public function render() + { + return view('components.input-field'); + } +} diff --git a/database/migrations/2025_10_18_114653_add_ip_to_users_table.php b/database/migrations/2025_10_18_114653_add_ip_to_users_table.php new file mode 100644 index 0000000..4f3c3da --- /dev/null +++ b/database/migrations/2025_10_18_114653_add_ip_to_users_table.php @@ -0,0 +1,22 @@ +string('ip_address')->nullable(); + }); + } + + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('ip_address'); + }); + } +}; diff --git a/database/migrations/2025_10_23_130227_create_ranks_table.php b/database/migrations/2025_10_23_130227_create_ranks_table.php new file mode 100644 index 0000000..3b1b734 --- /dev/null +++ b/database/migrations/2025_10_23_130227_create_ranks_table.php @@ -0,0 +1,39 @@ +id(); + + $table->string('nombre'); // Bronce, Plata, Oro, Diamante + + $table->string('logo')->nullable(); + + $table->decimal('cashback', 5, 2)->default(0); // % de cashback personal + $table->decimal('bono_equipo', 5, 2)->default(0); // % de bono sobre ventas del equipo + $table->integer('tope_bono_equipo')->default(0); // límite máximo del bono + + // Requisitos + $table->integer('compra_personal_min')->default(0); + $table->integer('directos_activos_min')->default(0); + $table->integer('niveles_min')->default(0); + $table->integer('volumen_equipo_min')->default(0); + + // Valor mínimo por nivel (ej: cada nivel >= 300.000) + $table->integer('volumen_por_nivel_min')->default(0); + + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('ranks'); + } +}; diff --git a/resources/views/components/input-field.blade.php b/resources/views/components/input-field.blade.php new file mode 100644 index 0000000..2ce1142 --- /dev/null +++ b/resources/views/components/input-field.blade.php @@ -0,0 +1,16 @@ +@props(['label', 'model', 'type' => 'text', 'step' => null]) + +
+ + + + + @error($model) + {{ $message }} + @enderror +
diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php index 5fd7925..5f700ff 100755 --- a/resources/views/layouts/navigation.blade.php +++ b/resources/views/layouts/navigation.blade.php @@ -182,6 +182,12 @@ @if (auth()->user()->rol->nombre == 'super') + + + + + Gestión Rangos + diff --git a/resources/views/livewire/share-code-component.blade.php b/resources/views/livewire/share-code-component.blade.php index b7d8315..3a7b9b9 100644 --- a/resources/views/livewire/share-code-component.blade.php +++ b/resources/views/livewire/share-code-component.blade.php @@ -1,3 +1,88 @@ +
+
+
+ Rango Diamante +
+ +
+

Rango actual

+

Bronce

+
+
+
+
+
+
+ +

Te faltan $120,000 para llegar a Plata

+ + +
+ +
+
+ + + +

Compra personal

+
+

$70,000

+
+ + +
+
+ + + + +

Cashback actual

+
+

$25,000

+
+ + +
+
+ + + + +

Volumen de equipo

+
+

$1,250,000

+
+ + +
+
+ + + +

Directos activos

+
+

4

+
+
+ +
+ + + + + + + + + + + + +
diff --git a/resources/views/livewire/show-gestion-rangos.blade.php b/resources/views/livewire/show-gestion-rangos.blade.php new file mode 100644 index 0000000..d94d36d --- /dev/null +++ b/resources/views/livewire/show-gestion-rangos.blade.php @@ -0,0 +1,130 @@ +
+
+
+
+ +

Rangos

+
+
+ +
+ +
+
+ + + + + Logo + Nombre + Cashback + Bono Equipo + Tope + Compra Mínima + Directos + Niveles + Volumen + Por Nivel + + + + + @forelse ($ranks as $rank) + + + @if($rank->logo) + {{ $rank->nombre }} + @endif + + {{ $rank->cashback }}% + {{ $rank->cashback }}% + {{ $rank->bono_equipo }}% + ${{ number_format($rank->tope_bono_equipo) }} + ${{ number_format($rank->compra_personal_min) }} + {{ $rank->directos_activos_min }} + {{ $rank->niveles_min }} + ${{ number_format($rank->volumen_equipo_min) }} + ${{ number_format($rank->volumen_por_nivel_min) }} + + + +
+ + +
+ + + @empty + + No hay rangos registrados aún. + + @endforelse +
+ + +
+ + {{-- Modal --}} +
+
+ +
+

{{ $rank_id ? 'Editar Rango' : 'Nuevo Rango' }}

+ +
+
+ + + @error('logo') {{ $message }} @enderror + + {{-- Preview --}} + @if ($logo) +
+ +
+ @elseif ($logo_url) +
+ +
+ @endif +
+ + + + + + + + + + + +
+ + +
+
+
+
+
diff --git a/resources/views/livewire/show-rangos.blade.php b/resources/views/livewire/show-rangos.blade.php new file mode 100644 index 0000000..15e5660 --- /dev/null +++ b/resources/views/livewire/show-rangos.blade.php @@ -0,0 +1,3 @@ +
+ {{-- The whole world belongs to you. --}} +
diff --git a/resources/views/menu/gestion-rangos.blade.php b/resources/views/menu/gestion-rangos.blade.php new file mode 100644 index 0000000..d357e67 --- /dev/null +++ b/resources/views/menu/gestion-rangos.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/routes/web.php b/routes/web.php index 45411a2..d69b70d 100755 --- a/routes/web.php +++ b/routes/web.php @@ -71,6 +71,7 @@ Route::middleware(["auth", "solo_usuario_administrador"])->group(function () { Route::any('/log-historial', [MenuController::class, 'showLogHistorial'])->name('logHistorial'); Route::get('/utilidades', [MenuController::class, 'showutilidad'])->name('utilidades'); Route::get('/mantenimiento', [MenuController::class, 'mantenimiento'])->name('mantenimiento'); + Route::get('/rank-management', [MenuController::class, 'showgestionrangos'])->name('gestion-rangos'); }); Route::post('/registrar-accion-compra', [MenuController::class, 'click_compra'])->name('click_compra');