Files
pos_heidiver/app/Imports/CompraDetallesImport.php
2026-01-06 15:35:59 -05:00

296 lines
10 KiB
PHP

<?php
namespace App\Imports;
use App\Models\Producto;
use App\Models\ProductVariant;
use App\Models\Bodega;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
use Illuminate\Support\Collection;
class CompraDetallesImport implements ToCollection, WithHeadingRow, WithValidation
{
protected $previewData = [];
protected $errors = [];
/**
* Procesa la colección y genera vista preliminar
*/
public function collection(Collection $rows)
{
$this->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();
}
}