previewData = []; $rowNumber = 2; // Comenzar en 2 porque fila 1 son los encabezados foreach ($rows as $row) { $detalle = $this->processRow($row->toArray(), $rowNumber); $this->previewData[] = $detalle; $rowNumber++; } } /** * Procesa una fila individual */ private function processRow(array $row, int $rowNumber): array { $result = [ 'row_number' => $rowNumber, 'valid' => true, 'errors' => [], 'data' => [], 'producto_creado' => false, ]; // Normalizar nombres de columnas $row = $this->normalizeKeys($row); // Buscar o crear producto $producto = null; $variante = null; $productoCreado = false; // 1. Primero intentar buscar por código de barras if (!empty($row['codigo_barras'])) { // Buscar variante primero $variante = ProductVariant::where('barcode', $row['codigo_barras'])->first(); if ($variante) { $producto = $variante->producto; } else { // Buscar producto por código $producto = Producto::where('codigo_barras', $row['codigo_barras'])->first(); } } // 2. Si no se encontró por código, buscar por nombre if (!$producto && !empty($row['producto'])) { $producto = Producto::where('nombre', 'like', '%' . trim($row['producto']) . '%')->first(); } // 3. Si no existe, crear el producto if (!$producto) { // Validar que tenga al menos nombre o código if (empty($row['producto']) && empty($row['codigo_barras'])) { $result['valid'] = false; $result['errors'][] = 'Debe proporcionar código de barras o nombre del producto'; } else { $nombreProducto = !empty($row['producto']) ? trim($row['producto']) : 'Producto ' . $row['codigo_barras']; $precioUnitario = isset($row['precio_unitario']) ? (float) $row['precio_unitario'] : 0; if ($precioUnitario <= 0) { $result['valid'] = false; $result['errors'][] = 'Precio debe ser mayor a 0 para crear producto'; } else { // Buscar o crear categoría "Importados" $categoria = \App\Models\Categoria::firstOrCreate( ['nombre' => 'Importados'], ['descripcion' => 'Productos creados automáticamente durante importación de compras'] ); // Generar código de barras único si no se proporcionó $codigoBarras = !empty($row['codigo_barras']) ? $row['codigo_barras'] : null; if (!$codigoBarras) { // Generar código único: IMP + timestamp + random do { $codigoBarras = 'IMP' . time() . rand(100, 999); } while (Producto::where('codigo_barras', $codigoBarras)->exists()); } $producto = Producto::create([ 'nombre' => $nombreProducto, 'descripcion' => 'Creado automáticamente desde importación', 'codigo_barras' => $codigoBarras, 'categoria_id' => $categoria->id, 'precio_compra' => $precioUnitario, 'precio_venta' => round($precioUnitario * 1.3, 2), // 30% de margen por defecto 'unidad_medida' => 'unidad', 'estado' => true, 'imagen' => '', ]); $productoCreado = true; $result['producto_creado'] = true; } } } // Buscar bodega $bodega = null; if (!empty($row['bodega'])) { $bodega = Bodega::where('nombre', 'like', '%' . $row['bodega'] . '%')->first(); } else { $bodega = Bodega::where('nombre', 'Principal')->first(); } if (!$bodega) { $result['valid'] = false; $result['errors'][] = 'Bodega no encontrada'; } // Validar cantidad $cantidad = isset($row['cantidad']) ? (int) $row['cantidad'] : 1; if ($cantidad <= 0) { $result['valid'] = false; $result['errors'][] = 'Cantidad debe ser mayor a 0'; } // Validar precio $precio = isset($row['precio_unitario']) ? (float) $row['precio_unitario'] : 0; if ($precio <= 0) { $result['valid'] = false; $result['errors'][] = 'Precio debe ser mayor a 0'; } // Calcular subtotal $subtotal = $cantidad * $precio; // Preparar datos $result['data'] = [ 'codigo_barras' => $row['codigo_barras'] ?? '', 'producto_nombre_excel' => $row['producto'] ?? '', 'bodega_id' => $bodega?->id, 'bodega_nombre' => $bodega?->nombre ?? 'No encontrada', 'producto_id' => $producto?->id, 'producto_nombre' => $producto?->nombre ?? 'No encontrado', 'variante_id' => $variante?->id, 'variante_info' => $variante ? $this->getVariantInfo($variante) : null, 'cantidad' => $cantidad, 'precio_unitario' => $precio, 'subtotal' => $subtotal, 'DetalleCompra' => $row['observaciones'] ?? $row['detalle'] ?? '', ]; return $result; } /** * Obtiene información formateada de la variante */ private function getVariantInfo($variante): string { $colorName = $variante->color?->name ?? 'Sin color'; $sizeName = $variante->size?->name ?? 'Sin talla'; return "$colorName / $sizeName"; } /** * Normaliza las claves del array */ private function normalizeKeys(array $row): array { $normalized = []; $mappings = [ 'codigo_de_barras' => 'codigo_barras', 'codigo' => 'codigo_barras', 'barcode' => 'codigo_barras', 'precio' => 'precio_unitario', 'precio_unit' => 'precio_unitario', 'observacion' => 'observaciones', 'detalle' => 'observaciones', 'cantidad' => 'cantidad', 'cant' => 'cantidad', 'qty' => 'cantidad', 'producto' => 'producto', 'nombre' => 'producto', 'nombre_producto' => 'producto', 'articulo' => 'producto', 'bodega' => 'bodega', ]; foreach ($row as $key => $value) { $normalizedKey = strtolower(trim($key)); $normalizedKey = str_replace(' ', '_', $normalizedKey); $normalizedKey = $this->removeAccents($normalizedKey); if (isset($mappings[$normalizedKey])) { $normalized[$mappings[$normalizedKey]] = $value; } else { $normalized[$normalizedKey] = $value; } } return $normalized; } /** * Elimina tildes */ private function removeAccents($string) { $unwanted = [ 'á' => 'a', 'é' => 'e', 'í' => 'i', 'ó' => 'o', 'ú' => 'u', 'Á' => 'A', 'É' => 'E', 'Í' => 'I', 'Ó' => 'O', 'Ú' => 'U', 'ñ' => 'n', 'Ñ' => 'N', ]; return strtr($string, $unwanted); } /** * Reglas de validación */ public function rules(): array { return [ 'codigo_barras' => 'nullable|string', 'producto' => 'nullable|string', 'bodega' => 'nullable|string', 'cantidad' => 'nullable|integer|min:1', 'precio_unitario' => 'nullable|numeric|min:0', 'observaciones' => 'nullable|string', ]; } /** * Obtiene los datos de vista preliminar */ public function getPreviewData(): array { return $this->previewData; } /** * Obtiene estadísticas de la importación */ public function getStats(): array { $valid = collect($this->previewData)->filter(fn($item) => $item['valid'])->count(); $invalid = collect($this->previewData)->filter(fn($item) => !$item['valid'])->count(); $productosCreados = collect($this->previewData)->filter(fn($item) => isset($item['producto_creado']) && $item['producto_creado'])->count(); $total = count($this->previewData); $totalAmount = collect($this->previewData) ->where('valid', true) ->sum(fn($item) => $item['data']['subtotal']); return [ 'total' => $total, 'valid' => $valid, 'invalid' => $invalid, 'productos_creados' => $productosCreados, 'total_amount' => $totalAmount, ]; } /** * Convierte los datos preliminares a formato para guardar */ public function getDetallesForSave(): array { return collect($this->previewData) ->filter(fn($item) => $item['valid']) ->map(fn($item) => [ 'bodega_id' => $item['data']['bodega_id'], 'producto_id' => $item['data']['producto_id'], 'variante_id' => $item['data']['variante_id'], 'cantidad' => $item['data']['cantidad'], 'precio_unitario' => $item['data']['precio_unitario'], 'subtotal' => $item['data']['subtotal'], 'DetalleCompra' => $item['data']['DetalleCompra'], 'producto_nombre_snapshot' => $item['data']['producto_nombre'], 'variante_info_snapshot' => $item['data']['variante_info'], ]) ->toArray(); } }