diff --git a/app/Http/Controllers/BLInventarioController.php b/app/Http/Controllers/BLInventarioController.php index 70ea462..e33f700 100644 --- a/app/Http/Controllers/BLInventarioController.php +++ b/app/Http/Controllers/BLInventarioController.php @@ -24,21 +24,44 @@ public function index() { $productos = BlProducto::with([ 'color', - 'empaques.inventarioDetalle.posicion.nivel.estanteria' + 'empaques.inventarioDetalle.zona.nivel.estanteria' ]) ->get() ->map(function ($producto) { // Obtener todas las ubicaciones donde está este producto $ubicaciones = []; + $zonasUnicas = []; + $estanteriasUnicas = []; // NUEVO: para almacenar códigos de estanterías + foreach ($producto->empaques as $empaque) { foreach ($empaque->inventarioDetalle as $inventario) { - if ($inventario->posicion && $inventario->posicion->nivel && $inventario->posicion->nivel->estanteria) { - $ubicaciones[] = $inventario->posicion->nivel->estanteria->nombre; + if ($inventario->zona && $inventario->zona->nivel && $inventario->zona->nivel->estanteria) { + $estanteria = $inventario->zona->nivel->estanteria; + $nivel = $inventario->zona->nivel; + $zona = $inventario->zona; + + // Crear ubicación legible + $ubicacionLegible = $estanteria->nombre . ' - ' . + $nivel->nivel . ' - ' . + 'Zona ' . $zona->zona; + + $ubicaciones[] = $ubicacionLegible; + + // Guardar información de zona única + $zonasUnicas[] = [ + 'estanteria_nombre' => $estanteria->nombre, + 'estanteria_codigo' => $estanteria->codigo, // EST-04, RACK-04, etc. + 'nivel_nombre' => $nivel->nivel, + 'zona_nombre' => $zona->zona, + 'codigo_completo' => $zona->codigo_completo + ]; + + // NUEVO: Guardar código de estantería para el plano + $estanteriasUnicas[] = $estanteria->codigo; } } } - // Si no tiene ubicación, mostrar "Sin ubicación" $estanteria = !empty($ubicaciones) ? implode(', ', array_unique($ubicaciones)) : 'Sin ubicación'; return [ @@ -49,9 +72,14 @@ public function index() 'descripcion' => $producto->descripcion, 'stock_total' => $producto->empaques ->where('estado', 'disponible') - ->sum('cantidad_unidades'), + ->sum('cantidad_por_empaque'), 'estanteria' => $estanteria, 'tiene_ubicacion' => !empty($ubicaciones), + 'ubicaciones_detalladas' => $zonasUnicas, + 'estanterias' => array_unique(array_column($zonasUnicas, 'estanteria_nombre')), + 'zonas_completas' => array_unique(array_column($zonasUnicas, 'codigo_completo')), + // NUEVO: Array de códigos de estantería para el plano + 'estanterias_codigos' => array_unique($estanteriasUnicas), ]; }); diff --git a/app/Models/BlInventarioDetalle.php b/app/Models/BlInventarioDetalle.php index 7be90aa..d32e460 100644 --- a/app/Models/BlInventarioDetalle.php +++ b/app/Models/BlInventarioDetalle.php @@ -11,8 +11,13 @@ class BlInventarioDetalle extends Model protected $table = 'bl_inventario_detalle'; protected $fillable = [ - 'empaque_id', 'posicion_id', 'cantidad_actual', - 'fecha_ubicacion', 'fecha_vencimiento', 'estado', 'notas' + 'empaque_id', + 'zona_id', // Cambiado: posicion_id → zona_id + 'cantidad_actual', + 'fecha_ubicacion', + 'fecha_vencimiento', + 'estado', + 'notas' ]; protected $dates = ['fecha_ubicacion', 'fecha_vencimiento']; @@ -23,10 +28,10 @@ public function empaque() return $this->belongsTo(BlEmpaque::class, 'empaque_id'); } - // Relación con posición - public function posicion() + // NUEVA RELACIÓN con zona (reemplaza posicion) + public function zona() { - return $this->belongsTo(BlPosicion::class, 'posicion_id'); + return $this->belongsTo(BlZonaNivel::class, 'zona_id'); } // Relación con producto (a través de empaque) @@ -42,7 +47,7 @@ public function producto() ); } - // Scopes útiles + // Scopes útiles ACTUALIZADOS public function scopeDisponibles($query) { return $query->where('estado', 'disponible'); @@ -50,15 +55,35 @@ public function scopeDisponibles($query) public function scopeEnEstanteria($query, $estanteriaId) { - return $query->whereHas('posicion.nivel.estanteria', function($q) use ($estanteriaId) { + return $query->whereHas('zona.nivel.estanteria', function($q) use ($estanteriaId) { $q->where('id', $estanteriaId); }); } + public function scopeEnZona($query, $zonaId) + { + return $query->where('zona_id', $zonaId); + } + public function scopeConProducto($query, $productoId) { return $query->whereHas('empaque', function($q) use ($productoId) { $q->where('producto_id', $productoId); }); } + + // Nuevo scope para buscar por código de zona + public function scopeEnZonaCodigo($query, $codigoZona) + { + return $query->whereHas('zona', function($q) use ($codigoZona) { + $q->where('codigo_completo', $codigoZona); + }); + } + + // Scope para productos próximos a vencer + public function scopeProximosAVencer($query, $dias = 30) + { + return $query->whereDate('fecha_vencimiento', '<=', now()->addDays($dias)) + ->where('estado', 'disponible'); + } } \ No newline at end of file diff --git a/app/Models/BlZonaNivel.php b/app/Models/BlZonaNivel.php new file mode 100644 index 0000000..56c3f01 --- /dev/null +++ b/app/Models/BlZonaNivel.php @@ -0,0 +1,65 @@ +belongsTo(BlNivelEstanteria::class, 'nivel_id'); + } + + // Relación con inventario detalle + public function inventarioDetalle() + { + return $this->hasMany(BlInventarioDetalle::class, 'zona_id'); + } + + // Relación con estantería (a través de nivel) + public function estanteria() + { + return $this->hasOneThrough( + BlEstanteria::class, + BlNivelEstanteria::class, + 'id', // Foreign key on bl_niveles_estanteria + 'id', // Foreign key on bl_estanterias + 'nivel_id', // Local key on bl_zonas_nivel + 'estanteria_id' // Local key on bl_niveles_estanteria + ); + } + + // Scope para zonas activas + public function scopeActivas($query) + { + return $query->where('activa', true); + } + + // Scope para zonas con capacidad disponible + public function scopeConCapacidad($query, $cantidad = 1) + { + return $query->whereRaw('(capacidad_maxima - productos_actuales) >= ?', [$cantidad]); + } + + // Método para calcular espacio disponible + public function getEspacioDisponibleAttribute() + { + return $this->capacidad_maxima - $this->productos_actuales; + } +} \ No newline at end of file diff --git a/database/migrations/2025_09_24_180852_bl_estanterias_table.php b/database/migrations/2025_09_24_180852_bl_estanterias_table.php index de7cc25..d7d6923 100644 --- a/database/migrations/2025_09_24_180852_bl_estanterias_table.php +++ b/database/migrations/2025_09_24_180852_bl_estanterias_table.php @@ -30,6 +30,6 @@ public function up(): void */ public function down(): void { - // + Schema::dropIfExists('bl_estanterias'); } }; diff --git a/database/migrations/2025_09_24_180942_bl_niveles_estanteria_table.php b/database/migrations/2025_09_24_180942_bl_niveles_estanteria_table.php index 2bde446..3f42702 100644 --- a/database/migrations/2025_09_24_180942_bl_niveles_estanteria_table.php +++ b/database/migrations/2025_09_24_180942_bl_niveles_estanteria_table.php @@ -30,6 +30,6 @@ public function up(): void */ public function down(): void { - // + Schema::dropIfExists('bl_niveles_estanteria'); } }; diff --git a/database/migrations/2025_09_24_181023_bl_posiciones_table.php b/database/migrations/2025_09_24_181023_bl_posiciones_table.php index e421490..0ee2c49 100644 --- a/database/migrations/2025_09_24_181023_bl_posiciones_table.php +++ b/database/migrations/2025_09_24_181023_bl_posiciones_table.php @@ -31,6 +31,6 @@ public function up(): void */ public function down(): void { - // + Schema::dropIfExists('bl_posiciones'); } }; diff --git a/database/migrations/2025_09_24_181042_bl_inventario_detalle_table.php b/database/migrations/2025_09_24_181042_bl_inventario_detalle_table.php index 4cdc5b3..69e359a 100644 --- a/database/migrations/2025_09_24_181042_bl_inventario_detalle_table.php +++ b/database/migrations/2025_09_24_181042_bl_inventario_detalle_table.php @@ -32,6 +32,6 @@ public function up(): void */ public function down(): void { - // + Schema::dropIfExists('bl_inventario_detalle'); } }; diff --git a/database/migrations/2025_09_28_152407_drop_bl_posiciones_table.php b/database/migrations/2025_09_28_152407_drop_bl_posiciones_table.php new file mode 100644 index 0000000..22de0f6 --- /dev/null +++ b/database/migrations/2025_09_28_152407_drop_bl_posiciones_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('nivel_id')->constrained('bl_niveles_estanteria'); + $table->string('posicion'); + $table->string('codigo_completo')->unique(); + $table->decimal('ancho', 8, 2)->nullable(); + $table->decimal('alto', 8, 2)->nullable(); + $table->decimal('profundidad', 8, 2)->nullable(); + $table->integer('capacidad_maxima')->default(1); + $table->boolean('ocupada')->default(false); + $table->boolean('activa')->default(true); + $table->timestamps(); + }); + } +}; diff --git a/database/migrations/2025_09_28_152633_bl_zonas_nivel_table.php b/database/migrations/2025_09_28_152633_bl_zonas_nivel_table.php new file mode 100644 index 0000000..cbf195a --- /dev/null +++ b/database/migrations/2025_09_28_152633_bl_zonas_nivel_table.php @@ -0,0 +1,36 @@ +id(); + $table->foreignId('nivel_id')->constrained('bl_niveles_estanteria'); + $table->string('zona'); // 'A', 'B' + $table->string('codigo_completo')->unique(); // Ej: 'RACK-01-B-A' + $table->integer('capacidad_maxima')->default(5000); // mitad de la capacidad del nivel + $table->integer('productos_actuales')->default(0); + $table->text('descripcion')->nullable(); // "Frente", "Fondo", etc. + $table->boolean('activa')->default(true); + $table->timestamps(); + + $table->unique(['nivel_id', 'zona']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('bl_zonas_nivel'); + } +}; diff --git a/database/migrations/2025_09_28_153005_change_posicion_to_zona_in_inventario_detalle.php b/database/migrations/2025_09_28_153005_change_posicion_to_zona_in_inventario_detalle.php new file mode 100644 index 0000000..447d164 --- /dev/null +++ b/database/migrations/2025_09_28_153005_change_posicion_to_zona_in_inventario_detalle.php @@ -0,0 +1,72 @@ +id(); + $table->foreignId('empaque_id')->constrained('bl_empaques'); + $table->foreignId('zona_id')->constrained('bl_zonas_nivel'); // NUEVO CAMPO + $table->integer('cantidad_actual')->default(0); + $table->date('fecha_ubicacion'); + $table->date('fecha_vencimiento')->nullable(); + $table->enum('estado', ['disponible', 'reservado', 'danado', 'caducado'])->default('disponible'); + $table->text('notas')->nullable(); + $table->timestamps(); + + $table->unique(['empaque_id', 'zona_id']); + $table->index(['zona_id', 'estado']); + $table->index('fecha_vencimiento'); + }); + + // 2️⃣ Copiar los datos desde la tabla antigua + // Aquí asumimos que existe un mapeo directo posicón_id → zona_id + // Si necesitas un mapeo distinto, ajusta el SELECT + DB::statement(' + INSERT INTO bl_inventario_detalle_new (id, empaque_id, zona_id, cantidad_actual, fecha_ubicacion, fecha_vencimiento, estado, notas, created_at, updated_at) + SELECT id, empaque_id, posicion_id, cantidad_actual, fecha_ubicacion, fecha_vencimiento, estado, notas, created_at, updated_at + FROM bl_inventario_detalle + '); + + // 3️⃣ Eliminar la tabla antigua + Schema::dropIfExists('bl_inventario_detalle'); + + // 4️⃣ Renombrar la nueva tabla con el nombre original + Schema::rename('bl_inventario_detalle_new', 'bl_inventario_detalle'); + } + + public function down(): void + { + // Revertir: volver a la tabla con posicion_id + Schema::create('bl_inventario_detalle_old', function (Blueprint $table) { + $table->id(); + $table->foreignId('empaque_id')->constrained('bl_empaques'); + $table->foreignId('posicion_id')->constrained('bl_posiciones'); + $table->integer('cantidad_actual')->default(0); + $table->date('fecha_ubicacion'); + $table->date('fecha_vencimiento')->nullable(); + $table->enum('estado', ['disponible', 'reservado', 'danado', 'caducado'])->default('disponible'); + $table->text('notas')->nullable(); + $table->timestamps(); + + $table->unique(['empaque_id', 'posicion_id']); + $table->index(['posicion_id', 'estado']); + }); + + DB::statement(' + INSERT INTO bl_inventario_detalle_old (id, empaque_id, posicion_id, cantidad_actual, fecha_ubicacion, fecha_vencimiento, estado, notas, created_at, updated_at) + SELECT id, empaque_id, zona_id, cantidad_actual, fecha_ubicacion, fecha_vencimiento, estado, notas, created_at, updated_at + FROM bl_inventario_detalle + '); + + Schema::dropIfExists('bl_inventario_detalle'); + Schema::rename('bl_inventario_detalle_old', 'bl_inventario_detalle'); + } +}; diff --git a/database/seeders/InventarioDetalleSeeder.php b/database/seeders/InventarioDetalleSeeder.php new file mode 100644 index 0000000..657d8d2 --- /dev/null +++ b/database/seeders/InventarioDetalleSeeder.php @@ -0,0 +1,137 @@ +where('estado', 'disponible') + ->get(); + + if ($empaques->isEmpty()) { + $this->command->warn('❌ No hay empaques disponibles para asignar a zonas.'); + return; + } + + // 2. Obtener zonas activas + $zonas = DB::table('bl_zonas_nivel') + ->where('activa', true) + ->get(); + + if ($zonas->isEmpty()) { + $this->command->warn('❌ No hay zonas activas disponibles.'); + return; + } + + $inventarioData = []; + $fechaBase = Carbon::now()->subMonths(6); // Fecha base para variar las fechas + + foreach ($empaques as $empaque) { + // Seleccionar una zona aleatoria + $zonaAleatoria = $zonas->random(); + + // Calcular fechas variadas + $fechaUbicacion = $fechaBase->copy()->addDays(rand(0, 180)); + $fechaVencimiento = $fechaUbicacion->copy()->addMonths(rand(6, 24)); + + // Determinar estado aleatorio (mayoría disponible) + $estados = ['disponible', 'disponible', 'disponible', 'reservado', 'disponible']; + $estado = $estados[array_rand($estados)]; + + // Cantidad aleatoria entre 1 y la capacidad máxima de la zona + $cantidadMaxima = min($empaque->cantidad_disponible ?? 100, $zonaAleatoria->capacidad_maxima); + $cantidadActual = rand(1, $cantidadMaxima); + + $inventarioData[] = [ + 'empaque_id' => $empaque->id, + 'zona_id' => $zonaAleatoria->id, + 'cantidad_actual' => $cantidadActual, + 'fecha_ubicacion' => $fechaUbicacion->format('Y-m-d'), + 'fecha_vencimiento' => $fechaVencimiento->format('Y-m-d'), + 'estado' => $estado, + 'notas' => $this->generarNotasAleatorias($estado), + 'created_at' => now(), + 'updated_at' => now(), + ]; + + // Limitar a 50 registros para no saturar (puedes ajustar) + if (count($inventarioData) >= 50) { + break; + } + } + + // 3. Insertar los datos en bl_inventario_detalle + DB::table('bl_inventario_detalle')->insert($inventarioData); + + // 4. Actualizar el contador de productos_actuales en las zonas + $this->actualizarContadorZonas(); + + $this->command->info('✅ Seeder de inventario detalle ejecutado correctamente:'); + $this->command->info(' - ' . count($inventarioData) . ' registros creados en bl_inventario_detalle'); + $this->command->info(' - Empaques asignados aleatoriamente a zonas disponibles'); + $this->command->info(' - Contadores de zonas actualizados'); + + // Mostrar algunos ejemplos + if (count($inventarioData) > 0) { + $this->command->info(''); + $this->command->info('📋 Ejemplos de registros creados:'); + $ejemplos = array_slice($inventarioData, 0, 3); + foreach ($ejemplos as $ejemplo) { + $zonaCodigo = DB::table('bl_zonas_nivel') + ->where('id', $ejemplo['zona_id']) + ->value('codigo_completo'); + + $empaqueNombre = DB::table('bl_empaques') + ->where('id', $ejemplo['empaque_id']) + ->value('nombre'); + + $this->command->info(" - {$empaqueNombre} → {$zonaCodigo} ({$ejemplo['cantidad_actual']} unidades)"); + } + } + } + + /** + * Generar notas aleatorias basadas en el estado + */ + private function generarNotasAleatorias(string $estado): ?string + { + $notas = [ + 'disponible' => [null, 'Stock nuevo', 'Recién ingresado', 'En óptimas condiciones'], + 'reservado' => ['Reservado para pedido #' . rand(1000, 9999), 'Cliente: Cliente ' . rand(1, 10)], + 'danado' => ['Daño en empaque', 'Producto golpeado', 'Requiere revisión'], + 'caducado' => ['Próximo a vencer', 'Requiere rotación'], + ]; + + $opciones = $notas[$estado] ?? [null]; + return $opciones[array_rand($opciones)]; + } + + /** + * Actualizar el contador de productos_actuales en las zonas + */ + private function actualizarContadorZonas(): void + { + // Obtener el conteo actual por zona + $conteoPorZona = DB::table('bl_inventario_detalle') + ->select('zona_id', DB::raw('SUM(cantidad_actual) as total_productos')) + ->where('estado', 'disponible') + ->groupBy('zona_id') + ->get(); + + foreach ($conteoPorZona as $conteo) { + DB::table('bl_zonas_nivel') + ->where('id', $conteo->zona_id) + ->update(['productos_actuales' => $conteo->total_productos]); + } + } +} \ No newline at end of file diff --git a/database/seeders/NivelesEstanteriaSeeder.php b/database/seeders/NivelesEstanteriaSeeder.php new file mode 100644 index 0000000..81e210b --- /dev/null +++ b/database/seeders/NivelesEstanteriaSeeder.php @@ -0,0 +1,66 @@ +get(); + + $nivelesData = []; + + foreach ($estanterias as $estanteria) { + // Crear 3 niveles para cada estantería + $niveles = [ + [ + 'estanteria_id' => $estanteria->id, + 'nivel' => 'Bajo', + 'codigo' => $estanteria->codigo . '-B', + 'capacidad_maxima' => 10000, + 'orden' => 1, + 'activo' => true, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'estanteria_id' => $estanteria->id, + 'nivel' => 'Medio', + 'codigo' => $estanteria->codigo . '-M', + 'capacidad_maxima' => 10000, + 'orden' => 2, + 'activo' => true, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'estanteria_id' => $estanteria->id, + 'nivel' => 'Alto', + 'codigo' => $estanteria->codigo . '-A', + 'capacidad_maxima' => 10000, + 'orden' => 3, + 'activo' => true, + 'created_at' => now(), + 'updated_at' => now(), + ], + ]; + + $nivelesData = array_merge($nivelesData, $niveles); + } + + // Insertar todos los niveles + DB::table('bl_niveles_estanteria')->insert($nivelesData); + + $this->command->info('Seeder de niveles ejecutado correctamente:'); + $this->command->info('- ' . count($estanterias) . ' estanterías procesadas'); + $this->command->info('- ' . count($nivelesData) . ' niveles creados'); + $this->command->info('- Capacidad máxima: 10,000 unidades por nivel'); + } +} \ No newline at end of file diff --git a/database/seeders/ZonasNivelSeeder.php b/database/seeders/ZonasNivelSeeder.php new file mode 100644 index 0000000..faa2044 --- /dev/null +++ b/database/seeders/ZonasNivelSeeder.php @@ -0,0 +1,80 @@ +get(); + + $zonasData = []; + + foreach ($niveles as $nivel) { + // Obtener información de la estantería padre para determinar el tipo + $estanteria = DB::table('bl_estanterias') + ->where('id', $nivel->estanteria_id) + ->first(); + + // Determinar descripción basada en el tipo de estantería + $descripcionZonaA = $estanteria->tipo === 'vertical' ? 'Lado izquierdo' : 'Zona frontal'; + $descripcionZonaB = $estanteria->tipo === 'vertical' ? 'Lado derecho' : 'Zona trasera'; + + // Calcular capacidad máxima por zona (mitad de la capacidad del nivel) + $capacidadPorZona = floor($nivel->capacidad_maxima / 2); + + // Crear 2 zonas (A y B) para cada nivel + $zonas = [ + [ + 'nivel_id' => $nivel->id, + 'zona' => 'A', + 'codigo_completo' => $nivel->codigo . '-A', + 'capacidad_maxima' => $capacidadPorZona, + 'productos_actuales' => 0, + 'descripcion' => $descripcionZonaA, + 'activa' => true, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'nivel_id' => $nivel->id, + 'zona' => 'B', + 'codigo_completo' => $nivel->codigo . '-B', + 'capacidad_maxima' => $capacidadPorZona, + 'productos_actuales' => 0, + 'descripcion' => $descripcionZonaB, + 'activa' => true, + 'created_at' => now(), + 'updated_at' => now(), + ], + ]; + + $zonasData = array_merge($zonasData, $zonas); + } + + // Insertar todas las zonas + DB::table('bl_zonas_nivel')->insert($zonasData); + + $this->command->info('✅ Seeder de zonas de nivel ejecutado correctamente:'); + $this->command->info(' - ' . count($niveles) . ' niveles procesados'); + $this->command->info(' - ' . count($zonasData) . ' zonas creadas (A y B por cada nivel)'); + $this->command->info(' - Capacidad por zona: calculada automáticamente según el nivel'); + + // Mostrar algunos ejemplos de códigos generados + if (count($zonasData) > 0) { + $this->command->info(''); + $this->command->info('📋 Ejemplos de códigos generados:'); + $this->command->info(' - ' . $zonasData[0]['codigo_completo']); + $this->command->info(' - ' . $zonasData[1]['codigo_completo']); + $this->command->info(' - ' . $zonasData[4]['codigo_completo']); + $this->command->info(' - ' . $zonasData[5]['codigo_completo']); + } + } +} \ No newline at end of file diff --git a/resources/js/components/BL/ListaInventario.jsx b/resources/js/components/BL/ListaInventario.jsx index 9d68941..a1b7fad 100644 --- a/resources/js/components/BL/ListaInventario.jsx +++ b/resources/js/components/BL/ListaInventario.jsx @@ -14,6 +14,8 @@ const ListaInventario = ({ onLimpiarFiltros, onEstanteriaClick }) => { + console.log(productosFiltrados); + const [expandida, setExpandida] = useState(true); const toggleExpandida = () => { @@ -112,33 +114,51 @@ const ListaInventario = ({ {/* Lista de productos */}
+ {productosFiltrados.length > 0 ? ( productosFiltrados.map((p) => (
onEstanteriaClick(p.estanteria)} + // onClick={() => onEstanteriaClick(p.estanterias_codigos?.[0] || p.estanteria)} > -
-
- {p.descripcion || `${p.tipo_producto} ${p.color_nombre} ${p.tamanio}`} - {!p.tiene_ubicacion && ( - ⚠️ - )} +
+ {/* Información del producto */} +
+
+ {p.descripcion || `${p.tipo_producto} ${p.color_nombre} ${p.tamanio}`} +
+
+ {p.tipo_producto} • {p.color_nombre} • {p.tamanio} +
-
- {p.tipo_producto} • {p.color_nombre} • {p.tamanio} + + {/* Stock */} +
+
+ {p.stock_total}u +
-
-
- {p.estanteria} + + {/* Ubicaciones como badges con scroll */} +
+
+ {p.estanteria.split(', ').map((ubicacion, index) => ( + + {ubicacion} + + ))}
-
{p.stock_total}u
)) diff --git a/resources/js/components/BL/PlanoAlmacen.jsx b/resources/js/components/BL/PlanoAlmacen.jsx index 80c5074..be08930 100644 --- a/resources/js/components/BL/PlanoAlmacen.jsx +++ b/resources/js/components/BL/PlanoAlmacen.jsx @@ -2,9 +2,46 @@ import React from 'react'; const PlanoAlmacen = ({ onEstanteriaClick, productos }) => { - // Calcular productos por estantería - const productosPorEstanteria = (nombreEstanteria) => { - return productos.filter(p => p.estanteria === nombreEstanteria).length; + // Función mejorada para contar productos por estantería + const productosPorEstanteria = (codigoEstanteria) => { + return productos.filter(p => { + // PRIMERO: Buscar en el nuevo array estanterias_codigos + if (p.estanterias_codigos && p.estanterias_codigos.length > 0) { + return p.estanterias_codigos.includes(codigoEstanteria); + } + // SEGUNDO: Buscar en ubicaciones_detalladas + if (p.ubicaciones_detalladas && p.ubicaciones_detalladas.length > 0) { + return p.ubicaciones_detalladas.some(ubicacion => + ubicacion.estanteria_codigo === codigoEstanteria + ); + } + // TERCERO: Fallback - buscar en la cadena de texto + return p.estanteria.includes(codigoEstanteria); + }).length; + }; + + // Función para obtener el nombre legible de la estantería + const getNombreEstanteria = (codigo) => { + const nombres = { + 'EST-01': 'Estantería 1', + 'EST-02': 'Estantería 2', + 'EST-03': 'Estantería 3', + 'EST-04': 'Estantería 4', + 'EST-05': 'Estantería 5', + 'EST-06': 'Estantería 6', + 'EST-07': 'Estantería 7', + 'RACK-01': 'Rack 1', + 'RACK-02': 'Rack 2', + 'RACK-03': 'Rack 3', + 'RACK-04': 'Rack 4', + 'RACK-05': 'Rack 5', + 'RACK-06': 'Rack 6', + 'RACK-07': 'Rack 7', + 'RACK-08': 'Rack 8', + 'RACK-09': 'Rack 9', + 'RACK-10': 'Rack 10' + }; + return nombres[codigo] || codigo; }; return ( @@ -26,12 +63,12 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => {
onEstanteriaClick("Estantería 1")} + onClick={() => onEstanteriaClick("EST-01")} > - E1 - Estantería 1 + EST-01 + {getNombreEstanteria("EST-01")} - {productosPorEstanteria("Estantería 1")} productos + {productosPorEstanteria("EST-01")} productos
@@ -62,21 +99,21 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => {
{/* MITAD IZQUIERDA INFERIOR */}
- {/* RACK 10 (R10) - OCUPA TODA LA ALTURA */} + {/* RACK 09 (R09) - OCUPA TODA LA ALTURA */}
onEstanteriaClick("Rack 10")} + onClick={() => onEstanteriaClick("RACK-09")} > - R10 - Rack 10 + R09 + {getNombreEstanteria("RACK-09")} - {productosPorEstanteria("Rack 10")} productos + {productosPorEstanteria("RACK-09")} productos
- {/* ZONA DERECHA DE R10 */} + {/* ZONA DERECHA DE R09 */}
{/* ESPACIO VACÍO (7%) */} @@ -88,12 +125,12 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => {
onEstanteriaClick("Estantería 2")} + onClick={() => onEstanteriaClick("EST-02")} > - E2 - Estantería 2 + EST-02 + {getNombreEstanteria("EST-02")} - {productosPorEstanteria("Estantería 2")} productos + {productosPorEstanteria("EST-02")} productos
@@ -102,12 +139,12 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => {
onEstanteriaClick("Estantería 3")} + onClick={() => onEstanteriaClick("EST-03")} > - E3 - Estantería 3 + EST-03 + {getNombreEstanteria("EST-03")} - {productosPorEstanteria("Estantería 3")} productos + {productosPorEstanteria("EST-03")} productos
@@ -117,30 +154,30 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => {

Pasillo

- {/* E5 (12%) */} + {/* E4 (12%) */}
onEstanteriaClick("Estantería 5")} + onClick={() => onEstanteriaClick("EST-04")} > - E5 - Estantería 5 + EST-04 + {getNombreEstanteria("EST-04")} - {productosPorEstanteria("Estantería 5")} productos + {productosPorEstanteria("EST-04")} productos
- {/* E4 (12%) */} + {/* E5 (12%) */}
onEstanteriaClick("Estantería 4")} + onClick={() => onEstanteriaClick("EST-05")} > - E4 - Estantería 4 + EST-05 + {getNombreEstanteria("EST-05")} - {productosPorEstanteria("Estantería 4")} productos + {productosPorEstanteria("EST-05")} productos
@@ -154,35 +191,34 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => {
onEstanteriaClick("Estantería 6")} + onClick={() => onEstanteriaClick("EST-06")} > - E6 - Estantería 6 + EST-06 + {getNombreEstanteria("EST-06")} - {productosPorEstanteria("Estantería 6")} productos + {productosPorEstanteria("EST-06")} productos
- {/* MITAD DERECHA INFERIOR: VACÍA */} {/* MITAD DERECHA INFERIOR */} -
+
{/* Zona superior de racks */}
- {/* Rack 9 */} + {/* Rack 8 */}
onEstanteriaClick("Rack 9")} + onClick={() => onEstanteriaClick("RACK-08")} > - R9 - Rack 9 + R08 + {getNombreEstanteria("RACK-08")} - {productosPorEstanteria("Rack 9")} productos + {productosPorEstanteria("RACK-08")} productos
@@ -190,30 +226,30 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => { {/* Espacio 5% */}
- {/* Rack 8 */} + {/* Rack 7 */}
onEstanteriaClick("Rack 8")} + onClick={() => onEstanteriaClick("RACK-07")} > - R8 - Rack 8 + R07 + {getNombreEstanteria("RACK-07")} - {productosPorEstanteria("Rack 8")} productos + {productosPorEstanteria("RACK-07")} productos
- {/* Rack 7 */} + {/* Rack 6 */}
onEstanteriaClick("Rack 7")} + onClick={() => onEstanteriaClick("RACK-06")} > - R7 - Rack 7 + R06 + {getNombreEstanteria("RACK-06")} - {productosPorEstanteria("Rack 7")} productos + {productosPorEstanteria("RACK-06")} productos
@@ -221,30 +257,30 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => { {/* Espacio 5% */}
- {/* Rack 6 */} + {/* Rack 5 */}
onEstanteriaClick("Rack 6")} + onClick={() => onEstanteriaClick("RACK-05")} > - R6 - Rack 6 + R05 + {getNombreEstanteria("RACK-05")} - {productosPorEstanteria("Rack 6")} productos + {productosPorEstanteria("RACK-05")} productos
- {/* Rack 5 */} + {/* Rack 4 */}
onEstanteriaClick("Rack 5")} + onClick={() => onEstanteriaClick("RACK-04")} > - R5 - Rack 5 + R04 + {getNombreEstanteria("RACK-04")} - {productosPorEstanteria("Rack 5")} productos + {productosPorEstanteria("RACK-04")} productos
@@ -252,30 +288,30 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => { {/* Espacio 5% */}
- {/* Rack 4 */} + {/* Rack 3 */}
onEstanteriaClick("Rack 4")} + onClick={() => onEstanteriaClick("RACK-03")} > - R4 - Rack 4 + R03 + {getNombreEstanteria("RACK-03")} - {productosPorEstanteria("Rack 4")} productos + {productosPorEstanteria("RACK-03")} productos
- {/* Rack 3 */} + {/* Rack 2 */}
onEstanteriaClick("Rack 3")} + onClick={() => onEstanteriaClick("RACK-02")} > - R3 - Rack 3 + R02 + {getNombreEstanteria("RACK-02")} - {productosPorEstanteria("Rack 3")} productos + {productosPorEstanteria("RACK-02")} productos
@@ -283,16 +319,16 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => { {/* Espacio 5% */}
- {/* Rack 2 */} + {/* Rack 1 */}
onEstanteriaClick("Rack 2")} + onClick={() => onEstanteriaClick("RACK-01")} > - R2 - Rack 2 + R01 + {getNombreEstanteria("RACK-01")} - {productosPorEstanteria("Rack 2")} productos + {productosPorEstanteria("RACK-01")} productos
@@ -302,22 +338,20 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => {
onEstanteriaClick("Estantería 7")} + onClick={() => onEstanteriaClick("EST-07")} > - E7 - Estantería 7 + EST-07 + {getNombreEstanteria("EST-07")} - {productosPorEstanteria("Estantería 7")} productos + {productosPorEstanteria("EST-07")} productos
-
- {/* ÁREA SIN UBICACIÓN (ESQUINA INFERIOR DERECHA) */} - {productos.some(p => p.estanteria === "Sin ubicación") && ( + {productos.some(p => !p.tiene_ubicacion) && (
onEstanteriaClick("Sin ubicación")} @@ -325,7 +359,7 @@ const PlanoAlmacen = ({ onEstanteriaClick, productos }) => {
📦 Sin ubicación - {productosPorEstanteria("Sin ubicación")} productos + {productos.filter(p => !p.tiene_ubicacion).length} productos
diff --git a/resources/js/components/BL/hooks/useFiltrosInventario.js b/resources/js/components/BL/hooks/useFiltrosInventario.js index c9ea3cd..77be8e8 100644 --- a/resources/js/components/BL/hooks/useFiltrosInventario.js +++ b/resources/js/components/BL/hooks/useFiltrosInventario.js @@ -10,15 +10,24 @@ export const useFiltrosInventario = (productos) => { // Extraer categorías únicas const categorias = ["Todos", ...new Set(productos.map(p => p.tipo_producto))]; - // Extraer estanterías únicas - const estanteriasUnicas = [...new Set(productos.map(p => p.estanteria))]; + // Extraer estanterías únicas para el filtro (usando estanterias_codigos) + const estanteriasUnicas = [...new Set( + productos.flatMap(p => p.estanterias_codigos || [p.estanteria]) + )].filter(Boolean); // Filtrar productos useEffect(() => { let resultado = productos; if (filtro) { - resultado = resultado.filter(p => p.estanteria === filtro); + resultado = resultado.filter(p => { + // Buscar en estanterias_codigos (nuevo array) + if (p.estanterias_codigos && p.estanterias_codigos.length > 0) { + return p.estanterias_codigos.includes(filtro); + } + // Fallback: buscar en la cadena de estantería + return p.estanteria.includes(filtro); + }); } if (busqueda) { @@ -27,7 +36,11 @@ export const useFiltrosInventario = (productos) => { p.descripcion?.toLowerCase().includes(busquedaLower) || p.tipo_producto?.toLowerCase().includes(busquedaLower) || p.color_nombre?.toLowerCase().includes(busquedaLower) || - p.estanteria?.toLowerCase().includes(busquedaLower) + p.estanteria?.toLowerCase().includes(busquedaLower) || + // También buscar en códigos de estantería + (p.estanterias_codigos && p.estanterias_codigos.some(codigo => + codigo.toLowerCase().includes(busquedaLower) + )) ); } diff --git a/resources/js/pages/BLInventario.jsx b/resources/js/pages/BLInventario.jsx index 0a88c59..86cdc24 100644 --- a/resources/js/pages/BLInventario.jsx +++ b/resources/js/pages/BLInventario.jsx @@ -15,6 +15,8 @@ const breadcrumbs = [ ]; export default function PlanoInventario({ productos }) { + console.log(productos); + const { isMobile } = useDispositivo(); const {