Skip to main content
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:
  1. Login with operator credentials
  2. From dashboard → “Admin” FAB → “Gestión” → Operator Portal
  3. 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

1

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
2

Machine Selection

For print, diecut, and rewind:
  1. System loads machines filtered by type:
machines = this.state.adminMachines().filter(m => m.type === 'Impresión');
  1. Operator selects machine
  2. Navigates to:
this.router.navigate(['/operator/form', type, machineName]);
3

Production Form

Universal form component loads with:
  • Selected machine name
  • Production type
  • OT search
  • Type-specific fields

Operator Form Component

Header

<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

Search Input

<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>

Production-Specific Forms

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>

Diecut Form

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>

Form Submission

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(24px);
  -webkit-backdrop-filter: blur(24px);
  border: 1px solid rgba(255, 255, 255, 0.05);
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
}

.glass-card {
  background: linear-gradient(160deg, 
    rgba(30, 41, 59, 0.3) 0%, 
    rgba(15, 23, 42, 0.4) 100%);
  backdrop-filter: blur(16px);
}

.glass-button {
  background: linear-gradient(180deg, 
    rgba(255,255,255,0.05) 0%, 
    rgba(255,255,255,0.01) 100%);
  backdrop-filter: blur(4px);
}

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: 600px; height: 600px;
  background: radial-gradient(circle, 
    rgba(29, 78, 216, 0.15) 0%, transparent 70%);
  filter: blur(80px);
}

CRT Scanline Effect

.scanline {
  background: linear-gradient(to bottom, 
    transparent 50%, rgba(0, 0, 0, 0.3) 51%);
  background-size: 100% 4px;
  opacity: 0.1;
}

Typography

Rajdhani Font (Tech Headers)

.font-tech {
  font-family: 'Rajdhani', sans-serif;
  letter-spacing: 0.15em;
  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

Form Layout

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

Build docs developers (and LLMs) love