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