Overview
The Employee Management module provides complete control over system users, roles, and permissions. It enables administrators to create staff accounts, assign roles, and configure granular access controls for different parts of the system.
Key Features
Role-Based Access Three pre-defined roles (Administrator, Technician, Cashier) with default permission sets
Granular Permissions Fine-grained access control with individual permission toggles for each system feature
User Lifecycle Create accounts with Firebase Authentication, edit details, activate/deactivate users
Security Permission validation on backend, secure password requirements, and audit tracking
Permission Editor Visual permission management interface with role presets and custom configurations
Status Control Active/Inactive status management without account deletion to preserve history
User Data Structure
Employee Record (Firestore)
interface Empleado {
id : string ;
uid : string ; // Firebase Auth UID
nombre : string ;
telefono : string ;
correo : string ;
rol : "Administrador" | "Tecnico" | "Cajero" ;
estado : "Activo" | "Inactivo" ;
permisos : PermisosMap ;
createdAt : Timestamp ;
}
Authorization Record (Firestore)
interface Autorizado {
uid : string ; // Document ID
activo : boolean ;
rol : string ;
permisos : PermisosMap ;
}
Employee data is stored in two collections: empleados (user profile) and autorizados (authentication/permissions). This separation enables fast permission checks.
Default Roles
Administrador (Administrator)
Full system access including:
Employee management (create, edit, delete users)
Complete POS access
Service creation and modification
Inventory management
Reports and analytics
Cash register operations
Tecnico (Technician)
Service-focused permissions:
Create and edit services
View client information
Limited inventory access (view only)
Cannot access POS or cash register
Cannot manage employees
Cajero (Cashier)
Sales and customer-facing operations:
Full POS access
Process sales and payments
Open/close cash register
View reports (sales only)
Cannot manage services or employees
Limited inventory modifications
Permission System
The system uses a comprehensive permission catalog:
export const PERMISOS_CATALOGO = [
{
key: "pos.vender" ,
label: "POS: Realizar ventas" ,
description: "Permite procesar ventas en el punto de venta"
},
{
key: "pos.devoluciones" ,
label: "POS: Gestionar devoluciones" ,
description: "Permite procesar devoluciones y reembolsos"
},
{
key: "inventario.ver" ,
label: "Inventario: Ver productos" ,
description: "Permite visualizar el catalogo de productos"
},
{
key: "inventario.modificar" ,
label: "Inventario: Modificar stock y precios" ,
description: "Permite editar productos, stock y precios"
},
{
key: "servicios.crear" ,
label: "Servicios: Registrar nuevos" ,
description: "Permite crear ordenes de servicio tecnico"
},
{
key: "servicios.modificar" ,
label: "Servicios: Modificar existentes" ,
description: "Permite editar servicios en progreso"
},
{
key: "clientes.ver" ,
label: "Clientes: Ver informacion" ,
description: "Permite acceder a datos de clientes"
},
{
key: "clientes.modificar" ,
label: "Clientes: Modificar datos" ,
description: "Permite editar informacion de clientes"
},
{
key: "reportes.ver" ,
label: "Reportes: Ver analytics" ,
description: "Permite visualizar reportes y graficas"
},
{
key: "reportes.caja" ,
label: "Reportes: Gestion de caja" ,
description: "Permite cerrar caja y ver cortes"
},
{
key: "empleados.gestionar" ,
label: "Empleados: Administrar usuarios" ,
description: "Permite crear, editar y eliminar empleados"
}
];
Permission Structure
Permissions use dot notation for hierarchical organization:
module.action
│ │
│ └─ Specific action (ver, modificar, crear, gestionar)
└──────── Feature area (pos, inventario, servicios, etc.)
Creating Employees
User Creation Flow
Click ”+ Nuevo Empleado” button (requires empleados.gestionar permission)
Fill in the employee form:
const [ form , setForm ] = useState ({
nombre: "" ,
telefono: "" ,
correo: "" ,
rol: "" ,
estado: "Activo" ,
password: "" ,
permisos: permisosBasePorRol ( "" )
});
Select role - permissions are auto-populated
Optionally customize permissions via “Checks de acceso” button
Click “Guardar”
Backend Process
const handleSubmit = async () => {
// Validation
if ( ! form . nombre || ! form . correo || ! form . rol || ! form . password ) {
alert ( "Completa los campos obligatorios" );
return ;
}
const permisosFinal = normalizarPermisos ( form . rol , form . permisos || {});
// Create Firebase Auth user
const adminActual = auth . currentUser ;
const userCredential = await createUserWithEmailAndPassword (
auth ,
form . correo ,
form . password
);
const uid = userCredential . user . uid ;
// Create employee profile
await addDoc ( collection ( db , "empleados" ), {
uid ,
nombre: form . nombre ,
telefono: form . telefono ,
correo: form . correo ,
rol: form . rol ,
estado: form . estado ,
permisos: permisosFinal ,
createdAt: new Date ()
});
// Create authorization record
await setDoc ( doc ( db , "autorizados" , uid ), {
activo: form . estado === "Activo" ,
rol: form . rol ,
permisos: permisosFinal
});
// Restore admin session
await auth . updateCurrentUser ( adminActual );
};
When creating a user, the current admin is temporarily signed out. The system automatically restores the admin session after user creation.
Editing Employees
Edit Process
const handleEdit = ( emp ) => {
const permisos = normalizarPermisos ( emp . rol || "" , emp . permisos || {});
setForm ({
nombre: emp . nombre || "" ,
telefono: emp . telefono || "" ,
correo: emp . correo || "" ,
rol: emp . rol || "" ,
estado: emp . estado || "Activo" ,
password: "" , // Password not editable
permisos
});
setEditingId ( emp . id );
setShowForm ( true );
};
Update Logic
if ( editingId ) {
const empleadoEditado = empleados . find ( e => e . id === editingId );
// Update employee profile
await updateDoc ( doc ( db , "empleados" , editingId ), {
nombre: form . nombre ,
telefono: form . telefono ,
rol: form . rol ,
estado: form . estado ,
permisos: permisosFinal
});
// Update authorization
await updateDoc ( doc ( db , "autorizados" , empleadoEditado . uid ), {
rol: form . rol ,
activo: form . estado === "Activo" ,
permisos: permisosFinal
});
}
Email addresses cannot be changed after account creation. To change an email, you must create a new account.
Permission Management
Permission Editor Modal
Click “Checks de acceso” to open the permission customization interface:
< div className = "emp-permisos-grid" >
{ PERMISOS_CATALOGO . map ( perm => (
< label key = { perm . key } className = "emp-perm-item" >
< input
type = "checkbox"
checked = { !! permisosDraft ?.[ perm . key ] }
onChange = { () => togglePermisoDraft ( perm . key ) }
/>
< div >
< strong > { perm . label } </ strong >
< small > { perm . description } </ small >
</ div >
</ label >
)) }
</ div >
Role-Based Defaults
Each role has default permissions:
export function permisosBasePorRol ( rol ) {
const base = {};
PERMISOS_CATALOGO . forEach ( p => {
base [ p . key ] = false ;
});
const rolLower = String ( rol || "" ). toLowerCase ();
if ( rolLower === "administrador" ) {
// All permissions
PERMISOS_CATALOGO . forEach ( p => {
base [ p . key ] = true ;
});
}
if ( rolLower === "tecnico" ) {
base [ "servicios.crear" ] = true ;
base [ "servicios.modificar" ] = true ;
base [ "clientes.ver" ] = true ;
base [ "inventario.ver" ] = true ;
}
if ( rolLower === "cajero" ) {
base [ "pos.vender" ] = true ;
base [ "clientes.ver" ] = true ;
base [ "clientes.modificar" ] = true ;
base [ "inventario.ver" ] = true ;
base [ "reportes.ver" ] = true ;
base [ "reportes.caja" ] = true ;
}
return base ;
}
Permission Normalization
When roles change, permissions are merged:
export function normalizarPermisos ( rol , permisosActuales = {}) {
const base = permisosBasePorRol ( rol );
const merged = { ... base };
// Preserve custom permissions if they don't conflict with role
Object . keys ( permisosActuales ). forEach ( key => {
if ( permisosActuales [ key ] === true ) {
merged [ key ] = true ;
}
});
return merged ;
}
Use “Cargar base por rol” button in the permission modal to reset permissions to role defaults.
Permission Checking
Throughout the application, features check permissions:
export function tienePermiso ( rol , permisos , permissionKey ) {
// Admins bypass all checks
if ( String ( rol || "" ). toLowerCase () === "administrador" ) {
return true ;
}
// Check specific permission
return !! ( permisos || {})[ permissionKey ];
}
// Usage example:
const [ puedeGestionarActual , setPuedeGestionarActual ] = useState ( false );
useEffect (() => {
const obtenerRol = async () => {
const uid = auth . currentUser ?. uid ;
if ( ! uid ) return ;
const snap = await getDoc ( doc ( db , "autorizados" , uid ));
if ( ! snap . exists ()) return ;
const data = snap . data ();
setPuedeGestionarActual (
tienePermiso ( data . rol || "" , data . permisos || {}, "empleados.gestionar" )
);
};
obtenerRol ();
}, []);
Conditional Rendering
{ puedeGestionarActual && (
< button className = "emp-btn emp-btn-primary" onClick = { handleNew } >
+ Nuevo Empleado
</ button >
)}
Active/Inactive Status
Status Toggle
Employees can be activated or deactivated:
< select
value = { form . estado }
onChange = { ( e ) => setForm ({ ... form , estado: e . target . value }) }
>
< option > Activo </ option >
< option > Inactivo </ option >
</ select >
Status Effects
// Update authorization
await updateDoc ( doc ( db , "autorizados" , empleadoEditado . uid ), {
activo: form . estado === "Activo" ,
// ...
});
// Backend validation (example)
if ( ! autorizado . activo ) {
throw new Error ( "Usuario inactivo. Contacta al administrador." );
}
Inactive users cannot log in but their records remain in the database for historical reporting.
Deleting Employees
Deletion Flow
const handleDelete = async ( emp ) => {
if ( ! window . confirm ( "Eliminar empleado?" )) return ;
const currentUid = auth . currentUser ?. uid ;
if ( emp . uid === currentUid ) {
alert ( "No puedes eliminar tu propia cuenta." );
return ;
}
await deleteDoc ( doc ( db , "empleados" , emp . id ));
await deleteDoc ( doc ( db , "autorizados" , emp . uid ));
};
Deleting an employee removes their access immediately but does NOT delete their Firebase Authentication account. Consider using Inactive status instead.
Employee List
The main table displays all employees:
< table >
< thead >
< tr >
< th > Nombre </ th >
< th > Rol </ th >
< th > Correo </ th >
< th > Estado </ th >
< th > Acciones </ th >
</ tr >
</ thead >
< tbody >
{ empleados . map ( emp => (
< tr key = { emp . id } >
< td > { emp . nombre } </ td >
< td > { emp . rol } </ td >
< td > { emp . correo } </ td >
< td >
< span className = { emp . estado === "Activo" ? "estado-activo" : "estado-inactivo" } >
{ emp . estado }
</ span >
</ td >
< td className = "emp-actions-cell" >
{ puedeGestionarActual && (
< div className = "emp-actions-group" >
< button onClick = { () => handleEdit ( emp ) } > Editar </ button >
< button onClick = { () => handleDelete ( emp ) } > Eliminar </ button >
</ div >
) }
</ td >
</ tr >
)) }
</ tbody >
</ table >
Real-Time Updates
Employee list uses Firestore snapshots for live updates:
useEffect (() => {
const unsub = onSnapshot (
collection ( db , "empleados" ),
( snapshot ) => {
const lista = snapshot . docs . map ( row => ({ id: row . id , ... row . data () }));
setEmpleados ( lista );
},
( error ) => {
console . warn ( "No se pudo suscribir a empleados:" , error ?. code );
setEmpleados ([]);
}
);
return () => unsub ();
}, []);
Changes to employee records are reflected immediately across all active sessions.
Phone Number Validation
Phone input enforces 10-digit format:
< input
type = "tel"
placeholder = "10 digitos"
value = { form . telefono }
maxLength = { 10 }
onChange = { ( e ) => {
const soloNumeros = e . target . value . replace ( / \D / g , "" ). slice ( 0 , 10 );
setForm ({ ... form , telefono: soloNumeros });
} }
/>
Security Best Practices
Firebase Auth enforces minimum 6 characters. Consider requiring:
At least 8 characters
Mix of uppercase and lowercase
At least one number
At least one special character
Track permission changes:
Log who modified permissions
Record when changes occurred
Store previous permission state
Review audit logs regularly
Least Privilege Principle
Grant minimum permissions needed for job role
Regularly review and revoke unnecessary permissions
Use temporary elevated access for one-time tasks
Separate duties between roles
Account Deactivation Process
When employee leaves:
Immediately set status to “Inactivo”
Document reason for departure
Revoke all permissions
Review their recent activities
Change shared passwords they knew
Integration with Other Modules
Service Management
Technicians see “created by” field
Permission check before service modification
Role-based service list filtering
POS System
Cashiers have full POS access
Technicians cannot access POS
Sales attributed to logged-in user
Reports
Permission required to view reports
Cash register closing restricted to authorized roles
Sensitive financial data protected
Technical Implementation
The employee management system uses:
import {
addDoc , collection , deleteDoc , doc , getDoc ,
onSnapshot , setDoc , updateDoc
} from "firebase/firestore" ;
import { createUserWithEmailAndPassword } from "firebase/auth" ;
import { auth , db } from "../initializer/firebase" ;
import {
PERMISOS_CATALOGO , normalizarPermisos ,
permisosBasePorRol , tienePermiso
} from "../js/services/permisos" ;
Key Files:
src/pages/empleados.jsx - Main interface (460 lines)
src/js/services/permisos.js - Permission system
src/css/empleados.css - Employee UI styling
Firebase Collections:
empleados - Employee profiles
autorizados - Permission records (keyed by UID)
Security Rules Example:
match / empleados / { empleadoId } {
allow read : if request . auth != null ;
allow write : if tienePermisoAdmin ( request . auth . uid );
}
match / autorizados / { uid } {
allow read : if request . auth != null && request . auth . uid == uid ;
allow write : if tienePermisoAdmin ( request . auth . uid );
}