Overview
The Rack Layout module provides visual warehouse organization, automatically mapping inventory items to physical storage locations based on numeric ranges.
RackConfig Data Model
Complete rack structure from inventory.models.ts:90-108:
Show Rack Interfaces (TypeScript)
export interface RackBox {
label : string ; // Display label (e.g., "1081-1135")
min ?: number ; // Minimum location number in range
max ?: number ; // Maximum location number in range
items : ( CliseItem | DieItem )[]; // Items currently in this box
}
export interface RackLevel {
levelNumber : number ; // Level/shelf number (1=bottom, 3=top)
boxes : RackBox []; // Boxes on this level
}
export interface RackConfig {
id : string ; // Unique rack identifier
name : string ; // Display name
type : 'clise' | 'die' ; // Item type this rack stores
levels : RackLevel []; // Rack shelves from bottom to top
orientation : 'vertical' | 'horizontal' ; // Display orientation
}
Field Descriptions
Human-readable range label displayed on rack boxes. Examples:
"1081-1135" = Holds items with ubicacion 1081-1135
"A-01 (1-50)" = Zone A, position 01, items 1-50
Numeric range boundaries. Items with ubicacion values between min and max are auto-assigned to this box. { label : "1081-1135" , min : 1081 , max : 1135 }
Item with ubicacion: "1100" → automatically mapped to this box
Array of inventory items currently stored in this box. Populated by mapItemsToLayout() service function. items : [
{ id: "c1" , item: "CL-1100" , cliente: "Nestlé" , ... },
{ id: "c2" , item: "CL-1105" , cliente: "Gloria" , ... }
]
orientation
'vertical' | 'horizontal'
Visual layout direction:
vertical : Levels stacked top-to-bottom (typical tall racks)
horizontal : Levels arranged left-to-right (wide storage zones)
Rack Configurations
Default warehouse layout defined in inventory.service.ts:83-180:
Cliché Racks
3-level vertical rack with 2 boxes per level: {
id : 'CL1' ,
name : 'RACK CL-1 (861-1190)' ,
type : 'clise' ,
orientation : 'vertical' ,
levels : [
{ levelNumber: 3 , boxes: [
{ label: '1081-1135' , min: 1081 , max: 1135 , items: [] },
{ label: '1136-1190' , min: 1136 , max: 1190 , items: [] }
]},
{ levelNumber: 2 , boxes: [
{ label: '971-1025' , min: 971 , max: 1025 , items: [] },
{ label: '1026-1080' , min: 1026 , max: 1080 , items: [] }
]},
{ levelNumber: 1 , boxes: [
{ label: '861-915' , min: 861 , max: 915 , items: [] },
{ label: '916-970' , min: 916 , max: 970 , items: [] }
]}
]
}
Capacity : 330 items per box × 6 boxes = 1,980 total capacity
3-level vertical rack:
Level 3: 1411-1465, 1466-1520
Level 2: 1301-1355, 1356-1410
Level 1: 1191-1245, 1246-1300
3-level vertical rack:
Level 3: 1741-1795, 1796-1850
Level 2: 1631-1685, 1686-1740
Level 1: 1521-1575, 1576-1630
3-level vertical rack:
Level 3: 2071-2125, 2126-2180
Level 2: 1961-2015, 2016-2070
Level 1: 1851-1905, 1906-1960
Horizontal zone layout (shelving): {
id : 'EST1' ,
name : 'ZONA A (0-200)' ,
type : 'clise' ,
orientation : 'horizontal' ,
levels : [
{ levelNumber: 1 , boxes: [
{ label: 'A-01 (1-50)' , min: 1 , max: 50 , items: [] },
{ label: 'A-02 (51-100)' , min: 51 , max: 100 , items: [] },
{ label: 'A-03 (101-150)' , min: 101 , max: 150 , items: [] },
{ label: 'A-04 (151-200)' , min: 151 , max: 200 , items: [] }
]}
]
}
Horizontal zone layout:
B-01 (201-250)
B-02 (251-300)
B-03 (301-350)
B-04 (351-400)
Die Racks
3-level vertical rack for dies: {
id : 'TRQ1' ,
name : 'RACK TRQ-1 (1-100)' ,
type : 'die' ,
orientation : 'vertical' ,
levels : [
{ levelNumber: 3 , boxes: [{ label: '1-33' , min: 1 , max: 33 , items: [] }] },
{ levelNumber: 2 , boxes: [{ label: '34-66' , min: 34 , max: 66 , items: [] }] },
{ levelNumber: 1 , boxes: [{ label: '67-100' , min: 67 , max: 100 , items: [] }] }
]
}
3-level vertical rack:
Level 3: 101-133
Level 2: 134-166
Level 1: 167-200
Reference: inventory.service.ts:83-180
Automatic Mapping
Items are auto-assigned to racks when imported or updated:
// inventory.service.ts:292-338
mapItemsToLayout () {
const currentLayout = this . layoutData ;
const clises = this . cliseItems ;
const dies = this . dieItems ;
// Clear existing assignments
currentLayout . forEach ( rack =>
rack . levels . forEach ( lvl =>
lvl . boxes . forEach ( box => box . items = [])
)
);
// Map Clises
clises . forEach ( item => {
if ( ! item . ubicacion ) return ;
const locationNumber = parseInt ( item . ubicacion . replace ( / \D / g , '' ));
if ( ! isNaN ( locationNumber )) {
for ( const rack of currentLayout ) {
if ( rack . type !== 'clise' ) continue ;
for ( const level of rack . levels ) {
for ( const box of level . boxes ) {
if ( box . min !== undefined && box . max !== undefined &&
locationNumber >= box . min && locationNumber <= box . max ) {
box . items . push ( item );
return ; // Stop after first match
}
}
}
}
}
});
// Map Dies (similar logic)
dies . forEach ( item => {
if ( ! item . ubicacion ) return ;
const locationNumber = parseInt ( item . ubicacion . replace ( / \D / g , '' ));
if ( ! isNaN ( locationNumber )) {
for ( const rack of currentLayout ) {
if ( rack . type !== 'die' ) continue ;
for ( const level of rack . levels ) {
for ( const box of level . boxes ) {
if ( box . min !== undefined && box . max !== undefined &&
locationNumber >= box . min && locationNumber <= box . max ) {
box . items . push ( item );
return ;
}
}
}
}
}
});
this . _layoutData . next ([ ... currentLayout ]);
}
Mapping Logic
Extract Numeric Value : parseInt(item.ubicacion.replace(/\D/g, ''))
"CL-1100" → 1100
"A-02" → 2
"TRQ-45" → 45
Match to Range : Find box where locationNumber >= min && locationNumber <= max
Assign Item : Add item to box.items array
Trigger Update : Emit new layout via BehaviorSubject
Items with non-numeric or out-of-range ubicacion values will not appear in the visualization. Ensure all items have valid numeric location codes.
UI Component
The InventoryMapComponent provides interactive visualization:
Tab Navigation
// inventory-map.component.ts:124
activeTab : 'clise' | 'die' = 'clise' ;
get cliseRacks () {
return this . layoutData . filter ( r => r . type === 'clise' );
}
get dieRacks () {
return this . layoutData . filter ( r => r . type === 'die' );
}
Reference: inventory-map.component.ts:120-136
Visual Rack Rendering
<!-- inventory-map.component.ts:46-62 -->
< div *ngIf = "activeTab === 'clise'" class = "animate-fadeIn" >
< div class = "flex flex-wrap gap-8" >
< div *ngFor = "let rack of cliseRacks" class = "flex flex-col" >
< h3 class = "text-center font-bold text-gray-700 mb-2 bg-gray-200 rounded py-1" >
{{ rack.name }}
</ h3 >
< div class = "flex border-4 border-gray-300 bg-white p-1 gap-1"
[class.flex-col] = "rack.orientation === 'vertical'" >
< div *ngFor = "let level of rack.levels" class = "flex gap-1"
[class.flex-col] = "rack.orientation === 'horizontal'" >
< div *ngFor = "let box of level.boxes"
class = "w-16 h-12 border border-gray-200 flex flex-col items-center justify-center text-[10px] cursor-pointer hover:bg-blue-50 transition-colors relative group"
[class.bg-green-100] = "box.items.length > 0"
(click) = "selectedBox = box" >
< span class = "font-bold text-gray-600" > {{ box.label }} </ span >
< span *ngIf = "box.items.length > 0" class = "text-xs font-black text-green-700" >
{{ box.items.length }}
</ span >
</ div >
</ div >
</ div >
</ div >
</ div >
</ div >
Visual Indicators
Empty Boxes : White background, gray border
Occupied Boxes : Green background (bg-green-100)
Item Count : Bold number showing items in box
Hover Effect : Blue highlight on mouseover
Clickable : Opens detail panel on click
Detail Panel
Clicking a box opens a sliding side panel:
// inventory-map.component.ts:86-112
selectedBox : RackBox | null = null ;
getItemLabel ( item : any ): string {
return item . item || item . serie || 'N/A' ;
}
getItemDesc ( item : any ): string {
return item . descripcion || item . medida || '' ;
}
Panel Features
Header : Shows box label and item count
Item Cards : Each item displays:
Primary identifier (item code or serie)
Cliente name
Description/medida
Ubicacion badge
Empty State : Message if box is empty
Close Button : X icon to dismiss panel
Panel slides in from right with animation:
@keyframes slideInRight {
from { transform : translateX ( 100 % ); }
to { transform : translateX ( 0 ); }
}
.animate-slideInRight { animation : slideInRight 0.3 s ease-out ; }
Reference: inventory-map.component.ts:86-113
Usage Examples
Find Item Location
Navigate to Mapa de Almacén
Click Inventory → Mapa de Almacén in sidebar
Select Tab
Choose Clisés or Troqueles tab
Locate Item
Green boxes contain items. Number shows quantity in each box.
View Details
Click box to see complete list of items with cliente and descriptions
Add New Rack
To expand warehouse capacity, add new RackConfig to the service:
// inventory.service.ts - Add to _layoutData array
{
id : 'CL5' ,
name : 'RACK CL-5 (2181-2510)' ,
type : 'clise' ,
orientation : 'vertical' ,
levels : [
{ levelNumber: 3 , boxes: [
{ label: '2401-2455' , min: 2401 , max: 2455 , items: [] },
{ label: '2456-2510' , min: 2456 , max: 2510 , items: [] }
]},
{ levelNumber: 2 , boxes: [
{ label: '2291-2345' , min: 2291 , max: 2345 , items: [] },
{ label: '2346-2400' , min: 2346 , max: 2400 , items: [] }
]},
{ levelNumber: 1 , boxes: [
{ label: '2181-2235' , min: 2181 , max: 2235 , items: [] },
{ label: '2236-2290' , min: 2236 , max: 2290 , items: [] }
]}
]
}
After adding racks, call mapItemsToLayout() to populate with existing inventory.
The detail modals for clisés and dies include embedded rack visualization:
<!-- inventory-clise.component.ts:563-586 -->
< div class = "flex flex-col gap-3" >
< div class = "flex justify-between items-center mb-1" >
< p class = "text-white text-sm font-bold uppercase tracking-wider" > Ubicación Física </ p >
< span class = "text-xs bg-[#1c2327] px-2 py-1 rounded text-[#9db0b9] border border-[#2a343b]" >
Planta Monterrey
</ span >
</ div >
< div class = "bg-[#1c2327] rounded-xl p-4 border border-[#2a343b] flex flex-col gap-4" >
< div class = "flex justify-between text-xs text-[#9db0b9] mb-1" >
< span > Estante {{ (currentClise.ubicacion || '---').split('-')[0] }} </ span >
< span > --- </ span >
</ div >
<!-- Visual Grid Placeholder (Static representation) -->
< div class = "grid grid-cols-4 gap-2 w-full aspect-video" >
< div *ngFor = "let box of [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]"
class = "rounded flex items-center justify-center text-[10px] bg-[#2a343b] text-[#4a5e69]" >
< span > . </ span >
</ div >
</ div >
< div class = "text-xs text-[#9db0b9] text-center mt-1" >
Posición Actual:
< input [readonly] = "isReadOnly" [(ngModel)] = "currentClise.ubicacion"
class = "bg-transparent border-b border-[#1193d4] text-white font-mono w-16 text-center outline-none" >
</ div >
</ div >
</ div >
Editable ubicacion field allows direct relocation from the item detail view.
Best Practices
Numeric Locations Use pure numeric values for ubicacion to enable automatic rack mapping
Sequential Ranges Assign consecutive number ranges to avoid gaps in rack coverage
Regular Validation Periodically audit unmapped items (those without valid location numbers)
Capacity Planning Monitor box fill rates (green boxes) to identify when new racks are needed
Finished Goods Zones
While not shown in default layout, finished goods stock uses location-based zoning:
DES-A-01 to DES-A-10 : Dispatch Zone A (high-volume customers)
DES-B-01 to DES-B-10 : Dispatch Zone B (standard shipments)
ZONA-RET : Retention area for quality holds
RECEPCIÓN : Receiving area for new production
These zones can be added to RackConfig array for visualization:
{
id : 'PT-DESP' ,
name : 'Zona Despacho (PT)' ,
type : 'stock' , // New type for finished goods
orientation : 'horizontal' ,
levels : [
{ levelNumber: 1 , boxes: [
{ label: 'DES-A-01' , items: [] },
{ label: 'DES-A-02' , items: [] },
// ... additional zones
]}
]
}
Finished goods mapping requires updating the mapItemsToLayout() function to handle StockItem types.