From 59d5cf748d51c7853b7f9876fc0cf9077c3ea4af Mon Sep 17 00:00:00 2001 From: BryamE Date: Fri, 9 Jan 2026 01:34:26 -0300 Subject: [PATCH 01/17] Cambios en vista de visitante, separada logica de productos y catalogos poara manejo individual, creados catalogo.index y catalogo.show. acomodadas rutas del catalogo para evitar errores. --- app/Http/Controllers/CatalogoController.php | 9 +- .../index.blade.php} | 18 +-- resources/views/catalogo/show.blade.php | 105 ++++++++++++++++++ resources/views/components/navbar.blade.php | 2 +- .../views/components/ui/carrousel.blade.php | 2 +- resources/views/sales/create.blade.php | 12 +- routes/web.php | 32 +++--- 7 files changed, 139 insertions(+), 41 deletions(-) rename resources/views/{catalogo.blade.php => catalogo/index.blade.php} (83%) create mode 100644 resources/views/catalogo/show.blade.php diff --git a/app/Http/Controllers/CatalogoController.php b/app/Http/Controllers/CatalogoController.php index 425580d..8991f3e 100644 --- a/app/Http/Controllers/CatalogoController.php +++ b/app/Http/Controllers/CatalogoController.php @@ -10,7 +10,7 @@ class CatalogoController extends Controller /** * Muestra los registros de ventas con buscador y paginación. */ - public function catalogo(Request $request) + public function index(Request $request) { // Consulta base $query = Product::query(); @@ -31,6 +31,11 @@ class CatalogoController extends Controller $products = $query->paginate(12)->withQueryString(); // withQueryString mantiene la búsqueda al cambiar de página // Devuelve la vista - return view('catalogo', compact('products')); + return view('catalogo.index', compact('products')); + } + + public function show(Product $product) + { + return view('catalogo.show', compact('product')); } } diff --git a/resources/views/catalogo.blade.php b/resources/views/catalogo/index.blade.php similarity index 83% rename from resources/views/catalogo.blade.php rename to resources/views/catalogo/index.blade.php index 0d33c3b..4e22482 100644 --- a/resources/views/catalogo.blade.php +++ b/resources/views/catalogo/index.blade.php @@ -7,11 +7,11 @@ @if ($products->count() > 0)
- @foreach ($products as $prod) + @foreach ($products as $product)
{{-- Imagen si existe --}} - @if (!empty($prod->image_path)) - {{ $prod->name }}image_path)) + {{ $product->name }} @else
@@ -20,22 +20,22 @@ @endif {{-- Nombre --}} -

{{ $prod->name }}

+

{{ $product->name }}

{{-- Descripción corta --}}
- @if (!empty($prod->description)) -

{{ $prod->description }}

+ @if (!empty($product->description)) +

{{ $product->description }}

@endif
{{-- Precio --}} - @if (!empty($prod->price)) -

${{ number_format($prod->price, 2, ',', '.') }}

+ @if (!empty($product->price)) +

${{ number_format($product->price, 2, ',', '.') }}

@endif {{-- Botón --}} - Ver + diff --git a/resources/views/catalogo/show.blade.php b/resources/views/catalogo/show.blade.php new file mode 100644 index 0000000..a7834cb --- /dev/null +++ b/resources/views/catalogo/show.blade.php @@ -0,0 +1,105 @@ + + +
+ + + + + Volver al Catálogo + + +
+
+ + +
+ + @if($product->image_path) + {{ $product->name }} + @else +
+ + Sin Imagen +
+ @endif + + +
+ @if($product->type == 'bike') + Bicicleta + @else + Accesorio + @endif +
+ +
+ + +
+ + +
+

SKU: {{ $product->sku ?? 'N/A' }}

+

{{ $product->name }}

+ + +
+ @if($product->stock_quantity > 5) + + En Stock ({{ $product->stock_quantity }} unid.) + @elseif($product->stock_quantity > 0) + + ¡Últimas Unidades! ({{ $product->stock_quantity }}) + @else + + Agotado + @endif +
+
+ + +
+ Precio Contado +
+ ${{ number_format($product->price, 0, ',', '.') }} + ARG +
+

* Consultar financiación en el local.

+
+ + +
+

Descripción

+

+ {{ $product->description ?? 'Sin descripción detallada disponible para este producto. Por favor, acérquese al local para más información.' }} +

+
+ + +
+ + + + + WhatsApp + +
+ +
+
+
+
+
+ {{-- Editar +
+ @csrf + @method('DELETE') + +
--}} \ No newline at end of file diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 5bc8c95..a3a1721 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -66,7 +66,7 @@
  • -
    +
    diff --git a/resources/views/sales/create.blade.php b/resources/views/sales/create.blade.php index d0bdec9..49d437d 100644 --- a/resources/views/sales/create.blade.php +++ b/resources/views/sales/create.blade.php @@ -38,16 +38,8 @@
    - @if(session('success')) -
    - {{ session('success') }} -
    - @endif - @if(session('error')) -
    - {{ session('error') }} -
    - @endif + + @if($errors->any())
      diff --git a/routes/web.php b/routes/web.php index 74304f5..5e6ba69 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,31 +14,27 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Session; Route::get('/',HomeController::class); -Route::get('/catalogo', [CatalogoController::class,'catalogo'])->name('catalogo'); -Route::get('login', function(){ - return view('login'); -})->name('login'); +// Route::get('/catalogo', [CatalogoController::class,'catalogo'])->name('catalogo'); +Route::resource('catalogo', CatalogoController::class)->only(['index', 'show'])->parameters(['catalogo' => 'product']); -Route::post('login', LoginController::class) -->middleware('throttle:5,1') -->name('login.attempt'); - -Route::view('dashboard', 'dashboard')->middleware('auth')->name('dashboard'); - -Route::post('logout', function(){ - Auth::guard('web')->logout(); - - Session::invalidate(); - Session::regenerateToken(); - - return redirect('/'); -})->name('logout'); +Route::get('login', function(){ return view('login'); })->name('login'); +Route::post('login', LoginController::class)->middleware('throttle:5,1')->name('login.attempt'); Route::view('register', 'register')->name('register'); Route::post('register', RegisterController::class)->name('register.store'); +Route::post('logout', function(){ + Auth::guard('web')->logout(); + + Session::invalidate(); + Session::regenerateToken(); + + return redirect('/'); +})->name('logout'); + Route::middleware(['auth'])->group(function () { + Route::view('dashboard', 'dashboard')->name('dashboard'); Route::resource('clients', ClientController::class); Route::resource('productos', ProductosController::class)->parameters([ 'productos' => 'product' From ad852c33d264416b9f3210fc0257e929a514df01 Mon Sep 17 00:00:00 2001 From: BryamE Date: Mon, 12 Jan 2026 13:30:56 -0300 Subject: [PATCH 02/17] cambio ruta --- resources/views/components/ui/carrousel.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/components/ui/carrousel.blade.php b/resources/views/components/ui/carrousel.blade.php index 540b6bd..63d71b1 100644 --- a/resources/views/components/ui/carrousel.blade.php +++ b/resources/views/components/ui/carrousel.blade.php @@ -46,7 +46,7 @@

      From 2cab6bd970cdd5f15e5981b30e5c9d155856024c Mon Sep 17 00:00:00 2001 From: Bryam Cesar <98431514+BryamE@users.noreply.github.com> Date: Tue, 20 Jan 2026 17:59:58 -0300 Subject: [PATCH 03/17] probando, capaz se vuelve --- package-lock.json | 1325 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1325 insertions(+) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9b5e238 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1325 @@ +{ + "name": "ProyectoLauck", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "axios": "^1.8.2", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^2.0.0", + "tailwindcss": "^4.0.0", + "vite": "^7.0.4" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz", + "integrity": "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz", + "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.5.1", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.12" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz", + "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-x64": "4.1.12", + "@tailwindcss/oxide-freebsd-x64": "4.1.12", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-x64-musl": "4.1.12", + "@tailwindcss/oxide-wasm32-wasi": "4.1.12", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz", + "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.12.tgz", + "integrity": "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.12", + "@tailwindcss/oxide": "4.1.12", + "tailwindcss": "4.1.12" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/laravel-vite-plugin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz", + "integrity": "sha512-zQuvzWfUKQu9oNVi1o0RZAJCwhGsdhx4NEOyrVQwJHaWDseGP9tl7XUPLY2T8Cj6+IrZ6lmyxlR1KC8unf3RLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^7.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.18", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", + "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz", + "integrity": "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.50.0", + "@rollup/rollup-android-arm64": "4.50.0", + "@rollup/rollup-darwin-arm64": "4.50.0", + "@rollup/rollup-darwin-x64": "4.50.0", + "@rollup/rollup-freebsd-arm64": "4.50.0", + "@rollup/rollup-freebsd-x64": "4.50.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.0", + "@rollup/rollup-linux-arm-musleabihf": "4.50.0", + "@rollup/rollup-linux-arm64-gnu": "4.50.0", + "@rollup/rollup-linux-arm64-musl": "4.50.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.50.0", + "@rollup/rollup-linux-ppc64-gnu": "4.50.0", + "@rollup/rollup-linux-riscv64-gnu": "4.50.0", + "@rollup/rollup-linux-riscv64-musl": "4.50.0", + "@rollup/rollup-linux-s390x-gnu": "4.50.0", + "@rollup/rollup-linux-x64-gnu": "4.50.0", + "@rollup/rollup-linux-x64-musl": "4.50.0", + "@rollup/rollup-openharmony-arm64": "4.50.0", + "@rollup/rollup-win32-arm64-msvc": "4.50.0", + "@rollup/rollup-win32-ia32-msvc": "4.50.0", + "@rollup/rollup-win32-x64-msvc": "4.50.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", + "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/vite": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.4.tgz", + "integrity": "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-full-reload": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz", + "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "node_modules/vite-plugin-full-reload/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} From b2f2343592092e40b2df7a974f1a0802d38783ed Mon Sep 17 00:00:00 2001 From: Bryam105 Date: Fri, 23 Jan 2026 11:56:27 -0330 Subject: [PATCH 04/17] NUEVO: Implementado PROTOTIPO (v0.1) de taller, creado a partir de modelo kanban con columnas de estados. PENDIENTE DE MUCHOS CAMBIOS... --- app/Http/Controllers/TallerController.php | 77 +++++++++++++++++++ app/Models/Appointment.php | 3 + ...dd_taller_fields_to_appointments_table.php | 35 +++++++++ .../views/components/taller-card.blade.php | 72 +++++++++++++++++ resources/views/dashboard.blade.php | 2 +- resources/views/taller/create.blade.php | 72 +++++++++++++++++ resources/views/taller/index.blade.php | 54 +++++++++++++ routes/web.php | 9 ++- 8 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 app/Http/Controllers/TallerController.php create mode 100644 database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php create mode 100644 resources/views/components/taller-card.blade.php create mode 100644 resources/views/taller/create.blade.php create mode 100644 resources/views/taller/index.blade.php diff --git a/app/Http/Controllers/TallerController.php b/app/Http/Controllers/TallerController.php new file mode 100644 index 0000000..bdaf41a --- /dev/null +++ b/app/Http/Controllers/TallerController.php @@ -0,0 +1,77 @@ +whereIn('status', ['pending', 'in_progress', 'ready']) // Entregados/historial no se muestra, implementar vista aparte + ->orderBy('scheduled_at', 'asc') + ->get(); + + // Agrupado por estado para las columnas + $pending = $jobs->where('status', 'pending'); + $inProgress = $jobs->where('status', 'in_progress'); + $ready = $jobs->where('status', 'ready'); + + return view('taller.index', compact('pending', 'inProgress', 'ready')); + } + + /** + * Formulario para nuevo servicio + */ + public function create() + { + $clients = Client::orderBy('name')->get(); + return view('taller.create', compact('clients')); + } + + /** + * Guardar el servicio + */ + public function store(Request $request) + { + $request->validate([ + 'client_id' => 'required|exists:clients,id', + 'bike_model' => 'required|string', + 'problem_description' => 'required|string', + 'scheduled_at' => 'required|date', + 'estimated_cost' => 'nullable|numeric', + ]); + + // Buscamos el cliente para copiar su teléfono + $client = Client::find($request->client_id); + + Appointment::create([ + 'client_id' => $request->client_id, + 'contact_phone' => $request->contact_phone ?? $client->phone, + 'bike_model' => $request->bike_model, + 'problem_description' => $request->problem_description, + 'parts_needed' => $request->parts_needed, + 'estimated_cost' => $request->estimated_cost, + 'scheduled_at' => $request->scheduled_at, + 'status' => 'pending', // Estado inicial: Pendiente + ]); + + return redirect()->route('taller.index')->with('success', 'Bicicleta ingresada al taller correctamente.'); + } + + /** + * Mover de estado + */ + public function updateStatus(Request $request, Appointment $appointment) + { + $appointment->update(['status' => $request->status]); + return back()->with('success', 'Estado actualizado.'); + } +} \ No newline at end of file diff --git a/app/Models/Appointment.php b/app/Models/Appointment.php index 85d1752..87a42b2 100644 --- a/app/Models/Appointment.php +++ b/app/Models/Appointment.php @@ -11,9 +11,12 @@ class Appointment extends Model protected $fillable = [ 'client_id', + 'contact_phone', 'scheduled_at', 'bike_model', 'problem_description', + 'parts_needed', + 'estimated_cost', 'status', 'notes' ]; diff --git a/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php b/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php new file mode 100644 index 0000000..5f1690e --- /dev/null +++ b/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php @@ -0,0 +1,35 @@ +text('parts_needed')->nullable()->after('problem_description'); + + // Costo Aproximado (Presupuesto) + $table->decimal('estimated_cost', 10, 2)->nullable()->after('parts_needed'); + + // Teléfono de contacto rápido (por si es distinto al del cliente registrado) + $table->string('contact_phone')->nullable()->after('client_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('appointments', function (Blueprint $table) { + $table->dropColumn(['parts_needed', 'estimated_cost', 'contact_phone']); + }); + } +}; diff --git a/resources/views/components/taller-card.blade.php b/resources/views/components/taller-card.blade.php new file mode 100644 index 0000000..757920e --- /dev/null +++ b/resources/views/components/taller-card.blade.php @@ -0,0 +1,72 @@ +@props(['job', 'color']) + +@php + $borderColor = match($color) { + 'red' => 'border-l-red-500', + 'yellow' => 'border-l-yellow-500', + 'neon' => 'border-l-neon-lime', + default => 'border-l-gray-500' + }; +@endphp + +
      + + +
      + {{ $job->client->name }} + {{ $job->scheduled_at->format('d/m H:i') }} +
      + +

      {{ $job->bike_model }}

      +

      {{ $job->problem_description }}

      + + @if($job->estimated_cost) +
      + Presupuesto: + ${{ number_format($job->estimated_cost, 0) }} +
      + @endif + + +
      + + + @if($job->status !== 'pending') + + @csrf @method('PATCH') + + + + @else +
      + @endif + + + + + Avisar + + + + @if($job->status !== 'ready') +
      + @csrf @method('PATCH') + + +
      + @elseif($job->status === 'ready') + +
      + @csrf @method('PATCH') + + +
      + @endif +
      +
      \ No newline at end of file diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index ec8aef8..b786324 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -16,7 +16,7 @@ - + diff --git a/resources/views/taller/create.blade.php b/resources/views/taller/create.blade.php new file mode 100644 index 0000000..94797f5 --- /dev/null +++ b/resources/views/taller/create.blade.php @@ -0,0 +1,72 @@ + + + + +
      +
      + @csrf + + +
      +

      1. Cliente y Contacto

      +
      +
      + + + + Nuevo Cliente +
      +
      + + +
      +
      +
      + + +
      +

      2. Diagnóstico Inicial

      +
      +
      + + +
      + +
      + + +
      + +
      + + +
      +
      +
      + + +
      +

      3. Acuerdo

      +
      +
      + + +
      +
      + + +
      +
      +
      + +
      + +
      +
      +
      +
      \ No newline at end of file diff --git a/resources/views/taller/index.blade.php b/resources/views/taller/index.blade.php new file mode 100644 index 0000000..f7dc5f2 --- /dev/null +++ b/resources/views/taller/index.blade.php @@ -0,0 +1,54 @@ + + + + + +
      + + +
      +
      +

      🔴 Pendientes / A Revisar

      + {{ $pending->count() }} +
      +
      + @foreach($pending as $job) + + @endforeach +
      +
      + + +
      +
      +

      🟡 En Reparación

      + {{ $inProgress->count() }} +
      +
      + @foreach($inProgress as $job) + + @endforeach +
      +
      + + +
      +
      +

      🟢 Listas para Retirar

      + {{ $ready->count() }} +
      +
      + @foreach($ready as $job) + + @endforeach +
      +
      + +
      + +
      \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 5e6ba69..61e25b2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -9,6 +9,7 @@ use App\Http\Controllers\HomeController; use App\Http\Controllers\LoginController; use App\Http\Controllers\ProductosController; use App\Http\Controllers\RegisterController; +use App\Http\Controllers\TallerController; use App\Models\Product; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Session; @@ -40,7 +41,13 @@ Route::middleware(['auth'])->group(function () { 'productos' => 'product' ]); Route::resource('sales', SaleController::class)->only(['index', 'create', 'store', 'show']); - + // Rutas de Taller + Route::controller(TallerController::class)->prefix('taller')->name('taller.')->group(function () { + Route::get('/', 'index')->name('index'); + Route::get('/create', 'create')->name('create'); + Route::post('/', 'store')->name('store'); + Route::patch('/{appointment}/status', 'updateStatus')->name('updateStatus'); + }); }); From 5b5c8b2fc6b301f6a6d1d0c107eba5f6ff65e999 Mon Sep 17 00:00:00 2001 From: Bryam105 Date: Tue, 27 Jan 2026 17:50:53 -0330 Subject: [PATCH 05/17] CAMBIOS: prototito taller v0.2? Ahora con buscador de repuestos --- app/Http/Controllers/TallerController.php | 8 +- resources/views/components/navbar.blade.php | 4 +- resources/views/taller/create.blade.php | 184 +++++++++++++++++--- resources/views/taller/index.blade.php | 8 +- 4 files changed, 173 insertions(+), 31 deletions(-) diff --git a/app/Http/Controllers/TallerController.php b/app/Http/Controllers/TallerController.php index bdaf41a..fd4725a 100644 --- a/app/Http/Controllers/TallerController.php +++ b/app/Http/Controllers/TallerController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers; use App\Models\Appointment; use App\Models\Client; +use App\Models\Product; use Illuminate\Http\Request; class TallerController extends Controller @@ -33,7 +34,12 @@ class TallerController extends Controller public function create() { $clients = Client::orderBy('name')->get(); - return view('taller.create', compact('clients')); + + $products = Product::whereIn('type', ['accessory']) + ->where('stock_quantity', '>', 0) + ->get(['id', 'name', 'price', 'sku', 'stock_quantity']); // Solo campos necesarios + + return view('taller.create', compact('clients', 'products')); } /** diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index a3a1721..a74ff52 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -42,8 +42,8 @@
    • - + Taller
    • diff --git a/resources/views/taller/create.blade.php b/resources/views/taller/create.blade.php index 94797f5..0b718dc 100644 --- a/resources/views/taller/create.blade.php +++ b/resources/views/taller/create.blade.php @@ -2,13 +2,15 @@ -
      -
      +
      + @csrf - +
      -

      1. Cliente y Contacto

      +

      + 1 Cliente +

      @@ -26,47 +28,181 @@
      - +
      -

      2. Diagnóstico Inicial

      +

      + 2 Diagnóstico y Repuestos +

      +
      -
      - - -
      - -
      - - + +
      +
      + + +
      + +
      + + +
      -
      - - + +
      + + + +
      + + + +
      + + +
      +

      Sin repuestos seleccionados

      +
      + + +
      -

      3. Acuerdo

      -
      +

      + 3 Acuerdo +

      +
      -
      - - + +
      + +
      + $ + + +
      +

      * Calculado automáticamente según repuestos. Editable.

      -
      -
      + + + @push('scripts') + + @endpush + \ No newline at end of file diff --git a/resources/views/taller/index.blade.php b/resources/views/taller/index.blade.php index f7dc5f2..94f3a83 100644 --- a/resources/views/taller/index.blade.php +++ b/resources/views/taller/index.blade.php @@ -1,14 +1,14 @@ + -
      - - + -
      +
      From 869d3e1a2c94a603f04bc5e070df77612b8d8b7f Mon Sep 17 00:00:00 2001 From: Bryam105 Date: Wed, 28 Jan 2026 09:44:01 -0330 Subject: [PATCH 06/17] ARREGLOS: Correccion de comentarios y cambio en tablas productos, cambiados los tipos de productos a (bike, accessory, clothing, spare) para manejo futuro de repuestos y carga de datos de excel de productos. --- app/Http/Controllers/CatalogoController.php | 6 +- app/Http/Controllers/ClientController.php | 12 ++-- app/Http/Controllers/HomeController.php | 6 +- app/Http/Controllers/LoginController.php | 6 +- app/Http/Controllers/ProductosController.php | 27 ++++---- app/Http/Controllers/RegisterController.php | 1 - app/Http/Controllers/TallerController.php | 2 +- app/Models/Sale.php | 8 +-- app/Models/SaleDetail.php | 4 +- config/app.php | 2 +- database/factories/ProductFactory.php | 15 ++--- ...5_12_06_063046_add_role_to_users_table.php | 2 +- ...025_12_06_182358_create_products_table.php | 2 +- ...dd_taller_fields_to_appointments_table.php | 2 +- database/seeders/DatabaseSeeder.php | 4 +- resources/views/catalogo/show.blade.php | 16 +++-- resources/views/components/ui/badge.blade.php | 1 + resources/views/productos/create.blade.php | 5 +- resources/views/productos/edit.blade.php | 61 +------------------ resources/views/productos/index.blade.php | 11 ++-- .../views/productos/vistaUsuario.blade.php | 4 +- resources/views/taller/create.blade.php | 2 +- 22 files changed, 76 insertions(+), 123 deletions(-) diff --git a/app/Http/Controllers/CatalogoController.php b/app/Http/Controllers/CatalogoController.php index 8991f3e..82eecb7 100644 --- a/app/Http/Controllers/CatalogoController.php +++ b/app/Http/Controllers/CatalogoController.php @@ -20,11 +20,11 @@ class CatalogoController extends Controller $searchTerm = $request->input('search'); $query->where(function($q) use ($searchTerm) { - $q->where('name', 'like', "%{$searchTerm}%") // Buscar por nombre - ->orWhere('sku', 'like', "%{$searchTerm}%"); // O por código SKU + $q->where('name', 'like', "%{$searchTerm}%") + ->orWhere('sku', 'like', "%{$searchTerm}%"); }); } - $query->whereIn('type', ['bike', 'accessory']);// Filtros para el público + $query->whereIn('type', ['bike', 'accessory', 'clothing', 'spare']); $query->where('stock_quantity', '>', 0); // Solo mostrar si tiene stock // Resultados paginados diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index b456d43..da56e93 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -20,7 +20,6 @@ class ClientController extends Controller return $q->where('name', 'like', "%{$query}%"); }) ->orderBy('name', 'asc') - //->orderBy('create_at', 'asc') ->paginate(10) // Paginamos de a 10 ->withQueryString(); // Mantiene el filtro de búsqueda al cambiar de página @@ -53,13 +52,18 @@ class ClientController extends Controller // 2. Verificamos el origen if ($request->input('origin') === 'sales') { - // Si vino de ventas, volvemos a ventas - // Y pasamos el ID del nuevo cliente para auto-seleccionarlo + // Si vino de ventas, volvemos a ventas con el cliente nuevo return redirect()->route('sales.create', ['new_client_id' => $client->id]) ->with('success', 'Cliente creado. Ya puedes seleccionarlo.'); } + else if ($request->input('origin') === 'taller') { + + // Si vino de taller, volvemos a taller con el cliente nuevo + return redirect()->route('taller.create', ['new_client_id' => $client->id]) + ->with('success', 'Cliente creado. Ya puedes seleccionarlo.'); + } - // 3. Si no, comportamiento normal (volver al index de clientes) + // 3. Si no, vuelvo al index de clientes return redirect()->route('clients.index') ->with('success', 'Cliente creado correctamente.'); } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index fd970d4..24379cc 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -11,9 +11,9 @@ class HomeController extends Controller { // Si tuvieras un campo 'sales_count', podrías usar ->orderByDesc('sales_count') $destacados = Product::where('type', 'bike') - ->latest() // Las más nuevas - ->take(5) - ->get(); + ->latest() // Las más nuevas + ->take(5) + ->get(); // Si no hay bicis, traemos cualquier cosa para que no se rompa if ($destacados->isEmpty()) { diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index d198e28..3397c06 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -14,13 +14,13 @@ class LoginController extends Controller 'email' => ['required', 'email'], 'password' => ['required'], ]); - + if (Auth::attempt($credentials)) { $request->session()->regenerate(); - + return redirect()->intended('dashboard'); } - + return back()->withErrors([ 'email' => 'Los datos no coinciden.', ])->onlyInput('email'); diff --git a/app/Http/Controllers/ProductosController.php b/app/Http/Controllers/ProductosController.php index 92e1c74..c7e37e7 100644 --- a/app/Http/Controllers/ProductosController.php +++ b/app/Http/Controllers/ProductosController.php @@ -4,8 +4,8 @@ namespace App\Http\Controllers; use App\Models\Product; use Illuminate\Http\Request; -use Illuminate\Validation\Rule; // Necesario para validar unicidad al editar -use Illuminate\Support\Facades\Storage; // <--- IMPORTANTE: Agregar esto arriba +use Illuminate\Validation\Rule; +use Illuminate\Support\Facades\Storage; class ProductosController extends Controller { @@ -14,33 +14,30 @@ class ProductosController extends Controller */ public function index(Request $request) { - // Recuperamos lo que el usuario escribió en el buscador (si escribió algo) + // Recuperamos lo escrito en el buscador (si aplica) $query = $request->input('search'); $status = $request->input('stock_status'); // Construimos la consulta $products = Product::query() ->when($query, function ($q) use ($query) { - // Si hay búsqueda, filtra por nombre o SKU + // Filtra por nombre o SKU return $q->where('name', 'like', "%{$query}%") - ->orWhere('sku', 'like', "%{$query}%"); + ->orWhere('sku', 'like', "%{$query}%"); }) ->when($status, function ($q) use ($status) { if ($status === 'low') { // Rojo: Menor o igual a la alerta - return $q->whereColumn('stock_quantity', '<', 'min_stock_alert') - ->where('type', '!=', 'service'); // Ignoramos servicios + return $q->whereColumn('stock_quantity', '<', 'min_stock_alert'); } elseif ($status === 'medium') { - // Amarillo: Mayor a alerta PERO menor o igual a alerta + 2 (margen pequeño) + // Amarillo: Mayor al min y menor o igual al min + 5 return $q->whereColumn('stock_quantity', '>=', 'min_stock_alert') - ->whereRaw('stock_quantity <= (min_stock_alert + 1)') // Ajusta este "+ 5" según tu criterio de "amarillo" - ->where('type', '!=', 'service'); + ->whereRaw('stock_quantity <= (min_stock_alert + 5)'); } elseif ($status === 'ok') { // Verde: Stock saludable - return $q->whereRaw('stock_quantity > (min_stock_alert + 1)') - ->where('type', '!=', 'service'); + return $q->whereRaw('stock_quantity > (min_stock_alert + 5)'); } }) ->orderBy('stock_quantity', 'asc') // Ordenamos primero los que tienen poco stock (Alerta visual) @@ -72,7 +69,7 @@ class ProductosController extends Controller 'cost' => 'nullable|numeric|min:0', // Costo opcional 'stock_quantity' => 'required|integer|min:0', 'min_stock_alert' => 'required|integer|min:0', - 'type' => 'required|in:bike,accessory,service', // Solo permite estos 3 valores + 'type' => 'required|in:bike,accessory,clothing,spare', // Solo permite estos valores 'serial_number' => 'nullable|string|max:100', 'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048', ]); @@ -93,6 +90,7 @@ class ProductosController extends Controller // Para no romper la logica del supplier $validated['suppliers_id'] = 1; + // 3. Creamos el producto Product::create($validated); @@ -103,7 +101,6 @@ class ProductosController extends Controller /** * Muestra el detalle de un producto. - * Usamos Route Model Binding: Laravel busca el ID solo. */ public function show(Product $product) { @@ -132,7 +129,7 @@ class ProductosController extends Controller 'cost' => 'nullable|numeric|min:0', 'stock_quantity' => 'required|integer|min:0', 'min_stock_alert' => 'required|integer|min:0', - 'type' => 'required|in:bike,accessory,service', + 'type' => 'required|in:bike,accessory,clothing,spare', 'serial_number' => 'nullable|string|max:100', 'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048', diff --git a/app/Http/Controllers/RegisterController.php b/app/Http/Controllers/RegisterController.php index 39f4fe1..9185db0 100644 --- a/app/Http/Controllers/RegisterController.php +++ b/app/Http/Controllers/RegisterController.php @@ -18,7 +18,6 @@ class RegisterController extends Controller ]); $userData['password'] = bcrypt($userData['password']); - $user = User::create($userData); Auth::login($user); diff --git a/app/Http/Controllers/TallerController.php b/app/Http/Controllers/TallerController.php index fd4725a..2ff9ab0 100644 --- a/app/Http/Controllers/TallerController.php +++ b/app/Http/Controllers/TallerController.php @@ -35,7 +35,7 @@ class TallerController extends Controller { $clients = Client::orderBy('name')->get(); - $products = Product::whereIn('type', ['accessory']) + $products = Product::whereIn('type', ['accessory','spare']) ->where('stock_quantity', '>', 0) ->get(['id', 'name', 'price', 'sku', 'stock_quantity']); // Solo campos necesarios diff --git a/app/Models/Sale.php b/app/Models/Sale.php index 67f7dcb..7057337 100644 --- a/app/Models/Sale.php +++ b/app/Models/Sale.php @@ -8,22 +8,22 @@ class Sale extends Model { //use HasFactory; - // Permitimos asignación masiva para poder guardar rápido + // Permitimos asignación masiva protected $guarded = []; - // Relación 1: Una venta la realiza un Usuario (User) + // Rel 1: Una venta la hace un Usuario public function user() { return $this->belongsTo(User::class); } - // Relación 2: Una venta pertenece a un Cliente (User) + // Rel 2: Una venta pertenece a un Cliente public function client() { return $this->belongsTo(Client::class); } - // Relación 3: Una venta tiene muchos items o detalles + // Rel 3: Una venta tiene muchos items public function details() { return $this->hasMany(SaleDetail::class); diff --git a/app/Models/SaleDetail.php b/app/Models/SaleDetail.php index 597eff4..94dcac5 100644 --- a/app/Models/SaleDetail.php +++ b/app/Models/SaleDetail.php @@ -9,13 +9,13 @@ class SaleDetail extends Model //use HasFactory; protected $guarded = []; - // este detalle pertenece a una Venta específica + // Rel 1: Este detalle pertenece a una Venta específica public function sale() { return $this->belongsTo(Sale::class); } - // este detalle corresponde a un Producto + // Rel 2: Este detalle pertenece a un Producto public function product() { return $this->belongsTo(Product::class); diff --git a/config/app.php b/config/app.php index 423eed5..25f728f 100644 --- a/config/app.php +++ b/config/app.php @@ -78,7 +78,7 @@ return [ | */ - 'locale' => env('APP_LOCALE', 'en'), + 'locale' => env('APP_LOCALE', 'es'), 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php index 2ab688a..2ce0624 100644 --- a/database/factories/ProductFactory.php +++ b/database/factories/ProductFactory.php @@ -19,23 +19,24 @@ class ProductFactory extends Factory $brands = ['Shimano', 'Venzo', 'Trek', 'Specialized', 'Maxxis', 'Sram', 'Raleigh']; $bikeModels = ['Loki', 'Marlin 5', 'Chisel', 'Talon 3', 'Aspect 950']; $accessories = ['Guantes Grip', 'Calco Reflectora', 'Coderas', 'Rodilleras', 'Casco MTB', 'Luz Delantera USB', 'Cubierta Kevlar']; - // PARA DESPUES $spareParts = ['Manubrio', 'Pedales Aluminio', 'Cámara 29"', 'Rayos x50', 'Disco de Freno', 'Cable de Freno', 'Asiento Goma', 'Cadena 9v']; - - $type = $this->faker->randomElement(['bike', 'accessory', 'service']); + $cats = ['Pro', 'Basic', 'Comp', 'Elite']; + $type = $this->faker->randomElement(['bike', 'accessory', 'clothing', 'spare']); // Generar nombre según el tipo if ($type === 'bike') { $name = $this->faker->randomElement($brands) . ' ' . $this->faker->randomElement($bikeModels); } elseif ($type === 'accessory') { - $name = $this->faker->randomElement($accessories) . ' ' . $this->faker->randomElement(['Pro', 'Basic', 'Comp', 'Elite']); + $name = $this->faker->randomElement($accessories) .' '. $this->faker->randomElement($cats); + } elseif ($type === 'clothing') { + $name = $this->faker->randomElement(['Remera', 'Pantalon', 'Casco', 'Coderas', 'Guantes']).' '.$this->faker->randomElement($cats); } else { - $name = $this->faker->randomElement(['Service General', 'Ajuste Cambios', 'Centrado de Rueda', 'Lavado y Engrase']); + $name = $this->faker->randomElement($spareParts) .' '. $this->faker->randomElement($cats); } // Lógica de Precios - $price = $this->faker->numberBetween(5000, 800000); - $cost = $price * $this->faker->randomFloat(2, 0.5, 0.7); + $price = $this->faker->numberBetween(5000, 200000); + $cost = $price * $this->faker->randomFloat(2, 0.1, 0.8); return [ 'name' => $name, diff --git a/database/migrations/2025_12_06_063046_add_role_to_users_table.php b/database/migrations/2025_12_06_063046_add_role_to_users_table.php index 78352f6..de5bce5 100644 --- a/database/migrations/2025_12_06_063046_add_role_to_users_table.php +++ b/database/migrations/2025_12_06_063046_add_role_to_users_table.php @@ -12,7 +12,7 @@ return new class extends Migration public function up(): void { Schema::table('users', function (Blueprint $table) { - $table->enum('role', ['admin', 'employee'])->default('employee')->after('email'); + $table->enum('role', ['admin', 'employee', 'guest'])->default('guest')->after('email'); }); } diff --git a/database/migrations/2025_12_06_182358_create_products_table.php b/database/migrations/2025_12_06_182358_create_products_table.php index ea3aba5..69f735f 100644 --- a/database/migrations/2025_12_06_182358_create_products_table.php +++ b/database/migrations/2025_12_06_182358_create_products_table.php @@ -23,7 +23,7 @@ return new class extends Migration $table->integer('stock_quantity')->default(0); $table->integer('min_stock_alert'); // Alerta - $table->enum('type', ['bike', 'accessory', 'service']); + $table->enum('type', ['bike', 'accessory', 'clothing', 'spare']); // Tipo de producto $table->string('serial_number')->nullable(); // Solo para bicis $table->foreignId('suppliers_id')->constrained()->default(1); diff --git a/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php b/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php index 5f1690e..3239982 100644 --- a/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php +++ b/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php @@ -15,7 +15,7 @@ return new class extends Migration // Repuestos estimados (texto simple por ahora) $table->text('parts_needed')->nullable()->after('problem_description'); - // Costo Aproximado (Presupuesto) + // Costo (Presupuesto) $table->decimal('estimated_cost', 10, 2)->nullable()->after('parts_needed'); // Teléfono de contacto rápido (por si es distinto al del cliente registrado) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 925158c..dc52494 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -16,7 +16,7 @@ class DatabaseSeeder extends Seeder { //Crear el Super Admin User::factory()->create([ - 'name' => 'Jose Admin', + 'name' => 'Sergio Lauck', 'email' => 'admin@lauck.com', 'password' => bcrypt('password'), // Cambiar en producción 'role' => 'admin', @@ -60,7 +60,7 @@ class DatabaseSeeder extends Seeder 'suppliers_id' => 1 ]); - // Generar 10 productos aleatorios más + // Generar 50 productos aleatorios más Product::factory(50)->create(); // Clientes y Turnos diff --git a/resources/views/catalogo/show.blade.php b/resources/views/catalogo/show.blade.php index a7834cb..4e94d0c 100644 --- a/resources/views/catalogo/show.blade.php +++ b/resources/views/catalogo/show.blade.php @@ -27,11 +27,17 @@
      - @if($product->type == 'bike') - Bicicleta - @else - Accesorio - @endif + + @if($product->type == 'bike') + Bicicleta + @elseif($product->type == 'clothing') + Indumentaria + @elseif($product->type == 'spare') + Repuesto + @else + Accesorio + @endif +
      diff --git a/resources/views/components/ui/badge.blade.php b/resources/views/components/ui/badge.blade.php index e455129..5e60593 100644 --- a/resources/views/components/ui/badge.blade.php +++ b/resources/views/components/ui/badge.blade.php @@ -4,6 +4,7 @@ $colors = [ 'gray' => 'bg-gray-700 text-gray-300', 'red' => 'bg-red-900/50 text-red-300 border border-red-800', + 'blue' => 'bg-blue-900/50 text-blue-300 border border-blue-800', 'green' => 'bg-green-900/50 text-green-300 border border-green-800', 'yellow' => 'bg-yellow-900/50 text-yellow-300 border border-yellow-800', 'neon' => 'bg-[#ccff00]/10 text-[#ccff00] border border-[#ccff00]/50', diff --git a/resources/views/productos/create.blade.php b/resources/views/productos/create.blade.php index 02f9d6b..1fe4560 100644 --- a/resources/views/productos/create.blade.php +++ b/resources/views/productos/create.blade.php @@ -26,9 +26,10 @@
      diff --git a/resources/views/productos/edit.blade.php b/resources/views/productos/edit.blade.php index afc80e7..e76918a 100644 --- a/resources/views/productos/edit.blade.php +++ b/resources/views/productos/edit.blade.php @@ -1,59 +1,3 @@ -{{-- - Lauck - Editar - Editar - {{$product->nombre}} - -
      -
      - @csrf - @method('PUT') -
      - - -
      -
      - - -
      -
      - - -
      -
      - - -
      -
      -
      - - -
      -
      - - -
      -
      -
      -
      - - -
      -
      - precio}} name="precio" id="precio" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" step="0.01"/> - -
      -
      -
      - - - Volver - -
      -
      -
      - -
      --}} @@ -76,9 +20,10 @@
      diff --git a/resources/views/productos/index.blade.php b/resources/views/productos/index.blade.php index bd97bde..3c27435 100644 --- a/resources/views/productos/index.blade.php +++ b/resources/views/productos/index.blade.php @@ -59,18 +59,17 @@ @if($product->type === 'bike') Bicicleta - @elseif($product->type === 'accessory') Accesorio - @else Servicio @endif + @elseif($product->type === 'clothing') Indumentaria + @elseif($product->type === 'spare') Repuesto + @else Accesorio @endif ${{ number_format($product->price, 2) }} - @if($product->type === 'service') - - - @elseif($product->stock_quantity < $product->min_stock_alert) + @if($product->stock_quantity < $product->min_stock_alert) {{ $product->stock_quantity }} - @elseif($product->stock_quantity <= $product->min_stock_alert+1) + @elseif($product->stock_quantity <= $product->min_stock_alert+5) {{ $product->stock_quantity }} @else {{ $product->stock_quantity }} diff --git a/resources/views/productos/vistaUsuario.blade.php b/resources/views/productos/vistaUsuario.blade.php index fbcbba2..10b5d82 100644 --- a/resources/views/productos/vistaUsuario.blade.php +++ b/resources/views/productos/vistaUsuario.blade.php @@ -1,9 +1,9 @@
      Volver a catalogo -

      Titulo: {{ $producto->nombre }}

      +

      Titulo: {{ $producto->name }}

      - Categoria: {{ $producto->Categoria }} + Categoria: {{ $producto->type }}

      {{ $producto->content }} diff --git a/resources/views/taller/create.blade.php b/resources/views/taller/create.blade.php index 0b718dc..70fa74e 100644 --- a/resources/views/taller/create.blade.php +++ b/resources/views/taller/create.blade.php @@ -60,7 +60,7 @@

      -
      +

      Sin repuestos seleccionados

      From 0c639cb7b9ae2145b6598f5fe3e195171816cf5a Mon Sep 17 00:00:00 2001 From: Bryam105 Date: Wed, 28 Jan 2026 10:17:37 -0330 Subject: [PATCH 07/17] ARREGLOS: Errores de disposicion de errores corregidos. --- resources/views/sales/create.blade.php | 25 ++++++++++---------- resources/views/taller/create.blade.php | 31 ++++++++++++++++++++++--- resources/views/taller/index.blade.php | 7 ++++++ 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/resources/views/sales/create.blade.php b/resources/views/sales/create.blade.php index 49d437d..50e6630 100644 --- a/resources/views/sales/create.blade.php +++ b/resources/views/sales/create.blade.php @@ -36,20 +36,19 @@ + + + @if($errors->any()) +
      +
        + @foreach($errors->all() as $error) +
      • {{ $error }}
      • + @endforeach +
      +
      + @endif +
      - - - - @if($errors->any()) -
      -
        - @foreach($errors->all() as $error) -
      • {{ $error }}
      • - @endforeach -
      -
      - @endif -
      @csrf diff --git a/resources/views/taller/create.blade.php b/resources/views/taller/create.blade.php index 70fa74e..9d54609 100644 --- a/resources/views/taller/create.blade.php +++ b/resources/views/taller/create.blade.php @@ -2,6 +2,18 @@ + + + @if($errors->any()) +
      +
        + @foreach($errors->all() as $error) +
      • {{ $error }}
      • + @endforeach +
      +
      + @endif +
      @csrf @@ -19,11 +31,11 @@ @endforeach - + Nuevo Cliente + + Nuevo Cliente
      - - + +
      @@ -106,9 +118,22 @@ @push('scripts') +``` + +#### 2. Confirmaciones Elegantes +```javascript +// Usar SweetAlert2 +Swal.fire({ + title: '¿Eliminar producto?', + text: "Esta acción no se puede revertir", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#ccff00', + cancelButtonColor: '#d33', + confirmButtonText: 'Sí, eliminar', + cancelButtonText: 'Cancelar' +}).then((result) => { + if (result.isConfirmed) { + // Ejecutar eliminación + } +}); +``` + +#### 3. Toast Notifications +```javascript +// Mensajes no intrusivos +const Toast = Swal.mixin({ + toast: true, + position: 'top-end', + showConfirmButton: false, + timer: 3000 +}); + +Toast.fire({ + icon: 'success', + title: 'Producto guardado' +}); +``` + +#### 4. Validación en Tiempo Real +```javascript +$('input[name="sku"]').on('blur', function() { + const sku = $(this).val(); + + $.get(`/api/products/check-sku/${sku}`, function(data) { + if (data.exists) { + // Mostrar error + $(this).addClass('border-red-500'); + } + }); +}); +``` + +### Testing + +#### 1. Tests de Feature +```php +// tests/Feature/ProductTest.php +public function test_admin_can_create_product() +{ + $admin = User::factory()->create(['role' => 'admin']); + + $this->actingAs($admin) + ->post('/productos', [ + 'name' => 'Test Product', + 'price' => 100, + 'stock_quantity' => 10, + 'min_stock_alert' => 5, + 'type' => 'accessory' + ]) + ->assertRedirect('/productos') + ->assertSessionHas('success'); + + $this->assertDatabaseHas('products', [ + 'name' => 'Test Product' + ]); +} + +public function test_guest_cannot_create_product() +{ + $response = $this->post('/productos', []); + $response->assertRedirect('/login'); +} +``` + +#### 2. Tests Unitarios +```php +// tests/Unit/ProductTest.php +public function test_product_has_low_stock() +{ + $product = Product::factory()->create([ + 'stock_quantity' => 3, + 'min_stock_alert' => 5 + ]); + + $this->assertTrue($product->hasLowStock()); +} +``` + +#### 3. Browser Tests (Dusk) +```php +// tests/Browser/PosTest.php +public function test_can_complete_sale() +{ + $this->browse(function (Browser $browser) { + $browser->loginAs(User::find(1)) + ->visit('/sales/create') + ->select('items[0][product_id]', 1) + ->type('items[0][quantity]', 2) + ->press('Confirmar Venta') + ->assertPathIs('/sales/1'); + }); +} +``` + +## 🐛 Issues Conocidos y Soluciones + +### 1. Select2 no se inicializa en filas dinámicas +**Problema**: Al agregar nueva fila en POS, Select2 no funciona. + +**Solución actual**: +```javascript +$('.select2-product').select2('destroy'); // Destruir +const newRow = firstRow.clone(); // Clonar +initSelect2(); // Re-inicializar todos +``` + +**Mejora sugerida**: +```javascript +function addRow() { + const newRow = firstRow.clone(); + container.append(newRow); + newRow.find('.select2-product').select2({ /* config */ }); +} +``` + +### 2. Imágenes no se muestran después de upload +**Problema**: Falta ejecutar `storage:link`. + +**Solución**: +```bash +php artisan storage:link +``` + +### 3. Error en eliminación de cliente con ventas +**Problema**: Violación de constraint de foreign key. + +**Solución implementada**: Try-catch con mensaje amigable. + +**Mejora**: Soft deletes +```php +// En migración +$table->softDeletes(); + +// En modelo +use SoftDeletes; +``` + +### 4. Problema de concurrencia en stock +**Escenario**: Dos ventas simultáneas del mismo producto. + +**Solución recomendada**: Optimistic locking +```php +// En modelo Product +protected $casts = [ + 'version' => 'integer' +]; + +// Al actualizar +Product::where('id', $id) + ->where('version', $currentVersion) + ->update([ + 'stock_quantity' => $newStock, + 'version' => $currentVersion + 1 + ]); +``` + +## 📝 Convenciones y Estándares + +### Nombres +- **Variables**: `camelCase` (`$productoActual`) +- **Funciones**: `camelCase` (`getUserData()`) +- **Clases**: `PascalCase` (`ProductController`) +- **Tablas**: `snake_case` plural (`products`, `sale_details`) +- **Rutas**: `kebab-case` (`/productos/crear`) + +### Commits +``` +Add: Nueva funcionalidad +Fix: Corrección de bugs +Update: Actualización de código existente +Refactor: Refactorización sin cambios funcionales +Docs: Cambios en documentación +Style: Formato/estilo (sin cambios lógicos) +Test: Agregar/modificar tests +Chore: Tareas de mantenimiento +``` + +### Branches +``` +main/master - Producción +develop - Desarrollo +feature/nombre - Nueva funcionalidad +fix/nombre - Corrección de bugs +hotfix/nombre - Corrección urgente en producción +``` + +## 🤝 Contribuir + +### Proceso +1. Fork del proyecto +2. Crear rama: `git checkout -b feature/AmazingFeature` +3. Commit: `git commit -m 'Add: nueva funcionalidad'` +4. Push: `git push origin feature/AmazingFeature` +5. Abrir Pull Request + +### Checklist antes del PR +- [ ] Código sigue las convenciones +- [ ] Tests pasan (`php artisan test`) +- [ ] No hay errores de linting (`composer run lint`) +- [ ] Documentación actualizada +- [ ] Migraciones incluidas si aplica +- [ ] Seeders actualizados si aplica + +## 📄 Licencia + +Este proyecto está bajo la Licencia MIT. Ver archivo `LICENSE` para detalles. + +## 🙏 Agradecimientos + +- [Laravel](https://laravel.com) - El framework PHP para artesanos web +- [Tailwind CSS](https://tailwindcss.com) - Framework CSS utility-first +- [Select2](https://select2.org) - Selectores avanzados +- [jQuery](https://jquery.com) - Manipulación DOM simplificada +- Comunidad de Laravel por su excelente documentación + +## 📞 Contacto + +**Bicicletería Lauck** +- Dirección: Av. Francisco Ramírez 1389, Paraná, Entre Ríos +- Teléfono: 343 422-0103 +- Web: En desarrollo + +--- + +**Versión**: 2.0.0 +**Estado**: En desarrollo activo 🚧 +**Última actualización**: Enero 2026 + +**Desarrollado con ❤️ para Bicicletería Lauck** \ No newline at end of file diff --git a/resources/views/catalogo/index.blade.php b/resources/views/catalogo/index.blade.php index 4e22482..6219ae8 100644 --- a/resources/views/catalogo/index.blade.php +++ b/resources/views/catalogo/index.blade.php @@ -6,7 +6,7 @@
      @if ($products->count() > 0) -
      +
      @foreach ($products as $product)
      {{-- Imagen si existe --}} diff --git a/resources/views/components/appc.blade.php b/resources/views/components/appc.blade.php index a054ae7..c1e4eaf 100644 --- a/resources/views/components/appc.blade.php +++ b/resources/views/components/appc.blade.php @@ -1,3 +1,6 @@ + +{{-- +DEPRECADO: Este componente ha sido reemplazado por layout.blade.php para una mejor organización. @props(['auth' => false]) @@ -120,4 +123,4 @@ }); - \ No newline at end of file + --}} \ No newline at end of file From cb7895e4a5fb8c5aee37ead6c1cedf8f6c6ce1ba Mon Sep 17 00:00:00 2001 From: Bryam105 Date: Fri, 30 Jan 2026 10:32:18 -0330 Subject: [PATCH 11/17] Docs: Cambio en el README --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6880eba..f1b5d35 100644 --- a/README.md +++ b/README.md @@ -1053,19 +1053,18 @@ Chore: Tareas de mantenimiento ### Branches ``` main/master - Producción -develop - Desarrollo -feature/nombre - Nueva funcionalidad -fix/nombre - Corrección de bugs -hotfix/nombre - Corrección urgente en producción +Bryam - Desarrollo particular de Bryam +Lucas - Desarrollo particular de Lucas +Giane - Desarrollo particular de Giane ``` ## 🤝 Contribuir ### Proceso 1. Fork del proyecto -2. Crear rama: `git checkout -b feature/AmazingFeature` +2. Crear rama: `git checkout -b (Tu Nombre)` 3. Commit: `git commit -m 'Add: nueva funcionalidad'` -4. Push: `git push origin feature/AmazingFeature` +4. Push: `git push origin (Tu Nombre)` 5. Abrir Pull Request ### Checklist antes del PR From 1b83a362a9a1e13caf8ea8c54663114da72be9f4 Mon Sep 17 00:00:00 2001 From: Bryam105 Date: Fri, 30 Jan 2026 10:37:10 -0330 Subject: [PATCH 12/17] Docs: Creado documento RECOMENDACIONES, modificado README --- README.md | 359 +-------------------------------------------- RECOMENDACIONES.md | 356 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 357 insertions(+), 358 deletions(-) create mode 100644 RECOMENDACIONES.md diff --git a/README.md b/README.md index f1b5d35..779f1a0 100644 --- a/README.md +++ b/README.md @@ -610,363 +610,6 @@ Dashboard mejorado con: - Reportes consolidados ``` -## 🔧 Mejoras Técnicas Recomendadas - -### Optimización de Rendimiento - -#### 1. Implementar Caché Estratégico -```php -// ProductosController.php -public function index(Request $request) -{ - $cacheKey = 'products.' . md5(serialize($request->all())); - - $products = Cache::remember($cacheKey, 300, function () use ($request) { - return Product::query() - ->when($request->search, fn($q, $term) => - $q->where('name', 'like', "%{$term}%") - ) - ->paginate(10); - }); -} - -// Limpiar caché al modificar productos -Cache::forget('products.*'); -``` - -#### 2. Eager Loading para Relaciones -```php -// Evitar N+1 queries -$sales = Sale::with(['client', 'details.product']) - ->latest() - ->paginate(15); - -// En lugar de: -$sales = Sale::all(); // Genera queries adicionales -``` - -#### 3. Índices de Base de Datos -```php -// En migraciones -Schema::table('products', function (Blueprint $table) { - $table->index('sku'); - $table->index('type'); - $table->index(['stock_quantity', 'min_stock_alert']); -}); - -Schema::table('sales', function (Blueprint $table) { - $table->index('created_at'); - $table->index(['client_id', 'created_at']); -}); -``` - -#### 4. Paginación con Cursor -```php -// Para datasets grandes -$products = Product::latest()->cursorPaginate(20); -``` - -### Refactorización de Código - -#### 1. Services para Lógica de Negocio -```php -// app/Services/SaleService.php -class SaleService -{ - public function processSale(array $data): Sale - { - return DB::transaction(function () use ($data) { - $sale = Sale::create([...]); - - foreach ($data['items'] as $item) { - $this->addItem($sale, $item); - } - - return $sale; - }); - } - - private function addItem(Sale $sale, array $item): void - { - $product = Product::findOrFail($item['product_id']); - - if ($product->stock_quantity < $item['quantity']) { - throw new InsufficientStockException(); - } - - SaleDetail::create([...]); - $product->decrement('stock_quantity', $item['quantity']); - } -} -``` - -#### 2. Form Request Validation -```php -// app/Http/Requests/StoreSaleRequest.php -class StoreSaleRequest extends FormRequest -{ - public function rules(): array - { - return [ - 'client_id' => 'nullable|exists:clients,id', - 'payment_method' => 'required|string', - 'items' => 'required|array|min:1', - 'items.*.product_id' => 'required|exists:products,id', - 'items.*.quantity' => 'required|integer|min:1', - ]; - } - - public function messages(): array - { - return [ - 'items.required' => 'Debe agregar al menos un producto.', - 'items.*.quantity.min' => 'La cantidad debe ser mayor a 0.', - ]; - } -} - -// En el controlador -public function store(StoreSaleRequest $request) -{ - // $request ya está validado -} -``` - -#### 3. Repository Pattern -```php -// app/Repositories/ProductRepository.php -interface ProductRepositoryInterface -{ - public function search(string $term): Collection; - public function lowStock(): Collection; - public function byType(string $type): Collection; -} - -class ProductRepository implements ProductRepositoryInterface -{ - public function search(string $term): Collection - { - return Product::where('name', 'like', "%{$term}%") - ->orWhere('sku', 'like', "%{$term}%") - ->get(); - } -} - -// Registrar en AppServiceProvider -$this->app->bind( - ProductRepositoryInterface::class, - ProductRepository::class -); -``` - -#### 4. Policies para Autorización -```php -// app/Policies/ProductPolicy.php -class ProductPolicy -{ - public function viewAny(User $user): bool - { - return in_array($user->role, ['admin', 'employee']); - } - - public function create(User $user): bool - { - return $user->role === 'admin'; - } - - public function update(User $user, Product $product): bool - { - return $user->role === 'admin'; - } - - public function delete(User $user, Product $product): bool - { - return $user->role === 'admin'; - } -} - -// En controlador -$this->authorize('create', Product::class); -``` - -### Mejoras de Seguridad - -#### 1. Rate Limiting Específico -```php -// routes/web.php -Route::post('/sales', [SaleController::class, 'store']) - ->middleware(['auth', 'throttle:sales']); - -// app/Providers/RouteServiceProvider.php -RateLimiter::for('sales', function (Request $request) { - return Limit::perMinute(10)->by($request->user()?->id); -}); -``` - -#### 2. Sanitización de Inputs -```php -// En Form Request -protected function prepareForValidation() -{ - $this->merge([ - 'name' => strip_tags($this->name), - 'description' => Purifier::clean($this->description), - ]); -} -``` - -#### 3. Protección CSRF Explícita -```php -// Verificar en AJAX -$.ajaxSetup({ - headers: { - 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') - } -}); -``` - -#### 4. Validación de Imágenes -```php -'image' => [ - 'nullable', - 'image', - 'mimes:jpeg,png,jpg,webp', - 'max:2048', // 2MB - 'dimensions:min_width=100,min_height=100,max_width=2000,max_height=2000' -] -``` - -### Mejoras de UI/UX - -#### 1. Loading States -```blade - - - -``` - -#### 2. Confirmaciones Elegantes -```javascript -// Usar SweetAlert2 -Swal.fire({ - title: '¿Eliminar producto?', - text: "Esta acción no se puede revertir", - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#ccff00', - cancelButtonColor: '#d33', - confirmButtonText: 'Sí, eliminar', - cancelButtonText: 'Cancelar' -}).then((result) => { - if (result.isConfirmed) { - // Ejecutar eliminación - } -}); -``` - -#### 3. Toast Notifications -```javascript -// Mensajes no intrusivos -const Toast = Swal.mixin({ - toast: true, - position: 'top-end', - showConfirmButton: false, - timer: 3000 -}); - -Toast.fire({ - icon: 'success', - title: 'Producto guardado' -}); -``` - -#### 4. Validación en Tiempo Real -```javascript -$('input[name="sku"]').on('blur', function() { - const sku = $(this).val(); - - $.get(`/api/products/check-sku/${sku}`, function(data) { - if (data.exists) { - // Mostrar error - $(this).addClass('border-red-500'); - } - }); -}); -``` - -### Testing - -#### 1. Tests de Feature -```php -// tests/Feature/ProductTest.php -public function test_admin_can_create_product() -{ - $admin = User::factory()->create(['role' => 'admin']); - - $this->actingAs($admin) - ->post('/productos', [ - 'name' => 'Test Product', - 'price' => 100, - 'stock_quantity' => 10, - 'min_stock_alert' => 5, - 'type' => 'accessory' - ]) - ->assertRedirect('/productos') - ->assertSessionHas('success'); - - $this->assertDatabaseHas('products', [ - 'name' => 'Test Product' - ]); -} - -public function test_guest_cannot_create_product() -{ - $response = $this->post('/productos', []); - $response->assertRedirect('/login'); -} -``` - -#### 2. Tests Unitarios -```php -// tests/Unit/ProductTest.php -public function test_product_has_low_stock() -{ - $product = Product::factory()->create([ - 'stock_quantity' => 3, - 'min_stock_alert' => 5 - ]); - - $this->assertTrue($product->hasLowStock()); -} -``` - -#### 3. Browser Tests (Dusk) -```php -// tests/Browser/PosTest.php -public function test_can_complete_sale() -{ - $this->browse(function (Browser $browser) { - $browser->loginAs(User::find(1)) - ->visit('/sales/create') - ->select('items[0][product_id]', 1) - ->type('items[0][quantity]', 2) - ->press('Confirmar Venta') - ->assertPathIs('/sales/1'); - }); -} -``` - ## 🐛 Issues Conocidos y Soluciones ### 1. Select2 no se inicializa en filas dinámicas @@ -1096,7 +739,7 @@ Este proyecto está bajo la Licencia MIT. Ver archivo `LICENSE` para detalles. --- -**Versión**: 2.0.0 +**Versión**: 0.2.0 **Estado**: En desarrollo activo 🚧 **Última actualización**: Enero 2026 diff --git a/RECOMENDACIONES.md b/RECOMENDACIONES.md new file mode 100644 index 0000000..82eb622 --- /dev/null +++ b/RECOMENDACIONES.md @@ -0,0 +1,356 @@ +## 🔧 Mejoras Técnicas Recomendadas + +### Optimización de Rendimiento + +#### 1. Implementar Caché Estratégico +```php +// ProductosController.php +public function index(Request $request) +{ + $cacheKey = 'products.' . md5(serialize($request->all())); + + $products = Cache::remember($cacheKey, 300, function () use ($request) { + return Product::query() + ->when($request->search, fn($q, $term) => + $q->where('name', 'like', "%{$term}%") + ) + ->paginate(10); + }); +} + +// Limpiar caché al modificar productos +Cache::forget('products.*'); +``` + +#### 2. Eager Loading para Relaciones +```php +// Evitar N+1 queries +$sales = Sale::with(['client', 'details.product']) + ->latest() + ->paginate(15); + +// En lugar de: +$sales = Sale::all(); // Genera queries adicionales +``` + +#### 3. Índices de Base de Datos +```php +// En migraciones +Schema::table('products', function (Blueprint $table) { + $table->index('sku'); + $table->index('type'); + $table->index(['stock_quantity', 'min_stock_alert']); +}); + +Schema::table('sales', function (Blueprint $table) { + $table->index('created_at'); + $table->index(['client_id', 'created_at']); +}); +``` + +#### 4. Paginación con Cursor +```php +// Para datasets grandes +$products = Product::latest()->cursorPaginate(20); +``` + +### Refactorización de Código + +#### 1. Services para Lógica de Negocio +```php +// app/Services/SaleService.php +class SaleService +{ + public function processSale(array $data): Sale + { + return DB::transaction(function () use ($data) { + $sale = Sale::create([...]); + + foreach ($data['items'] as $item) { + $this->addItem($sale, $item); + } + + return $sale; + }); + } + + private function addItem(Sale $sale, array $item): void + { + $product = Product::findOrFail($item['product_id']); + + if ($product->stock_quantity < $item['quantity']) { + throw new InsufficientStockException(); + } + + SaleDetail::create([...]); + $product->decrement('stock_quantity', $item['quantity']); + } +} +``` + +#### 2. Form Request Validation +```php +// app/Http/Requests/StoreSaleRequest.php +class StoreSaleRequest extends FormRequest +{ + public function rules(): array + { + return [ + 'client_id' => 'nullable|exists:clients,id', + 'payment_method' => 'required|string', + 'items' => 'required|array|min:1', + 'items.*.product_id' => 'required|exists:products,id', + 'items.*.quantity' => 'required|integer|min:1', + ]; + } + + public function messages(): array + { + return [ + 'items.required' => 'Debe agregar al menos un producto.', + 'items.*.quantity.min' => 'La cantidad debe ser mayor a 0.', + ]; + } +} + +// En el controlador +public function store(StoreSaleRequest $request) +{ + // $request ya está validado +} +``` + +#### 3. Repository Pattern +```php +// app/Repositories/ProductRepository.php +interface ProductRepositoryInterface +{ + public function search(string $term): Collection; + public function lowStock(): Collection; + public function byType(string $type): Collection; +} + +class ProductRepository implements ProductRepositoryInterface +{ + public function search(string $term): Collection + { + return Product::where('name', 'like', "%{$term}%") + ->orWhere('sku', 'like', "%{$term}%") + ->get(); + } +} + +// Registrar en AppServiceProvider +$this->app->bind( + ProductRepositoryInterface::class, + ProductRepository::class +); +``` + +#### 4. Policies para Autorización +```php +// app/Policies/ProductPolicy.php +class ProductPolicy +{ + public function viewAny(User $user): bool + { + return in_array($user->role, ['admin', 'employee']); + } + + public function create(User $user): bool + { + return $user->role === 'admin'; + } + + public function update(User $user, Product $product): bool + { + return $user->role === 'admin'; + } + + public function delete(User $user, Product $product): bool + { + return $user->role === 'admin'; + } +} + +// En controlador +$this->authorize('create', Product::class); +``` + +### Mejoras de Seguridad + +#### 1. Rate Limiting Específico +```php +// routes/web.php +Route::post('/sales', [SaleController::class, 'store']) + ->middleware(['auth', 'throttle:sales']); + +// app/Providers/RouteServiceProvider.php +RateLimiter::for('sales', function (Request $request) { + return Limit::perMinute(10)->by($request->user()?->id); +}); +``` + +#### 2. Sanitización de Inputs +```php +// En Form Request +protected function prepareForValidation() +{ + $this->merge([ + 'name' => strip_tags($this->name), + 'description' => Purifier::clean($this->description), + ]); +} +``` + +#### 3. Protección CSRF Explícita +```php +// Verificar en AJAX +$.ajaxSetup({ + headers: { + 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') + } +}); +``` + +#### 4. Validación de Imágenes +```php +'image' => [ + 'nullable', + 'image', + 'mimes:jpeg,png,jpg,webp', + 'max:2048', // 2MB + 'dimensions:min_width=100,min_height=100,max_width=2000,max_height=2000' +] +``` + +### Mejoras de UI/UX + +#### 1. Loading States +```blade + + + +``` + +#### 2. Confirmaciones Elegantes +```javascript +// Usar SweetAlert2 +Swal.fire({ + title: '¿Eliminar producto?', + text: "Esta acción no se puede revertir", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#ccff00', + cancelButtonColor: '#d33', + confirmButtonText: 'Sí, eliminar', + cancelButtonText: 'Cancelar' +}).then((result) => { + if (result.isConfirmed) { + // Ejecutar eliminación + } +}); +``` + +#### 3. Toast Notifications +```javascript +// Mensajes no intrusivos +const Toast = Swal.mixin({ + toast: true, + position: 'top-end', + showConfirmButton: false, + timer: 3000 +}); + +Toast.fire({ + icon: 'success', + title: 'Producto guardado' +}); +``` + +#### 4. Validación en Tiempo Real +```javascript +$('input[name="sku"]').on('blur', function() { + const sku = $(this).val(); + + $.get(`/api/products/check-sku/${sku}`, function(data) { + if (data.exists) { + // Mostrar error + $(this).addClass('border-red-500'); + } + }); +}); +``` + +### Testing + +#### 1. Tests de Feature +```php +// tests/Feature/ProductTest.php +public function test_admin_can_create_product() +{ + $admin = User::factory()->create(['role' => 'admin']); + + $this->actingAs($admin) + ->post('/productos', [ + 'name' => 'Test Product', + 'price' => 100, + 'stock_quantity' => 10, + 'min_stock_alert' => 5, + 'type' => 'accessory' + ]) + ->assertRedirect('/productos') + ->assertSessionHas('success'); + + $this->assertDatabaseHas('products', [ + 'name' => 'Test Product' + ]); +} + +public function test_guest_cannot_create_product() +{ + $response = $this->post('/productos', []); + $response->assertRedirect('/login'); +} +``` + +#### 2. Tests Unitarios +```php +// tests/Unit/ProductTest.php +public function test_product_has_low_stock() +{ + $product = Product::factory()->create([ + 'stock_quantity' => 3, + 'min_stock_alert' => 5 + ]); + + $this->assertTrue($product->hasLowStock()); +} +``` + +#### 3. Browser Tests (Dusk) +```php +// tests/Browser/PosTest.php +public function test_can_complete_sale() +{ + $this->browse(function (Browser $browser) { + $browser->loginAs(User::find(1)) + ->visit('/sales/create') + ->select('items[0][product_id]', 1) + ->type('items[0][quantity]', 2) + ->press('Confirmar Venta') + ->assertPathIs('/sales/1'); + }); +} +``` \ No newline at end of file From 2e4510fc88f3b398a4584a95dd95ea12eeabb3d8 Mon Sep 17 00:00:00 2001 From: Bryam105 Date: Wed, 4 Feb 2026 12:04:02 -0330 Subject: [PATCH 13/17] Update: Actualizado el seeder de productos con algunas bicicletas reales del excel. --- database/seeders/ProductSeeder.php | 76 +++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/database/seeders/ProductSeeder.php b/database/seeders/ProductSeeder.php index ec5e31b..01f2772 100644 --- a/database/seeders/ProductSeeder.php +++ b/database/seeders/ProductSeeder.php @@ -17,14 +17,78 @@ class ProductSeeder extends Seeder $products = [ // 1. BICICLETAS [ - 'name' => 'Bicicleta Venzo Loki R29', - 'sku' => 'BIC-VEN-001', - 'price' => 450000, - 'cost' => 300000, - 'stock_quantity' => 2, - 'min_stock_alert' => 1, + 'name' => 'Bicicleta R12 nena-varon rueda maciza', + 'sku' => 'BIC-001', + 'price' => 148000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, 'type' => 'bike', // bike, accessory, clothing, spare ], + [ + 'name' => 'Bicicleta R12 con rayos varon', + 'sku' => 'BIC-002', + 'price' => 175000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R12 reina flecos canasto', + 'sku' => 'BIC-003', + 'price' => 999, + 'cost' => 111, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R12 topmega nena', + 'sku' => 'BIC-004', + 'price' => 250000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R12 topmega varon', + 'sku' => 'BIC-005', + 'price' => 250000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R12 Max SLP', + 'sku' => 'BIC-006', + 'price' => 250000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R12 Max SLP rueda megnesio', + 'sku' => 'BIC-007', + 'price' => 268000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R12 SLP dolphin', + 'sku' => 'BIC-008', + 'price' => 250000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + // 2. ACCESORIOS [ 'name' => 'Cámara 29 Valvula Auto', 'sku' => 'CAM-29-A', From e94d03c7799d4c82977664c998d588e590622384 Mon Sep 17 00:00:00 2001 From: Bryam105 Date: Thu, 5 Feb 2026 08:38:35 -0330 Subject: [PATCH 14/17] Update: Actualizado el seeder de productos con algunas bicicletas reales del excel (Parte 2). --- database/seeders/ProductSeeder.php | 173 ++++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 1 deletion(-) diff --git a/database/seeders/ProductSeeder.php b/database/seeders/ProductSeeder.php index 01f2772..abadd51 100644 --- a/database/seeders/ProductSeeder.php +++ b/database/seeders/ProductSeeder.php @@ -68,7 +68,7 @@ class ProductSeeder extends Seeder 'cost' => 100000, 'stock_quantity' => 5, 'min_stock_alert' => 2, - 'type' => 'bike', // bike, accessory, clothing, spare + 'type' => 'bike', ], [ 'name' => 'Bicicleta R12 Max SLP rueda megnesio', @@ -88,6 +88,177 @@ class ProductSeeder extends Seeder 'min_stock_alert' => 2, 'type' => 'bike', // bike, accessory, clothing, spare ], + [ + 'name' => 'Camicleta SLP varon', + 'sku' => 'BIC-009', + 'price' => 139000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Camicleta SLP ruedas macizas', + 'sku' => 'BIC-010', + 'price' => 138000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Camicleta SLP rayos', + 'sku' => 'BIC-011', + 'price' => 185000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Camicleta Economica ruedas macizas rosa', + 'sku' => 'BIC-012', + 'price' => 100000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R14 Cross varon', + 'sku' => 'BIC-013', + 'price' => 228000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R14 Full nena', + 'sku' => 'BIC-014', + 'price' => 228000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R16 Peretti nena canasto', + 'sku' => 'BIC-015', + 'price' => 239000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R16 Firebird', + 'sku' => 'BIC-016', + 'price' => 269000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R16 Full nena', + 'sku' => 'BIC-017', + 'price' => 239000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', // bike, accessory, clothing, spare + ], + [ + 'name' => 'Bicicleta R16 dolphin/zest nena', + 'sku' => 'BIC-018', + 'price' => 269000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', + ], + [ + 'name' => 'Bicicleta R16 Cross estabilizadores', + 'sku' => 'BIC-019', + 'price' => 230000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', + ], + [ + 'name' => 'Bicicleta R16 Peretti Cross', + 'sku' => 'BIC-020', + 'price' => 235000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', + ], + [ + 'name' => 'Bicicleta R16 Max SLP', + 'sku' => 'BIC-021', + 'price' => 268000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', + ], + [ + 'name' => 'Bicicleta R16 Cross Peretti cromada', + 'sku' => 'BIC-022', + 'price' => 240000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', + ], + [ + 'name' => 'Bicicleta R16 Cross Topmega', + 'sku' => 'BIC-023', + 'price' => 240000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', + ], + [ + 'name' => 'Bicicleta R20 Cross', + 'sku' => 'BIC-024', + 'price' => 245000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', + ], + [ + 'name' => 'Bicicleta R20 Peretti Cross varon', + 'sku' => 'BIC-025', + 'price' => 245000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', + ], + [ + 'name' => 'Bicicleta R20 Max SLP varon', + 'sku' => 'BIC-026', + 'price' => 298000, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', + ], + [ + 'name' => 'Bicicleta R20 Firebird', + 'sku' => 'BIC-027', + 'price' => 1, + 'cost' => 100000, + 'stock_quantity' => 5, + 'min_stock_alert' => 2, + 'type' => 'bike', + ], // 2. ACCESORIOS [ 'name' => 'Cámara 29 Valvula Auto', From 8448a9e43b9eb67922f0ffef06892eee4d77a0da Mon Sep 17 00:00:00 2001 From: Bryam105 Date: Mon, 9 Feb 2026 09:14:26 -0330 Subject: [PATCH 15/17] Update: Actualizada vista de sales.create, Agregado un stack('styles') en layout para agregar css. --- resources/views/components/layout.blade.php | 1 + resources/views/sales/create.blade.php | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/resources/views/components/layout.blade.php b/resources/views/components/layout.blade.php index b6f852c..2c69596 100644 --- a/resources/views/components/layout.blade.php +++ b/resources/views/components/layout.blade.php @@ -5,6 +5,7 @@ {{ $title ?? 'Lauck Dashboard' }} + @stack('styles') @vite(['resources/css/app.css', 'resources/js/app.js']) diff --git a/resources/views/sales/create.blade.php b/resources/views/sales/create.blade.php index 50e6630..bfdacea 100644 --- a/resources/views/sales/create.blade.php +++ b/resources/views/sales/create.blade.php @@ -52,7 +52,7 @@ @csrf -
      +
      @@ -64,14 +64,14 @@
      - -
      +
      @@ -98,7 +98,7 @@
      + class="quantity-input bg-neutral-800 border-neutral-700 border rounded-lg text-white text-sm focus:ring-neon-lime focus:border-neon-lime block w-full p-1 px-2 h-7">
      @@ -146,7 +146,7 @@
      - + Cancelar + {{-- LÓGICA DE VISITANTE (GUEST) --}} @guest
    • + class="block py-2 px-3 md:p-0 transition-colors {{ request()->is('catalogo*') ? 'text-lime-600 dark:text-neon-lime border-b-2 border-lime-600 dark:border-neon-lime' : 'dark:text-white hover:text-lime-600 hover:dark:text-neon-lime' }}"> Catalogo
    • @@ -69,9 +76,9 @@
      - + @endforeach
      @@ -91,8 +91,8 @@ $track.css('transform', `translateX(${translateX}%)`); // Actualizar puntos - $('.carousel-dot').removeClass('bg-neon-lime scale-125').addClass('bg-white/20'); - $(`.carousel-dot[data-index="${currentIndex}"]`).addClass('bg-neon-lime scale-125').removeClass('bg-white/20'); + $('.carousel-dot').removeClass('bg-lime-500 dark:bg-neon-lime scale-125').addClass('bg-black/20 dark:bg-white/20'); + $(`.carousel-dot[data-index="${currentIndex}"]`).addClass('bg-lime-500 dark:bg-neon-lime scale-125').removeClass('bg-black/20 dark:bg-white/20'); } function nextSlide() { diff --git a/resources/views/login.blade.php b/resources/views/login.blade.php index 9875eac..f8d2e6f 100644 --- a/resources/views/login.blade.php +++ b/resources/views/login.blade.php @@ -11,7 +11,7 @@ /> --> -
      +
      @csrf {{-- No muestra los errores, ver --}} diff --git a/tailwind.config.js b/tailwind.config.js index 20be7ba..443c689 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,5 +1,6 @@ /** @type {import('tailwindcss').Config} */ export default { + darkMode: "selector", content: [ "./resources/**/*.blade.php", "./resources/**/*.js", From 7c3d46033373d7802ea78e1f64b9ac482052e319 Mon Sep 17 00:00:00 2001 From: BryamE Date: Fri, 27 Feb 2026 00:05:42 -0300 Subject: [PATCH 17/17] STYLE: - Continuo con la variante al modo claro. --- resources/views/catalogo/index.blade.php | 12 ++--- resources/views/catalogo/show.blade.php | 44 +++++++++---------- .../views/components/forms/input.blade.php | 2 +- .../views/components/forms/label.blade.php | 2 +- resources/views/components/ui/alert.blade.php | 4 +- resources/views/components/ui/badge.blade.php | 12 ++--- resources/views/components/ui/card.blade.php | 10 ++--- resources/views/dashboard.blade.php | 14 +++--- resources/views/productos/create.blade.php | 16 +++---- resources/views/productos/index.blade.php | 30 ++++++------- 10 files changed, 73 insertions(+), 73 deletions(-) diff --git a/resources/views/catalogo/index.blade.php b/resources/views/catalogo/index.blade.php index 717bac2..b50f89f 100644 --- a/resources/views/catalogo/index.blade.php +++ b/resources/views/catalogo/index.blade.php @@ -8,14 +8,14 @@ @if ($products->count() > 0)
      @foreach ($products as $product) -
      +
      {{-- Imagen si existe --}} @if (!empty($product->image_path)) {{ $product->name }} @else -
      - Sin imagen +
      + Sin imagen
      @endif @@ -25,18 +25,18 @@ {{-- Descripción corta --}}
      @if (!empty($product->description)) -

      {{ $product->description }}

      +

      {{ $product->description }}

      @endif
      {{-- Precio --}} @if (!empty($product->price)) -

      ${{ number_format($product->price, 2, ',', '.') }}

      +

      ${{ number_format($product->price, 2, ',', '.') }}

      @endif {{-- Botón --}} + class="mt-4 block w-full bg-neutral-900 dark:bg-neon-lime/80 uppercase text-neon-lime dark:text-black font-semibold py-2 rounded-lg text-center shadow-md shadow-gray-900/10 dark:shadow-neon-lime/10 hover:bg-neon-lime hover:dark:bg-neutral-900/70 border border-transparent hover:text-black hover:dark:text-neon-lime hover:border-black hover:dark:border-neon-lime transition-all"> Ver + diff --git a/resources/views/catalogo/show.blade.php b/resources/views/catalogo/show.blade.php index 4e94d0c..a137c90 100644 --- a/resources/views/catalogo/show.blade.php +++ b/resources/views/catalogo/show.blade.php @@ -3,23 +3,23 @@
      - + Volver al Catálogo -
      +
      - -
      + +
      @if($product->image_path) {{ $product->name }} @else -
      +
      Sin Imagen
      @@ -27,7 +27,7 @@
      - + @if($product->type == 'bike') Bicicleta @elseif($product->type == 'clothing') @@ -42,34 +42,34 @@
      - -
      + +
      -
      -

      SKU: {{ $product->sku ?? 'N/A' }}

      -

      {{ $product->name }}

      +
      +

      SKU: {{ $product->sku ?? 'N/A' }}

      +

      {{ $product->name }}

      @if($product->stock_quantity > 5) - - En Stock ({{ $product->stock_quantity }} unid.) + + En Stock ({{ $product->stock_quantity }} unid.) @elseif($product->stock_quantity > 0) - - ¡Últimas Unidades! ({{ $product->stock_quantity }}) + + ¡Últimas Unidades! ({{ $product->stock_quantity }}) @else - Agotado + Agotado @endif
      - Precio Contado + Precio Contado
      - ${{ number_format($product->price, 0, ',', '.') }} + ${{ number_format($product->price, 0, ',', '.') }} ARG

      * Consultar financiación en el local.

      @@ -77,20 +77,20 @@
      -

      Descripción

      -

      +

      Descripción

      +

      {{ $product->description ?? 'Sin descripción detallada disponible para este producto. Por favor, acérquese al local para más información.' }}

      - - + WhatsApp diff --git a/resources/views/components/forms/input.blade.php b/resources/views/components/forms/input.blade.php index 5f0c99c..dfe5fa1 100644 --- a/resources/views/components/forms/input.blade.php +++ b/resources/views/components/forms/input.blade.php @@ -1,6 +1,6 @@ @props(['disabled' => false, 'error' => null]) -merge(['class' => 'bg-neutral-800 border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-gray-500 transition-colors ' . ($error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : '')]) !!}> +merge(['class' => 'bg-gray-300 dark:bg-neutral-800 border border-neutral-400 dark:border-neutral-700 dark:text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-neutral-600 dark:placeholder-gray-400 transition-colors ' . ($error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : '')]) !!}> @if($error)

      {{ $error }}

      diff --git a/resources/views/components/forms/label.blade.php b/resources/views/components/forms/label.blade.php index e729c95..57439de 100644 --- a/resources/views/components/forms/label.blade.php +++ b/resources/views/components/forms/label.blade.php @@ -1,5 +1,5 @@ @props(['value']) -