Overview
The Horarios Stop (Schedule Management) module is an advanced system for managing employee shifts at Stop Jeans stores. It features real-time synchronization, shift color coding, automated calculations, change history tracking, and multiple export formats.
User Guide Complete guide for working with schedules
Key Features
Real-time Updates Changes sync instantly across all users via Firebase
Color-Coded Shifts Visual shift identification with customizable colors
Global Color Cache Preloaded shift colors for instant rendering
Change History Complete audit trail of all schedule modifications
Multiple Exports Export to Excel, PNG, ICS, and text formats
Automated Calculations Automatic hour totals and rest day tracking
Schedule Structure
Data Organization
Schedules are stored in a hierarchical Firebase structure:
celdas/
└── {nombreAsesor}/
└── {dia}/
└── {año}/
└── {mes}/
└── {valor_turno}
Example:
{
"celdas" : {
"Andrés_Felipe_Yepes_Tascón" : {
"15" : {
"2024" : {
"Enero" : "A"
}
}
}
}
}
Shift Types
The system supports various shift types stored in the Turnos collection:
{
"A" : {
"Descripcion" : "8:00 - 17:00" ,
"Horas" : 9 ,
"ColorF" : "3b82f6" , // Background color
"ColorT" : "ffffff" // Text color
},
"B" : {
"Descripcion" : "10:00 - 19:00" ,
"Horas" : 9 ,
"ColorF" : "10b981" ,
"ColorT" : "ffffff"
},
"D" : {
"Descripcion" : "Descanso" ,
"Horas" : 0 ,
"ColorF" : "ef4444" ,
"ColorT" : "ffffff"
}
}
Global Color Cache
The schedule module implements a global color cache for optimal performance:
// Global cache
const cacheColoresTurnos = new Map ();
let cacheInicializado = false ;
// Preload all shift colors on startup
async function precargarColoresTurnos () {
if ( cacheInicializado ) return cacheColoresTurnos ;
try {
const snapshot = await firebase . database (). ref ( 'Turnos' ). once ( 'value' );
const data = snapshot . val ();
if ( data ) {
Object . entries ( data ). forEach (([ turno , valores ]) => {
cacheColoresTurnos . set ( turno , {
colorFondo: valores . ColorF ? `# ${ valores . ColorF } ` : '#ffffff' ,
colorTexto: valores . ColorT ? `# ${ valores . ColorT } ` : '#000000'
});
});
}
cacheInicializado = true ;
console . log ( `✅ Caché de colores inicializado: ${ cacheColoresTurnos . size } turnos` );
return cacheColoresTurnos ;
} catch ( error ) {
console . error ( "❌ Error al precargar colores:" , error );
return cacheColoresTurnos ;
}
}
window . onload = function () {
precargarColoresTurnos ();
// ... other initialization
};
Applying Cached Colors
function actualizarColorCelda ( celda ) {
const valorTurno = celda . textContent . trim ();
if ( cacheColoresTurnos . has ( valorTurno )) {
const { colorFondo , colorTexto } = cacheColoresTurnos . get ( valorTurno );
celda . style . backgroundColor = colorFondo ;
celda . style . color = colorTexto ;
} else {
// Default colors
celda . style . backgroundColor = '#ffffff' ;
celda . style . color = '#000000' ;
}
}
The global cache eliminates thousands of Firebase reads per page load, dramatically improving performance.
Editing Schedules
Password Protection
Schedule editing requires password authentication:
const EDIT_PASSWORD = 'admin123' ; // In production, use environment variables
function habilitarEdicion () {
const password = prompt ( 'Ingrese la contraseña para editar:' );
if ( password === EDIT_PASSWORD ) {
document . querySelectorAll ( 'table td' ). forEach ( celda => {
celda . contentEditable = true ;
celda . classList . add ( 'editable' );
});
alert ( 'Edición habilitada' );
} else {
alert ( 'Contraseña incorrecta' );
}
}
Saving Changes
Changes are saved to Firebase with change history tracking:
function guardarCelda ( celda ) {
const asesor = celda . dataset . asesor ;
const dia = celda . dataset . dia ;
const mes = celda . dataset . mes ;
const año = celda . dataset . año ;
const valorNuevo = celda . textContent . trim ();
const valorAnterior = celda . dataset . valorAnterior ;
// Save to Firebase
firebase . database ()
. ref ( `celdas/ ${ asesor } / ${ dia } / ${ año } / ${ mes } ` )
. set ( valorNuevo )
. then (() => {
// Update cell color
actualizarColorCelda ( celda );
// Save to history
guardarHistorial ( asesor , dia , mes , año , valorAnterior , valorNuevo );
// Update cached value
celda . dataset . valorAnterior = valorNuevo ;
})
. catch ( error => {
console . error ( 'Error al guardar:' , error );
alert ( 'Error al guardar el cambio' );
});
}
Change History
Every schedule modification is tracked in the Historial collection:
function guardarHistorial ( asesor , dia , mes , año , valorAnterior , valorNuevo ) {
const historialRef = firebase . database (). ref ( 'Historial' ). push ();
historialRef . set ({
asesor: asesor ,
dia: dia ,
mes: mes ,
año: año ,
valorAnterior: valorAnterior ,
valorNuevo: valorNuevo ,
timestamp: Date . now (),
usuario: localStorage . getItem ( 'nombreAsesorActual' )
});
}
Viewing History
Retrieve and display change history:
function mostrarHistorial ( asesor , mes , año ) {
firebase . database ()
. ref ( 'Historial' )
. orderByChild ( 'timestamp' )
. once ( 'value' , snapshot => {
const cambios = [];
snapshot . forEach ( child => {
const cambio = child . val ();
if ( cambio . asesor === asesor &&
cambio . mes === mes &&
cambio . año === año ) {
cambios . push ( cambio );
}
});
renderizarHistorial ( cambios );
});
}
Export Functionality
Excel Export
Export schedules to Excel format using the TableExport library:
function exportarExcel () {
const table = document . querySelector ( 'table' );
// Use TableExport library
const exporter = new TableExport ( table , {
formats: [ 'xlsx' ],
filename: `Horarios_ ${ mesActual } _ ${ añoActual } ` ,
sheetname: 'Horarios'
});
exporter . export ();
}
PNG Image Export
Capture schedule as an image using html2canvas:
function exportarPNG () {
const table = document . querySelector ( 'table' );
html2canvas ( table ). then ( canvas => {
canvas . toBlob ( blob => {
saveAs ( blob , `Horarios_ ${ mesActual } _ ${ añoActual } .png` );
});
});
}
ICS Calendar Export
Export shifts to calendar format:
import { createEvents } from 'ics' ;
function exportarICS ( asesor , mes , año ) {
const eventos = [];
// Gather all shifts for the month
firebase . database ()
. ref ( `celdas/ ${ asesor } ` )
. once ( 'value' , snapshot => {
snapshot . forEach ( diaSnap => {
const dia = diaSnap . key ;
const turno = diaSnap . child ( ` ${ año } / ${ mes } ` ). val ();
if ( turno && turno !== 'D' ) {
const turnoData = cacheColoresTurnos . get ( turno );
eventos . push ({
start: [ año , getMesNumero ( mes ), parseInt ( dia ), 8 , 0 ],
duration: { hours: turnoData ?. Horas || 9 },
title: `Turno ${ turno } ` ,
description: turnoData ?. Descripcion || ''
});
}
});
createEvents ( eventos , ( error , value ) => {
if ( ! error ) {
downloadFile ( `Horarios_ ${ asesor } _ ${ mes } .ics` , value , 'text/calendar' );
}
});
});
}
ICS files can be imported into Google Calendar, Outlook, and Apple Calendar for easy schedule viewing.
Automated Features
Week Border Highlighting
Automatically highlight week boundaries:
function cambiarBordeColumna () {
const celdas = document . querySelectorAll ( 'table td' );
const diasSemana = [ 'Lunes' , 'Martes' , 'Miércoles' , 'Jueves' , 'Viernes' , 'Sábado' , 'Domingo' ];
celdas . forEach ( celda => {
const dia = celda . dataset . diaSemana ;
if ( dia === 'Domingo' ) {
celda . style . borderRight = '3px solid #000' ;
}
});
}
Holiday Highlighting
Mark holidays with special styling:
const FESTIVOS = {
'2024' : {
'Enero' : [ 1 , 8 ],
'Marzo' : [ 25 ],
'Abril' : [ 18 , 19 ],
'Mayo' : [ 1 , 13 ],
'Junio' : [ 3 , 10 , 24 ],
'Julio' : [ 1 , 20 ],
'Agosto' : [ 7 , 19 ],
'Octubre' : [ 14 ],
'Noviembre' : [ 4 , 11 ],
'Diciembre' : [ 8 , 25 ]
}
};
function Festivos () {
const año = document . getElementById ( 'año' ). value ;
const mes = document . getElementById ( 'mes' ). value ;
const festivosMes = FESTIVOS [ año ]?.[ mes ] || [];
festivosMes . forEach ( dia => {
const celda = document . querySelector ( `[data-dia=" ${ dia } "]` );
if ( celda ) {
celda . classList . add ( 'festivo' );
celda . style . backgroundColor = '#fef3c7' ;
}
});
}
Hour Calculation
Automatically calculate total hours worked:
function calcularHoras ( asesor , mes , año ) {
let totalHoras = 0 ;
firebase . database ()
. ref ( `celdas/ ${ asesor } ` )
. once ( 'value' , snapshot => {
snapshot . forEach ( diaSnap => {
const turno = diaSnap . child ( ` ${ año } / ${ mes } ` ). val ();
if ( turno && cacheColoresTurnos . has ( turno )) {
const turnoData = firebase . database ()
. ref ( `Turnos/ ${ turno } /Horas` )
. once ( 'value' );
turnoData . then ( horasSnap => {
totalHoras += horasSnap . val () || 0 ;
});
}
});
document . getElementById ( `horas- ${ asesor } ` ). textContent = totalHoras ;
});
}
Weekly Summary View
Display aggregated weekly statistics:
function mostrarResumenSemanal () {
const semanas = {};
// Group days by week
for ( let dia = 1 ; dia <= 31 ; dia ++ ) {
const fecha = new Date ( año , getMesNumero ( mes ) - 1 , dia );
const semana = getWeekNumber ( fecha );
if ( ! semanas [ semana ]) {
semanas [ semana ] = {
horas: 0 ,
descansos: 0 ,
dias: []
};
}
semanas [ semana ]. dias . push ( dia );
}
// Calculate stats per week
Object . entries ( semanas ). forEach (([ semana , data ]) => {
data . dias . forEach ( dia => {
const celda = document . querySelector ( `[data-dia=" ${ dia } "]` );
const turno = celda ?. textContent . trim ();
if ( turno === 'D' ) {
data . descansos ++ ;
} else if ( cacheColoresTurnos . has ( turno )) {
// Add hours from cache
data . horas += getTurnoHoras ( turno );
}
});
});
renderizarResumen ( semanas );
}
Best Practices
Regular Backups Export schedules monthly as Excel files for backup
Verify Changes Review change history to audit modifications
Consistent Codes Use standardized shift codes across all schedules
Color Coding Maintain consistent color schemes for easy recognition
Managing Schedules Complete user guide
Shift Tracking Track shifts and payments
Data Export Export schedules in various formats
Caching Strategy Technical details on color cache