The Operator Mode provides a dedicated full-screen interface optimized for production floor operators, featuring large touchscreen-friendly controls, immersive visuals, and streamlined workflows.
Overview
Operator mode consists of:
Station selector with visual cards for each production type
Full-screen forms for production reporting
Touchscreen-optimized UI with large buttons
Operator selection and shift management
Barcode/QR scanning support for OT selection
Glass morphism design with gradient mesh backgrounds
Components:
OperatorSelectorComponent - Station selection hub
OperatorMachineSelectorComponent - Machine picker (for print, diecut, rewind)
OperatorFormComponent - Universal production form
Location: src/features/operator/
Station Selector Interface
Access
Navigate to operator mode via:
Login with operator credentials
From dashboard → “Admin” FAB → “Gestión” → Operator Portal
Direct URL: /operator
< header class = "glass-panel h-20" >
< button (click) = "logout()" > Back </ button >
< h1 > TERMINAL 04 </ h1 >
< div > {{ state.currentShift() || 'TURNO GENERAL' }} </ div >
< div > ID: {{ state.userName() }} </ div >
</ header >
Features:
Terminal identifier (TERMINAL 04)
Current shift display
Operator ID/username
System status indicator (NORMAL)
“Incidencia” alert button
Production Stations
Four stations are available (operator-selector.component.ts:98):
ST-01: Impresión Flexographic printing operations
Multi-activity tracking
Tooling status (cliché + die)
Linear meter production
Color: Blue gradient
ST-02: Troquelado Die-cutting and conversion
Unit production tracking
Die series management
Frequency settings (flatbed)
Color: Purple gradient
ST-03: Rebobinado Roll finishing and rewinding
Roll counting
Label quantity verification
Quality checks
Color: Orange gradient
ST-04: Empaquetado Packaging and palletizing
Lot completion tracking
Demasía recording
No machine selection required
Color: Teal/Emerald gradient
Card Structure
Each station card features (operator-selector.component.ts:98):
< div (click) = "navigateTo('print')" class = "group glass-card rounded-3xl" >
<!-- Background image with overlays -->
< img src = "..." class = "grayscale-[30%] group-hover:grayscale-0" />
<!-- Content -->
< div >
< span class = "badge" > ST-01 </ span >
< div class = "icon-container" >
< span class = "material-symbols-outlined" > print </ span >
</ div >
< h3 > Impresión </ h3 >
< div class = "divider" ></ div >
</ div >
</ div >
Interactive Effects:
Hover lifts card with -translate-y-2
Grayscale fades from 30% to 0% on hover
Icon scales to 110% on hover
Divider expands from 12 to 24 width
Color-matched glow shadows
Navigation Flow
Station Selection
Operator clicks station card: navigateTo ( type : string ) {
if ( type === 'packaging' ) {
this . router . navigate ([ '/operator/packaging' ]);
} else {
this . router . navigate ([ '/operator/select-machine' , type ]);
}
}
Packaging : Goes directly to form (no machine selection)
Others : Proceed to machine selector
Machine Selection
For print, diecut, and rewind:
System loads machines filtered by type:
machines = this . state . adminMachines (). filter ( m => m . type === 'Impresión' );
Operator selects machine
Navigates to:
this . router . navigate ([ '/operator/form' , type , machineName ]);
Production Form
Universal form component loads with:
Selected machine name
Production type
OT search
Type-specific fields
< header class = "glass-panel h-24" >
< button (click) = "goBack()" > ← Back </ button >
< h1 > REPORTE DE {{ typeName }} </ h1 >
< div > {{ machineName }} | {{ state.currentShift() }} </ div >
</ header >
Machine Status Bar
< div class = "p-6 bg-white/[0.02] flex items-center gap-5" >
< div class = "icon-container" >
< span class = "material-symbols-outlined" > precision_manufacturing </ span >
</ div >
< div >
< p class = "text-xs" > Máquina Activa </ p >
< h2 class = "text-2xl" > {{ machineName }} </ h2 >
</ div >
<!-- OPERATOR SELECTION -->
< select [(ngModel)] = "formData.selectedOperator" >
< option *ngFor = "let op of operatorList" [value] = "op" > {{ op }} </ option >
</ select >
</ div >
Operator List (operator-form.component.ts:542):
operatorList = [
'Juan Martinez' ,
'Carlos Ruiz' ,
'Ana Lopez' ,
'Miguel Torres' ,
'Luis Diaz' ,
'Pedro Gomez' ,
'Sofia Herrera' ,
'Usuario Temporal'
];
OT Search Interface
< input type = "text"
[(ngModel)] = "otSearch"
(ngModelChange) = "updateOtSearch($event)"
placeholder = "ESCANEAR CÓDIGO O ESCRIBIR OT..."
class = "text-xl font-bold uppercase font-mono" >
Features:
Uppercase display
Monospace font for barcode alignment
Large text (text-xl) for visibility
Icon: Search magnifying glass
Suggestions Dropdown
get filteredOts () {
const term = this . otSearch . toLowerCase ();
if ( ! term ) return [];
return this . ordersService . ots
. filter ( ot =>
String ( ot . OT ). includes ( term ) ||
ot [ 'Razon Social' ]. toLowerCase (). includes ( term )
)
. slice ( 0 , 5 );
}
Displays:
OT number (large, bold, blue)
Order status badge
Client name (bold)
Product description (small, truncated)
Selected OT Card
After selection:
< div class = "glass-panel rounded-2xl p-6 border-blue-500/30 animate-fadeIn" >
< div class = "border-l-2 border-blue-500" > <!-- Accent bar --> </ div >
< div > {{ selectedOt['Razon Social'] }} </ div >
< div > {{ selectedOt.descripcion }} </ div >
<!-- Item/Cliché input for print -->
< input *ngIf = "type === 'print'" [(ngModel)] = "formData.cliseItem" />
<!-- Troquel input for diecut -->
< input *ngIf = "type === 'diecut'" [(ngModel)] = "formData.dieSeries" />
<!-- Production status selector -->
< button (click) = "formData.productionStatus = 'PARCIAL'" > PARCIAL </ button >
< button (click) = "formData.productionStatus = 'TOTAL'" > TOTAL </ button >
</ div >
Activity Addition Card:
< div class = "bg-[#0a0f18]/60 border border-white/10 rounded-2xl p-6" >
< h3 > Agregar Registro de Actividad </ h3 >
< select [(ngModel)] = "currentActivity.type" >
< option *ngFor = "let act of printActivities" > {{ act }} </ option >
</ select >
< input type = "time" [(ngModel)] = "currentActivity.startTime" />
< input type = "time" [(ngModel)] = "currentActivity.endTime" />
< input type = "number" [(ngModel)] = "currentActivity.meters" placeholder = "0" />
< button (click) = "addActivity()" [disabled] = "!isValidActivity()" >
Agregar
</ button >
</ div >
Activity List Table:
Shows all added activities
Calculates duration automatically
Totals meters at bottom
Delete button per row
Tooling Status Check:
< div class = "grid grid-cols-2 gap-6" >
<!-- Cliché Status -->
< button (click) = "formData.cliseStatus = 'OK'" > OK </ button >
< button (click) = "formData.cliseStatus = 'Desgaste'" > Desgaste </ button >
< button (click) = "formData.cliseStatus = 'Dañado'" > Dañado </ button >
<!-- Die Status -->
< button (click) = "formData.dieStatus = 'OK'" > OK </ button >
< button (click) = "formData.dieStatus = 'Desgaste'" > Desgaste </ button >
< button (click) = "formData.dieStatus = 'Dañado'" > Dañado </ button >
</ div >
Frequency Input (Flatbed Only):
get isFlatbed (): boolean {
return ( this . machineName || '' ). toUpperCase (). includes ( 'PLANA' );
}
< div *ngIf = "isFlatbed" class = "bg-purple-500/10" >
< label > Frecuencia (Troqueladora Plana) </ label >
< input [(ngModel)] = "formData.frequency" />
</ div >
Activity Addition:
Similar to print, but with:
Purple theme (border-purple-500)
Observations field per activity
Activity types: SETUP, TROQUELADO, ESTAMPADO, etc.
Rewind / Other Processes
Generic form with:
< input [(ngModel)] = "formData.goodQty" placeholder = "Cantidad" />
< input [(ngModel)] = "formData.waste" placeholder = "Mermas" />
< input [(ngModel)] = "formData.labelsPerRoll" placeholder = "Etiquetas x Rollo" />
< textarea [(ngModel)] = "formData.notes" placeholder = "Observaciones..." ></ textarea >
Validation
submitReport () {
// Validation logic
if ( this . type === 'print' && this . reportActivities . length === 0 ) {
// Error: No activities
return ;
}
if ( this . type === 'diecut' && this . diecutReportActivities . length === 0 ) {
// Error: No activities
return ;
}
if ( this . type !== 'print' && this . type !== 'diecut' && ! this . formData . goodQty ) {
// Error: No quantity
return ;
}
// Submit...
}
Button Disabled State:
< button (click) = "submitReport()"
[disabled] = "!selectedOt || (...validation logic...)" >
< span class = "material-symbols-outlined" > save </ span >
Confirmar y Guardar Reporte
</ button >
Audit Logging
const operator = this . formData . selectedOperator || this . state . userName ();
const details = `Op: ${ operator } , Máquina: ${ this . machineName } , ...` ;
this . audit . log (
this . state . userName (),
'Operario' ,
'PRODUCCIÓN' ,
'Reporte Turno' ,
details
);
Logs include:
User who submitted
Role: “Operario”
Module: “PRODUCCIÓN”
Action: “Reporte Turno”
Details: OT, machine, operator, production data
Visual Design System
Glass Morphism
.glass-panel {
background : rgba ( 2 , 4 , 8 , 0.6 );
backdrop-filter : blur ( 24 px );
-webkit-backdrop-filter : blur ( 24 px );
border : 1 px solid rgba ( 255 , 255 , 255 , 0.05 );
box-shadow : 0 4 px 30 px rgba ( 0 , 0 , 0 , 0.3 );
}
.glass-card {
background : linear-gradient ( 160 deg ,
rgba ( 30 , 41 , 59 , 0.3 ) 0 % ,
rgba ( 15 , 23 , 42 , 0.4 ) 100 % );
backdrop-filter : blur ( 16 px );
}
.glass-button {
background : linear-gradient ( 180 deg ,
rgba ( 255 , 255 , 255 , 0.05 ) 0 % ,
rgba ( 255 , 255 , 255 , 0.01 ) 100 % );
backdrop-filter : blur ( 4 px );
}
Gradient Mesh Background
.bg-gradient-mesh {
background-color : #0f172a ;
background-image :
radial-gradient ( at 0 % 0 % , hsla ( 253 , 16 % , 7 % , 1 ) 0 , transparent 50 % ),
radial-gradient ( at 50 % 0 % , hsla ( 225 , 39 % , 30 % , 1 ) 0 , transparent 50 % ),
radial-gradient ( at 100 % 0 % , hsla ( 339 , 49 % , 30 % , 1 ) 0 , transparent 50 % );
}
Floating Orbs
.orb-1 {
top : -10 % ; left : 20 % ;
width : 600 px ; height : 600 px ;
background : radial-gradient ( circle ,
rgba ( 29 , 78 , 216 , 0.15 ) 0 % , transparent 70 % );
filter : blur ( 80 px );
}
CRT Scanline Effect
.scanline {
background : linear-gradient ( to bottom ,
transparent 50 % , rgba ( 0 , 0 , 0 , 0.3 ) 51 % );
background-size : 100 % 4 px ;
opacity : 0.1 ;
}
Typography
.font-tech {
font-family : 'Rajdhani' , sans-serif ;
letter-spacing : 0.15 em ;
text-transform : uppercase ;
}
Used for:
Page titles
Section headers
Button labels
Inter Font (Body Text)
Default sans-serif for:
Form labels
Input values
Table data
Monospace (Data)
Used for:
OT numbers
Time inputs
Quantity values
Meter/unit displays
Supervisor Message Panel
At bottom of station selector (operator-selector.component.ts:190):
< div class = "glass-panel rounded-2xl p-4 border border-yellow-500/10" >
< div class = "icon-container" >
< span class = "material-symbols-outlined" > mail </ span >
</ div >
< div >
< h4 > Mensaje del Supervisor </ h4 >
< span class = "badge" > NUEVO </ span >
< p > {{ state.config().operatorMessage }} </ p >
</ div >
</ div >
Features:
Yellow accent (warning/info color)
“NUEVO” badge for unread messages
Displays config.operatorMessage from state
Admin Access FAB
Floating Action Button in bottom-right (operator-selector.component.ts:211):
< div class = "fixed bottom-6 right-6 z-50" >
< button (click) = "goToManager()" class = "glass-button" >
< span class = "material-symbols-outlined" > settings </ span >
< div >
< span > Admin </ span >
< span > Gestión </ span >
</ div >
</ button >
</ div >
Navigation:
goToManager () {
this . router . navigate ([ '/dashboard' ]);
}
Allows operators with elevated privileges to access management dashboard.
Responsive Behavior
Breakpoints
Mobile : 1 column grid for station cards
Tablet (md) : 2 column grid
Desktop (lg) : 4 column grid
Activity addition grid:
Mobile : 1 column, stacked inputs
Large (lg) : 12-column grid:
Activity type: 4 columns
Start/End time: 2 columns each
Meters: 2 columns
Add button: 2 columns
Best Practices
Use barcode scanner for OT entry when available
Always select correct operator from dropdown
Verify machine name before starting report
Add activities immediately as they occur, not at end of shift
Use large buttons/inputs on touchscreen devices
Review total production before submitting
Ensure selected operator matches actual person running machine
Do not share operator credentials
Report technical issues using “Incidencia” button immediately
Keyboard Shortcuts & Scanning
Barcode/QR Scanning
OT search input accepts:
Keyboard input : Manual typing
Barcode scanner : Acts as keyboard input
QR scanner : Paste or scan directly
No special configuration needed—scanners simulate keyboard.
Touch Gestures
Tap : Select/activate
Long press : (Not implemented, could trigger context menus)
Swipe : Native scrolling