From de67293e99d29232118274db17c2e65c4fb7bdfa Mon Sep 17 00:00:00 2001 From: gianella <146126967+GianellaDelMestre@users.noreply.github.com> Date: Sat, 18 Apr 2026 16:01:30 -0300 Subject: [PATCH] cambios para importar el excel --- app/Console/Commands/ImportarExcelLimpio.php | 97 ++++++++++++------- app/Http/Controllers/ProductosController.php | 65 +++++-------- app/Models/Product.php | 18 ++-- ...025_12_06_182358_create_products_table.php | 15 +-- 4 files changed, 101 insertions(+), 94 deletions(-) diff --git a/app/Console/Commands/ImportarExcelLimpio.php b/app/Console/Commands/ImportarExcelLimpio.php index ef2e364..de40db8 100644 --- a/app/Console/Commands/ImportarExcelLimpio.php +++ b/app/Console/Commands/ImportarExcelLimpio.php @@ -9,22 +9,15 @@ use Illuminate\Support\Facades\Storage; class ImportarExcelLimpio extends Command { - // Este es el comando que vas a escribir en la terminal protected $signature = 'importar:excel'; - protected $description = 'Importa el catálogo limpio desde el archivo CSV'; + protected $description = 'Importa el catálogo desde CSV con asociación dinámica de proveedores'; public function handle() { - $this->info("Iniciando importación del Excel limpio..."); + $this->info("Iniciando importación del Excel con lógica de proveedores..."); - // 1. Asegurarnos de que existe el proveedor comodín (para evitar el error de la llave foránea) - Supplier::firstOrCreate( - ['id' => 1], - [ - 'name' => 'Proveedor General Lauck', - 'phone' => '0000000000' - ] - ); + // Aseguramos que existan al menos los 3 proveedores básicos por si acaso + $this->asegurarProveedores(); $rutaCompleta = storage_path('app/imports/catalogo_limpio.csv'); @@ -32,45 +25,58 @@ class ImportarExcelLimpio extends Command $this->error("No se encontró el archivo en: " . $rutaCompleta); return; } - $file = fopen($rutaCompleta, 'r'); - // Saltamos la primera fila (porque son los títulos: tipo, descripcion, costo, precio) - fgetcsv($file); + $file = fopen($rutaCompleta, 'r'); + fgetcsv($file); // Saltar cabecera $contador = 0; while (($fila = fgetcsv($file, 1000, ',')) !== false) { - // Asignamos las columnas del Excel (0 a 3) - $tipoRaw = strtolower(trim($fila[0] ?? '')); - $descripcion = trim($fila[1] ?? ''); - $costoRaw = trim($fila[2] ?? ''); - $precioRaw = trim($fila[3] ?? ''); + // 1. Lectura de las 6 columnas + $tipoRaw = strtolower(trim($fila[0] ?? '')); + $nombreRaw = trim($fila[1] ?? ''); + $descripcionRaw = trim($fila[2] ?? ''); + $costoRaw = trim($fila[3] ?? ''); + $precioRaw = trim($fila[4] ?? ''); + $proveedorId = trim($fila[5] ?? 1); // ID del proveedor - // Si la descripción está vacía, saltamos la fila - if (empty($descripcion)) { - continue; - } + if (empty($nombreRaw)) continue; - // --- Lógica de limpieza de Tipos --- - // Si en el Excel dice "bicicleta", lo guardamos como "bike" para mantener el estándar. - $tipoFinal = ($tipoRaw === 'bicicleta') ? 'bike' : $tipoRaw; + // 2. Limpieza de Nombre (Primera letra de cada palabra) + $nombreSinEspacios = preg_replace('/\s+/', ' ', $nombreRaw); + $nombreFinal = mb_convert_case($nombreSinEspacios, MB_CASE_TITLE, "UTF-8"); - // --- Lógica de limpieza de Precios ("$ 7,200" -> 7200.0) --- - // Borramos el signo peso, los espacios y las comas de los miles + // 3. Traductor de Categorías (Evita errores SQL de truncado) + $tipoRaw = preg_replace('/[\xEF\xBB\xBF]/', '', $tipoRaw); // Limpieza de BOM + $mapeoTipos = [ + 'bicicleta' => 'bike', 'bicicletas' => 'bike', 'bike' => 'bike', + 'indumentaria' => 'clothing', 'ropa' => 'clothing', 'clothing' => 'clothing', + 'accesorio' => 'accessory', 'accesorios' => 'accessory', 'accessory' => 'accessory', + 'repuesto' => 'spare', 'repuestos' => 'spare', 'spare' => 'spare', + 'infantil' => 'children', 'niños' => 'children', 'children' => 'children', + 'rollers' => 'rollers', 'roller' => 'rollers', + 'skate' => 'skate', 'skates' => 'skate', + 'servicio' => 'service', 'service' => 'service', + 'otro' => 'other', 'otros' => 'other', 'other' => 'other' + ]; + $tipoFinal = $mapeoTipos[$tipoRaw] ?? 'other'; + + // 4. Limpieza de Precios $costoLimpio = (float) str_replace(['$', ' ', ','], '', $costoRaw); $precioLimpio = (float) str_replace(['$', ' ', ','], '', $precioRaw); - // Guardamos en la base de datos + // 5. Creación o Actualización Product::updateOrCreate( - ['name' => $descripcion], // Buscamos por descripción + ['name' => $nombreFinal], [ - 'type' => $tipoFinal, - 'cost' => $costoLimpio, - 'price' => $precioLimpio, - 'stock_quantity' => 20, // Al ser lista de precios, entra con stock 0 + 'description' => $descripcionRaw, + 'type' => $tipoFinal, + 'cost' => $costoLimpio, + 'price' => $precioLimpio, + 'suppliers_id' => $proveedorId, // Asociación dinámica + 'stock_quantity' => 20, 'min_stock_alert' => 5, - 'suppliers_id' => 1, ] ); @@ -78,6 +84,25 @@ class ImportarExcelLimpio extends Command } fclose($file); - $this->info("¡Éxito! Se importaron/actualizaron {$contador} productos."); + $this->info("¡Éxito! Se procesaron {$contador} productos con sus respectivos proveedores."); + } + + /** + * Crea los proveedores básicos si no existen para evitar errores de integridad. + */ + private function asegurarProveedores() + { + $proveedores = [ + 1 => 'Proveedor General Lauck', + 2 => 'Distribuidora Norte', + 3 => 'Repuestos Express' + ]; + + foreach ($proveedores as $id => $name) { + Supplier::firstOrCreate( + ['id' => $id], + ['name' => $name, 'phone' => '0000000000'] + ); + } } } \ No newline at end of file diff --git a/app/Http/Controllers/ProductosController.php b/app/Http/Controllers/ProductosController.php index 6ddeded..ea104a4 100644 --- a/app/Http/Controllers/ProductosController.php +++ b/app/Http/Controllers/ProductosController.php @@ -3,16 +3,14 @@ namespace App\Http\Controllers; use App\Models\Product; +use App\Models\Supplier; use Illuminate\Http\Request; use Illuminate\Validation\Rule; use Illuminate\Support\Facades\Storage; class ProductosController extends Controller { - /** - * Muestra la lista de productos con buscador y paginación. - */ - public function index(Request $request) + /*public function index(Request $request) { $query = $request->input('search'); $status = $request->input('stock_status'); @@ -40,89 +38,79 @@ class ProductosController extends Controller ->where('type', '!=', 'service'); } }) - ->orderBy('stock_quantity', 'asc') // Ordena primero los que tienen poco stock - ->paginate(10) // Muestra de a 10 productos - ->withQueryString(); // Mantiene los filtros al pasar a la página 2, 3, etc. + ->orderBy('stock_quantity', 'asc') + ->paginate(10) + ->withQueryString(); + + return view('productos.index', compact('products')); + }*/ + public function index() + { + // Traemos todos los productos ordenados (sin paginar, para que JS los pueda buscar todos) + $products = Product::orderBy('name', 'asc')->get(); return view('productos.index', compact('products')); } - /** - * Muestra el formulario de creación. - */ public function create() { - return view('productos.create'); + $suppliers = Supplier::orderBy('name')->get(); + return view('productos.create', compact('suppliers')); } - /** - * Guarda el producto nuevo en la base de datos. - */ public function store(Request $request) { - // 1. Validamos los datos con las nuevas columnas $validated = $request->validate([ - 'name' => 'required|string|max:255', // CORREGIDO: description en lugar de name + 'name' => 'required|string|max:255', 'price' => 'required|numeric|min:0', 'cost' => 'nullable|numeric|min:0', 'stock_quantity' => 'required|integer|min:0', 'min_stock_alert' => 'required|integer|min:0', 'type' => 'required|in:bike,accessory,clothing,spare,service,children,skate,rollers,other', 'serial_number' => 'nullable|string|max:100', - 'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048', + 'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048', + 'suppliers_id' => 'required|exists:suppliers,id', + 'description' => 'nullable|string' ]); - // 2. Validacion de imagenes if ($request->hasFile('image')) { $path = $request->file('image')->store('products', 'public'); $validated['image_path'] = $path; } unset($validated['image']); - // Para no romper la logica del supplier - $validated['suppliers_id'] = 1; - - // 3. Creamos el producto Product::create($validated); - // 4. Redireccionamos return redirect()->route('productos.index') ->with('success', 'Producto creado correctamente.'); } - /** - * Muestra el detalle de un producto. - */ public function show(Product $product) { return view('productos.show', compact('product')); } - /** - * Muestra el formulario de edición. - */ public function edit(Product $product) { - return view('productos.edit', compact('product')); + $suppliers = Supplier::orderBy('name')->get(); + return view('productos.edit', compact('product', 'suppliers')); } - /** - * Actualiza el producto existente. - */ public function update(Request $request, Product $product) { $validated = $request->validate([ - 'name' => 'required|string|max:255', // CORREGIDO - 'type' => 'required|in:bike,accessory,clothing,spare,service,children,skate,rollers,other', // CORREGIDO + 'name' => 'required|string|max:255', + 'type' => 'required|in:bike,accessory,clothing,spare,service,children,skate,rollers,other', 'price' => 'required|numeric|min:0', 'cost' => 'nullable|numeric|min:0', 'stock_quantity' => 'required|integer|min:0', 'min_stock_alert' => 'required|integer|min:0', 'serial_number' => 'nullable|string|max:100', 'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048', + 'suppliers_id' => 'required|exists:suppliers,id', + 'description' => 'nullable|string' ]); - // 2. Manejo de imagen al actualizar if ($request->hasFile('image')) { if ($product->image_path) { Storage::disk('public')->delete($product->image_path); @@ -132,8 +120,6 @@ class ProductosController extends Controller $validated['image_path'] = $path; } unset($validated['image']); - - $validated['suppliers_id'] = 1; $product->update($validated); @@ -141,9 +127,6 @@ class ProductosController extends Controller ->with('success', 'Producto actualizado exitosamente.'); } - /** - * Elimina el producto. - */ public function destroy(Product $product) { if ($product->image_path) { diff --git a/app/Models/Product.php b/app/Models/Product.php index f9ce056..664995f 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -8,7 +8,18 @@ use Illuminate\Database\Eloquent\Model; class Product extends Model { use HasFactory; - protected $guarded = []; + protected $fillable = [ + 'name', + 'description', + 'type', + 'cost', + 'price', + 'stock_quantity', + 'min_stock_alert', + 'serial_number', + 'image_path', + 'suppliers_id' + ]; public function hasLowStock(): bool @@ -20,16 +31,11 @@ class Product extends Model { return $this->belongsTo(Supplier::class); } - - // Opción 1: Relación directa con los detalles (Renglones de ticket) - // Útil para saber cantidad total vendida: $product->saleDetails->sum('quantity') public function saleDetails() { return $this->hasMany(SaleDetail::class); } - // Opción 2 (Pro Tip): Relación directa con las Ventas a través de los detalles - // Útil para saber EN QUÉ fechas se vendió: $product->sales public function sales() { return $this->belongsToMany(Sale::class, 'sale_details'); 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 fe306b5..d500b03 100644 --- a/database/migrations/2025_12_06_182358_create_products_table.php +++ b/database/migrations/2025_12_06_182358_create_products_table.php @@ -13,20 +13,13 @@ return new class extends Migration { Schema::create('products', function (Blueprint $table) { $table->id(); - $table->enum('type', ['bike', 'accessory', 'clothing', 'spare', 'children', 'skate', 'rollers','other']); // Tipo de producto - //$table->string('name'); - //$table->string('sku')->unique()->nullable(); // Código de barras o interno + $table->enum('type', ['bike', 'accessory', 'clothing', 'spare', 'children', 'skate', 'rollers','other']); $table->text('name')->nullable(); - - $table->decimal('price', 10, 2); // Precio venta - $table->decimal('cost', 10, 2)->nullable(); // Costo (solo admin) - + $table->text('description')->nullable();; + $table->decimal('price', 10, 2); // Precio + $table->decimal('cost', 10, 2)->nullable(); // Costo $table->integer('stock_quantity')->default(100); $table->integer('min_stock_alert')->default(5); // Alerta - - - $table->string('serial_number')->nullable(); // Solo para bicis - $table->foreignId('suppliers_id')->constrained()->default(1); $table->timestamps(); });