Overview
The Cashouts system is a comprehensive workflow management tool for registering, reviewing, and tracking cashout operations across multiple gaming companies. It features real-time queue management, statistics tracking, and Google Sheets synchronization.
Access: /cashouts/ (requires authentication)
System Architecture
Main Features
Submit Cashouts Operators can submit cashout requests with operation codes and optional observations
Review Queue Supervisors review pending cashouts with real-time timer tracking
Statistics Comprehensive analytics by operator, company, shift, and time period
Leaderboard Real-time rankings of operator performance
Company Rules Searchable database of cashout procedures per company
Team Management Supervisors can create and manage user accounts
The sidebar provides access to all system features (cashouts/index.html:22-66):
< aside class = "sidebar" >
< button class = "role-btn operator active" > Submit a Cashout </ button >
< button class = "role-btn supervisor" > 🔍 Review a Cashout </ button >
< button class = "role-btn rules" > 📜 Cashout Rules </ button >
< button class = "role-btn leaderboard" > 🏆 Leaderboard </ button >
< button class = "role-btn stats" > 📊 Stats </ button >
< button class = "role-btn" id = "manageTeamBtn" style = "display: none;" > 👥 Manage Team </ button >
</ aside >
The “Manage Team” button is only visible to users with the supervisor role (cashouts/index.html:611-615)
Submit a Cashout
Operation Code
Unique identifier for the cashout transaction < input type = "text" id = "operationCode" name = "operationCode" required >
Operator Name
Auto-filled and read-only - populated from authenticated useroperatorNameInput . value = currentUser . fullName ;
operatorNameInput . readOnly = true ; // Prevents impersonation
Company
Dropdown populated from company database < select id = "company" name = "company" required >
< option value = "" > Seleccione una compañía </ option >
</ select >
Observation (Optional)
Modal prompt after form submission < div id = "observacionModal" class = "modal-overlay" >
< h3 > ¿Tienes alguna observación sobre este cashout? </ h3 >
< div class = "modal-buttons" >
< button id = "btnObservacionSi" class = "btn-si" > SÍ </ button >
< button id = "btnObservacionNo" class = "btn-no" > NO </ button >
</ div >
</ div >
Submission Flow
// 1. Form submission captures data
cashoutPendiente = {
operationCode: document . getElementById ( 'operationCode' ). value ,
operatorName: currentUser . fullName , // From JWT
company: document . getElementById ( 'company' ). value ,
observacion: ""
};
// 2. Optional observation modal
document . getElementById ( 'observacionModal' ). style . display = 'flex' ;
// 3. Submit to API
await apiFetch ( `/cashouts` , {
method: "POST" ,
body: JSON . stringify ( cashoutPendiente )
});
// 4. Success notification
document . getElementById ( 'operatorSuccess' ). style . display = 'block' ;
showNotification ( "¡Cash out enviado correctamente!" , "success" );
Review Queue
Real-Time Queue Management
The review queue auto-refreshes every 10 seconds when active (cashouts/index.html:780-792):
function iniciarAutoRefresh () {
if ( autoRefreshInterval ) return ;
autoRefreshInterval = setInterval (() => {
if ( reviewSection . style . display === 'block' ) cargarCola ();
}, 10000 ); // Refresh every 10 seconds
}
Queue Item Display
Each pending cashout shows:
Review Process
Select Action
Reviewer clicks Approve or Reject button
Reviewer Identification
Modal prompts for reviewer name (first time): < select id = "revisionTurnoSelect" >
< option value = "" > Seleccione un revisor </ option >
< option value = "Melanie Barrientos" > Melanie Barrientos </ option >
< option value = "Cristopher Cardozo" > Cristopher Cardozo </ option >
<!-- ...more reviewers... -->
</ select >
Observation Review
If operator left an observation, reviewer can add comments: < div id = "observacionOriginalDisplay" >
< strong > Observación Operador: </ strong >< br > ${observacionOriginal}
</ div >
< textarea id = "supervisorObsTexto" placeholder = "Escribe tu complemento aquí..." ></ textarea >
Submit Decision
Action is processed via API and reflected in statistics
Cashout Rules
Searchable database of company-specific cashout procedures (cashouts/index.html:171-193):
Features
Search < input type = "text" id = "searchRules"
placeholder = "🔍 Buscar compañía por nombre..."
style = "width: 100%;" >
Add Company Supervisors can add new companies (hidden by default): < div id = "newCompanyContainer" style = "display: none;" >
< input type = "text" id = "newCompanyName"
placeholder = "Nombre de la nueva compañía" >
< button id = "btnCreateCompany" > Guardar </ button >
</ div >
Grid Layout Rules displayed in responsive grid: < div id = "rulesGrid" class = "rules-grid" ></ div >
Database Sync Rules loaded from backend on demand: < div id = "rulesLoading" style = "text-align: center;" >
⏳ Cargando reglas desde la base de datos...
</ div >
Statistics Dashboard
Comprehensive analytics modal (cashouts/index.html:196-432):
Filters
< div class = "stats-filters" >
< input type = "date" id = "statsStartDate" />
< input type = "date" id = "statsEndDate" />
< select id = "statsCompanyFilter" >
< option value = "" > Todas las compañías </ option >
</ select >
< button id = "applyStatsFilters" > Aplicar Filtros </ button >
</ div >
Metrics Overview
Approved Cashouts Green card with total approved count < div id = "totalApproved" style = "color: #27ae60;" > - </ div >
Rejected Cashouts Red card with total rejected count < div id = "totalRejected" style = "color: #e74c3c;" > - </ div >
Approval Rate Blue card with percentage < div id = "approvalRate" style = "color: #3498db;" > - </ div >
Total Processed Orange card with total count < div id = "totalProcessed" style = "color: #f39c12;" > - </ div >
Detailed Reports
Operator Ranking
Shift Averages
Operator by Shift
Company Breakdown
Complete History
Table ranking by operator performance: # Operator Approved Rejected Total Rate 1 John Doe 245 12 257 95.3%
< table id = "rankingTable" >
< thead >
< tr >
< th > # </ th >
< th > Operador </ th >
< th > Aprobados </ th >
< th > Rechazados </ th >
< th > Total </ th >
< th > Tasa </ th >
</ tr >
</ thead >
< tbody id = "rankingTableBody" ></ tbody >
</ table >
Average performance by shift:
🌅 Mañana (7:00-14:30)
🌇 Tarde (14:30-22:00)
🌙 Noche (22:00-7:00)
< tbody id = "shiftTableBody" >
< tr >
< td > Mañana </ td >
< td > Total Cashouts </ td >
< td > Promedio/día </ td >
< td > % del Total </ td >
</ tr >
</ tbody >
Individual operator performance per shift: < table >
< thead >
< tr >
< th > Operador </ th >
< th > 🌅 Mañana </ th >
< th > 🌇 Tarde </ th >
< th > 🌙 Noche </ th >
< th > Mejor Turno </ th >
</ tr >
</ thead >
< tbody id = "operatorShiftTableBody" ></ tbody >
</ table >
Cashouts grouped by company: < tbody id = "companyTableBody" >
< tr >
< td > Company Name </ td >
< td > Approved </ td >
< td > Rejected </ td >
< td > Total </ td >
< td > Rate </ td >
</ tr >
</ tbody >
Paginated full transaction history: < input type = "text" id = "searchHistory"
placeholder = "🔍 Buscar por código de operación..." >
< select id = "operatorFilter" >
< option value = "" > Todos los operadores </ option >
</ select >
< div id = "pagination" >
< span id = "paginationInfo" > Mostrando 0-0 de 0 </ span >
< button id = "prevPage" > ◄ Anterior </ button >
< span id = "pageNumbers" ></ span >
< button id = "nextPage" > Siguiente ► </ button >
</ div >
Leaderboard
Real-time operator rankings modal (cashouts/index.html:530-541):
< div id = "leaderboardModal" class = "modal-overlay" >
< div class = "modal-content leaderboard-modal" >
< div class = "leaderboard-header" >
< h2 class = "leaderboard-title" > 🏆 LEADERBOARD </ h2 >
< button id = "cerrarLeaderboard" class = "btn-close-leaderboard" > ✕ </ button >
</ div >
< div id = "leaderboardContent" class = "leaderboard-body" >
< p > Cargando estadísticas... </ p >
</ div >
</ div >
</ div >
Team Management
Available to: Supervisors only
Create New Users
< div style = "background: rgba(42, 157, 143, 0.05);" >
< h3 > + Crear Nuevo Usuario </ h3 >
< input type = "text" id = "newFullName" placeholder = "Nombre Completo" >
< input type = "text" id = "newUsername" placeholder = "Usuario de acceso" >
< input type = "password" id = "newPassword" placeholder = "Contraseña" >
< select id = "newRole" >
< option value = "analista" > Analista </ option >
< option value = "supervisor" > Supervisor </ option >
< option value = "chats" > Chats </ option >
</ select >
< button id = "btnCreateUser" > Crear Cuenta </ button >
</ div >
User List Table
< table >
< thead >
< tr >
< th > Nombre </ th >
< th > Usuario </ th >
< th > Rol </ th >
< th > Acción </ th >
</ tr >
</ thead >
< tbody id = "usersTableBody" >
<!-- Dynamically populated -->
</ tbody >
</ table >
API Endpoints
Endpoint Method Purpose Auth Required /api/cashoutsPOST Submit new cashout ✅ /api/cashouts?status=pending&limit=300GET Fetch pending queue ✅ /api/cashoutsGET Fetch all cashouts (filtered) ✅
Base URL: https://general-cashouts.onrender.com/api
Queue Rendering Logic
Smart Update Strategy
To prevent UI flicker, the queue only re-renders when actual changes occur:
function renderizarCola ( items ) {
// Sort by newest first
items . sort (( a , b ) =>
new Date ( b . cashoutChecking || b . timestamp ) -
new Date ( a . cashoutChecking || a . timestamp )
);
const idsActuales = items . map ( i => i . row ). sort (). join ( ',' );
const idsEnUI = Array . from ( verificationQueue . querySelectorAll ( '[data-cashout-id]' ))
. map ( el => el . getAttribute ( 'data-cashout-id' ))
. sort (). join ( ',' );
// Only update timers if no structural changes
if ( idsActuales === idsEnUI && items . length > 0 ) {
items . forEach ( item => {
const div = verificationQueue . querySelector ( `[data-cashout-id=" ${ item . row } "]` );
if ( div && item . cashoutChecking ) {
const cro = div . querySelector ( '.cronometro-live' );
if ( cro ) cro . textContent = `⏱️ ${ calcularTiempoTranscurrido ( item . cashoutChecking ) } ` ;
}
});
return ; // Skip full re-render
}
// Full render if items changed
// ...
}
Notifications System
function showNotification ( msg , type = "success" ) {
notification . textContent = msg ;
notification . style . background = type === "error"
? "linear-gradient(135deg, #FF6B9D, #FFA07A)"
: "linear-gradient(135deg, #00C9FF, #92FE9D)" ;
notification . style . color = type === "error" ? "#fff" : "#003d5c" ;
notification . classList . add ( 'show' );
setTimeout (() => notification . classList . remove ( 'show' ), 3200 );
}
Usage examples:
showNotification("¡Cash out enviado correctamente!", "success")
showNotification("Error al subir cash out.", "error")
Best Practices
Always Include Observations for Edge Cases
Use the observation field to document unusual circumstances, helping reviewers make informed decisions.
Review Queue Should Stay Open
Keep the review queue tab active to benefit from auto-refresh and real-time updates.
Use Filters in Statistics
Apply date and company filters to generate targeted reports for specific periods or partners.
Queue items with long elapsed times may indicate stalled reviews that need attention.