$row['nombre'] ?? null, 'sku' => isset($row['sku']) ? (string) $row['sku'] : null, 'codigo_barras' => isset($row['codigo_barras']) ? (string) $row['codigo_barras'] : null, 'categoria' => $row['categoria'] ?? null, 'unidad_medida' => $row['unidad_medida'] ?? null, 'precio_venta' => $row['precio_venta'] ?? null, 'impuesto' => $row['impuesto'] ?? null, ]; } /** * Procesa cada fila del Excel */ public function model(array $row) { // Ignorar filas completamente vacías if (empty($row['nombre']) && empty($row['sku']) && empty($row['precio_venta'])) { return null; } try { $existingInventory = null; if(!empty($row['sku'])) { $existingInventory = Inventory::where('sku', trim($row['sku']))->first(); } if(!$existingInventory && !empty($row['codigo_barras'])) { $existingInventory = Inventory::where('barcode', trim($row['codigo_barras']))->first(); } // Si el producto ya existe, actualizar información if ($existingInventory) { return $this->updateExistingProduct($existingInventory, $row); } // Producto nuevo return $this->createNewProduct($row); } catch (\Exception $e) { $this->skipped++; $this->errors[] = "Error en fila: " . $e->getMessage(); return null; } } private function createNewProduct(array $row) { try { // Validar nombre del producto if (!isset($row['nombre']) || empty(trim($row['nombre']))) { $this->skipped++; $this->errors[] = "Fila sin nombre de producto"; return null; } // Validar precio de venta $precioVenta = (float) $row['precio_venta']; if ($precioVenta <= 0) { $this->skipped++; $this->errors[] = "Fila con producto '{$row['nombre']}': El precio de venta debe ser mayor a 0"; return null; } // Buscar o crear categoría si se proporciona $categoryId = null; if (!empty($row['categoria'])) { $category = Category::firstOrCreate( ['name' => trim($row['categoria'])], ['is_active' => true] ); $categoryId = $category->id; } // Buscar unidad de medida (requerida) $unitId = null; if (!empty($row['unidad_medida'])) { $unit = UnitOfMeasurement::where('name', trim($row['unidad_medida'])) ->orWhere('abbreviation', trim($row['unidad_medida'])) ->first(); if ($unit) { $unitId = $unit->id; } else { $this->skipped++; $this->errors[] = "Fila con producto '{$row['nombre']}': Unidad de medida '{$row['unidad_medida']}' no encontrada"; return null; } } else { // Si no se proporciona, usar 'Pieza' por defecto $unit = UnitOfMeasurement::where('name', 'Pieza')->first(); if ($unit) { $unitId = $unit->id; } else { $this->skipped++; $this->errors[] = "Fila con producto '{$row['nombre']}': No se proporcionó unidad de medida y no existe 'Pieza' por defecto"; return null; } } // Crear el producto en inventario $inventory = new Inventory(); $inventory->name = trim($row['nombre']); $inventory->sku = !empty($row['sku']) ? trim($row['sku']) : null; $inventory->barcode = !empty($row['codigo_barras']) ? trim($row['codigo_barras']) : null; $inventory->category_id = $categoryId; $inventory->unit_of_measure_id = $unitId; $inventory->is_active = true; $inventory->track_serials = false; $inventory->save(); // Crear el precio del producto Price::create([ 'inventory_id' => $inventory->id, 'cost' => 0, 'retail_price' => $precioVenta, 'tax' => !empty($row['impuesto']) ? (float) $row['impuesto'] : 0, ]); $this->imported++; return $inventory; } catch (\Exception $e) { $this->skipped++; $this->errors[] = "Error creando producto '{$row['nombre']}': " . $e->getMessage(); return null; } } /** * Actualiza un producto existente con nueva información */ private function updateExistingProduct(Inventory $inventory, array $row) { try { // Actualizar información básica del producto if (!empty($row['nombre'])) { $inventory->name = trim($row['nombre']); } if (!empty($row['codigo_barras'])) { $inventory->barcode = trim($row['codigo_barras']); } // Actualizar categoría si se proporciona if (!empty($row['categoria'])) { $category = Category::firstOrCreate( ['name' => trim($row['categoria'])], ['is_active' => true] ); $inventory->category_id = $category->id; } // Actualizar unidad de medida si se proporciona if (!empty($row['unidad_medida'])) { $unit = UnitOfMeasurement::where('name', trim($row['unidad_medida'])) ->orWhere('abbreviation', trim($row['unidad_medida'])) ->first(); if ($unit) { $inventory->unit_of_measure_id = $unit->id; } } $inventory->save(); // Actualizar precio de venta e impuesto (NO el costo) if ($inventory->price) { $updateData = []; if (!empty($row['precio_venta'])) { $updateData['retail_price'] = (float) $row['precio_venta']; } if (isset($row['impuesto'])) { $updateData['tax'] = (float) $row['impuesto']; } if (!empty($updateData)) { $inventory->price->update($updateData); } } $this->updated++; return null; // No retornar modelo para evitar que Maatwebsite intente guardarlo } catch (\Exception $e) { $this->skipped++; $this->errors[] = "Error actualizando producto '{$inventory->name}': " . $e->getMessage(); return null; } } /** * Reglas de validación para cada fila */ public function rules(): array { return InventoryImportRequest::rowRules(); } /** * Mensajes personalizados de validación */ public function customValidationMessages() { return InventoryImportRequest::rowMessages(); } /** * Chunk size for reading */ public function chunkSize(): int { return 100; } /** * Obtener estadísticas de la importación */ public function getStats(): array { return [ 'imported' => $this->imported, 'updated' => $this->updated, 'skipped' => $this->skipped, 'errors' => $this->errors, ]; } }