Files
LauckCycles/RECOMENDACIONES.md

8.0 KiB

🔧 Mejoras Técnicas Recomendadas

Optimización de Rendimiento

1. Implementar Caché Estratégico

// 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

// 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

// 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

// Para datasets grandes
$products = Product::latest()->cursorPaginate(20);

Refactorización de Código

1. Services para Lógica de Negocio

// 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

// 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

// 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

// 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

// 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

// En Form Request
protected function prepareForValidation()
{
    $this->merge([
        'name' => strip_tags($this->name),
        'description' => Purifier::clean($this->description),
    ]);
}

3. Protección CSRF Explícita

// Verificar en AJAX
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

4. Validación de Imágenes

'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

<button type="submit" id="submit-btn" class="...">
    <span class="loading hidden">
        <svg class="animate-spin h-5 w-5" ...></svg>
    </span>
    <span class="text">Guardar</span>
</button>

<script>
$('#form').on('submit', function() {
    $('#submit-btn .loading').removeClass('hidden');
    $('#submit-btn .text').addClass('hidden');
    $('#submit-btn').prop('disabled', true);
});
</script>

2. Confirmaciones Elegantes

// 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

// 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

$('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

// 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

// 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)

// 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');
    });
}