Overview
Dashboard Backus implements a two-tier role system with distinct capabilities for administrators and clients. Authentication is handled via a custom PIN system backed by Supabase, with MD5 hashing for security.
User Roles
Admin Full Control
Configure traffic light thresholds
Register and close incidents
Manually finalize truck exits
Access all dashboard panels
Switch between simulation/real modes
Cliente Read-Only Monitoring
View real-time truck queue
Monitor bay assignments
See dashboard statistics
Fixed traffic light thresholds (60/120)
Cannot register incidents or modify config
Database Schema
CREATE TABLE usuarios (
id SERIAL PRIMARY KEY ,
email TEXT UNIQUE NOT NULL ,
pin TEXT NOT NULL , -- MD5 hash of PIN
rol TEXT NOT NULL CHECK (rol IN ( 'admin' , 'cliente' )),
nombre TEXT ,
activo BOOLEAN DEFAULT TRUE
);
-- Example admin user
INSERT INTO usuarios (email, pin, rol, nombre, activo)
VALUES (
'[email protected] ' ,
MD5( '1234' ), -- PostgreSQL MD5 function
'admin' ,
'Administrador Principal' ,
TRUE
);
-- Example client user
INSERT INTO usuarios (email, pin, rol, nombre, activo)
VALUES (
'[email protected] ' ,
MD5( '5678' ),
'cliente' ,
'Monitor Cementos Pacasmayo' ,
TRUE
);
TypeScript Interfaces
// types.ts
export type Rol = 'admin' | 'cliente' ;
export interface ConfigSimulador {
modo : 'simulacion' | 'real' ;
tiempoAmarillo : number ;
tiempoRojo : number ;
rol : Rol ;
}
// supabaseService.ts
export interface UsuarioLogin {
id : number ;
email : string ;
rol : 'admin' | 'cliente' ;
nombre : string | null ;
}
Authentication Flow
User Enters Credentials
Email and PIN entered in login form
PIN Hashing
Frontend hashes PIN with MD5 (same algorithm as PostgreSQL)
Database Query
Supabase query matches email + pin hash + active status
Session Start
User role determines available features
Login Function
export async function loginUsuario (
email : string ,
password : string
) : Promise < UsuarioLogin | null > {
const pinHash = md5Hex ( password );
const { data , error } = await supabase
. from ( 'usuarios' )
. select ( 'id, email, rol, nombre' )
. eq ( 'email' , email . trim (). toLowerCase ())
. eq ( 'pin' , pinHash )
. eq ( 'activo' , true )
. maybeSingle ();
if ( error ) {
manejarError ( 'loginUsuario' , error );
return null ;
}
if ( ! data ) return null ;
return {
id: data . id as number ,
email: data . email as string ,
rol: data . rol as 'admin' | 'cliente' ,
nombre: data . nombre as string | null ,
};
}
MD5 Hashing
The frontend uses a pure JavaScript MD5 implementation that produces identical output to PostgreSQL’s md5() function:
function md5Hex ( input : string ) : string {
// ... RFC 1321 implementation (513 lines)
// Returns 32-character hex string
}
// Examples:
md5Hex ( '1234' ) // "81dc9bdb52d04dc20036dbd8313ed055"
md5Hex ( '5678' ) // "674f3c2c1a8a6f900461d9c34f301c14"
Security Note: MD5 is used for compatibility with existing PostgreSQL functions, not for cryptographic security. For production systems, consider migrating to bcrypt or Argon2.
Role Enforcement
Configuration Modal
Admin users see editable threshold fields:
// ModalConfig.tsx - Admin view
< input
type = "number"
min = "1"
max = "999"
value = {config. tiempoAmarillo }
onChange = { e => setConfig ({
... config ,
tiempoAmarillo: Number ( e . target . value )
})}
disabled = { false } // Editable for admins
/>
Client users see grayed-out fields:
// ModalConfig.tsx - Client view
< input
type = "number"
value = {TIEMPOS_CLIENTE. tiempoAmarillo }
disabled = { true } // Read-only for clients
className = "opacity-50 cursor-not-allowed"
/>
Hardcoded Client Thresholds
// SimuladorMapa.tsx
const TIEMPOS_CLIENTE = {
tiempoAmarillo: 60 ,
tiempoRojo: 120
} as const ;
const handleConfirm = ( c : ConfigSimulador ) => {
const configFinal : ConfigSimulador = rolInicial === 'cliente'
? { ... c , rol: 'cliente' , ... TIEMPOS_CLIENTE } // Force fixed values
: { ... c , rol: 'admin' };
setConfig ( configFinal );
setSimulacionActiva ( true );
};
Any attempt by a client to modify thresholds (via browser dev tools) is overwritten with TIEMPOS_CLIENTE constants before saving.
Feature Access Matrix
Feature Admin Cliente View truck queue ✅ ✅ Drag & drop trucks to bays ✅ ✅ View dashboard panels ✅ ✅ Register incidents ✅ ❌ Close incidents ✅ ❌ Manually finalize trucks ✅ ❌ Configure traffic light thresholds ✅ ❌ Switch simulation/real mode ✅ ✅ Generate reports ✅ ✅ Toggle dark/light mode ✅ ✅ Toggle help mode ✅ ✅
Conditional UI Rendering
// BahiaOverlay.tsx
{ config . rol === 'admin' && (
< button onClick = {() => setShowIncidencia ( true )} >
⚠️ Incid .
</ button >
)}
// BahiaOverlay.tsx
{ config . rol === 'admin' && (
< button onClick = { handleFinalizar } >
✅ Salida
</ button >
)}
Configuration Fields
// ModalConfig.tsx
const readOnly = config . rol === 'cliente' ;
< input
type = "number"
value = {config. tiempoAmarillo }
disabled = { readOnly }
className = {readOnly ? 'opacity-50 cursor-not-allowed' : '' }
/>
Session Management
Logout Handler
const handleCerrarSesion = () => {
if ( window . confirm ( '¿Deseas cerrar la sesión?' )) {
setSimulacionActiva ( false );
setCola ([]);
setEnProceso ({});
notify ( '👋 Sesión cerrada' , 'info' );
setTimeout (() => onLogout (), 800 ); // 800ms delay for toast visibility
}
};
// Header.tsx
< Header
darkMode = { darkMode }
simulacionActiva = { simulacionActiva }
config = { config }
rol = { rolInicial } // Passed from parent
nombreUsuario = { nombreUsuario }
onCerrarSesion = { handleCerrarSesion }
/>
// Display role badge
< span className = { `
px-2 py-1 rounded-md text-xs font-bold uppercase
${ config . rol === 'admin' ? 'bg-blue-500 text-white' : 'bg-purple-500 text-white' }
` } >
{ config . rol === 'admin' ? '👮 Admin' : '👁️ Cliente' }
</ span >
API Security
Row-Level Security (RLS)
Supabase RLS policies ensure clients cannot modify critical tables:
-- Example RLS policy for incidencias table
CREATE POLICY "Admin only can insert incidents"
ON incidencias
FOR INSERT
TO authenticated
USING (
( SELECT rol FROM usuarios WHERE email = auth . email ()) = 'admin'
);
CREATE POLICY "Admin only can update incidents"
ON incidencias
FOR UPDATE
TO authenticated
USING (
( SELECT rol FROM usuarios WHERE email = auth . email ()) = 'admin'
);
CREATE POLICY "All can read incidents"
ON incidencias
FOR SELECT
TO authenticated
USING (true);
Important: Even if a client bypasses frontend restrictions, RLS policies on Supabase prevent unauthorized writes.
Role-Specific Configuration
Admin Configuration
const [ config , setConfig ] = useState < ConfigSimulador >({
modo: 'simulacion' ,
rol: 'admin' ,
tiempoAmarillo: 60 , // Editable
tiempoRojo: 120 , // Editable
});
Client Configuration
const [ config , setConfig ] = useState < ConfigSimulador >({
modo: 'real' ,
rol: 'cliente' ,
tiempoAmarillo: TIEMPOS_CLIENTE . tiempoAmarillo , // Fixed: 60
tiempoRojo: TIEMPOS_CLIENTE . tiempoRojo , // Fixed: 120
});
User Activation
The activo column allows administrators to disable accounts without deletion:
-- Deactivate user
UPDATE usuarios
SET activo = FALSE
WHERE email = '[email protected] ' ;
-- Reactivate user
UPDATE usuarios
SET activo = TRUE
WHERE email = '[email protected] ' ;
Inactive users cannot log in:
const { data , error } = await supabase
. from ( 'usuarios' )
. select ( 'id, email, rol, nombre' )
. eq ( 'email' , email . trim (). toLowerCase ())
. eq ( 'pin' , pinHash )
. eq ( 'activo' , true ) // ← Only active users
. maybeSingle ();
Adding New Users
INSERT INTO usuarios (email, pin, rol, nombre, activo)
VALUES (
'[email protected] ' ,
MD5( 'newpin123' ),
'admin' ,
'Nuevo Administrador' ,
TRUE
);
Navigate to Table Editor → usuarios
Click Insert row
Fill fields:
email: User email
pin: Use PostgreSQL function md5('pin_text')
rol: Select admin or cliente
nombre: Display name
activo: Check TRUE
Click Save
Password Reset
Since PIN hashes are stored, resets require database access:
-- Reset PIN to "newpin123"
UPDATE usuarios
SET pin = MD5( 'newpin123' )
WHERE email = '[email protected] ' ;
Production Recommendation: Implement a self-service password reset flow with email verification for better UX.
Traffic Light System See how client roles have fixed thresholds
Incident Management Learn why only admins can register incidents