fix(pos): cargar empleados reales en login en vez de datos demo
- Nuevo endpoint GET /pos/api/auth/employees/<tenant_id> - Login carga empleados dinamicamente del tenant - Si hay 1 solo empleado, se auto-selecciona - Removidos 6 usuarios hardcodeados del design system Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -157,6 +157,38 @@ def login_pin():
|
||||
})
|
||||
|
||||
|
||||
@auth_bp.route('/employees/<int:tenant_id>', methods=['GET'])
|
||||
def list_login_employees(tenant_id):
|
||||
"""Public endpoint: list employees for the login screen (names + roles only, no sensitive data)."""
|
||||
try:
|
||||
conn = get_tenant_conn(tenant_id)
|
||||
except ValueError:
|
||||
return jsonify({'error': 'Tenant not found'}), 404
|
||||
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT id, name, role FROM employees
|
||||
WHERE is_active = true AND pin IS NOT NULL
|
||||
ORDER BY name
|
||||
""")
|
||||
employees = []
|
||||
for row in cur.fetchall():
|
||||
name = row[1]
|
||||
parts = name.split()
|
||||
initials = ''.join([p[0].upper() for p in parts[:2]]) if parts else '?'
|
||||
role_labels = {'owner': 'Dueño', 'admin': 'Administrador', 'cashier': 'Cajero', 'warehouse': 'Almacén', 'accountant': 'Contador'}
|
||||
employees.append({
|
||||
'id': row[0],
|
||||
'name': name,
|
||||
'initials': initials,
|
||||
'role': row[2],
|
||||
'role_label': role_labels.get(row[2], row[2])
|
||||
})
|
||||
cur.close()
|
||||
conn.close()
|
||||
return jsonify({'data': employees})
|
||||
|
||||
|
||||
@auth_bp.route('/me', methods=['GET'])
|
||||
def auth_me():
|
||||
"""Get current employee info from token."""
|
||||
|
||||
@@ -890,92 +890,9 @@
|
||||
<!-- USER SELECTION -->
|
||||
<section class="users-section" aria-labelledby="users-label">
|
||||
<div class="section-label" id="users-label">Seleccionar usuario</div>
|
||||
<div class="users-grid" role="radiogroup" aria-label="Usuarios disponibles">
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="JR"
|
||||
data-name="J. Ramírez"
|
||||
data-role="Vendedor"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="Jorge Ramírez, Vendedor"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">JR</div>
|
||||
<div class="user-name">J. Ramírez</div>
|
||||
<div class="user-role">Vendedor</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="ML"
|
||||
data-name="M. López"
|
||||
data-role="Cajero"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="María López, Cajero"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">ML</div>
|
||||
<div class="user-name">M. López</div>
|
||||
<div class="user-role">Cajero</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="AP"
|
||||
data-name="A. Peña"
|
||||
data-role="Almacén"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="Alejandro Peña, Almacén"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">AP</div>
|
||||
<div class="user-name">A. Peña</div>
|
||||
<div class="user-role">Almacén</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="SC"
|
||||
data-name="S. Cruz"
|
||||
data-role="Supervisor"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="Sara Cruz, Supervisor"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">SC</div>
|
||||
<div class="user-name">S. Cruz</div>
|
||||
<div class="user-role">Supervisor</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="HG"
|
||||
data-name="H. García"
|
||||
data-role="Gerente"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="Hugo García, Gerente"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">HG</div>
|
||||
<div class="user-name">H. García</div>
|
||||
<div class="user-role">Gerente</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="RT"
|
||||
data-name="R. Torres"
|
||||
data-role="Vendedor"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="Roberto Torres, Vendedor"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">RT</div>
|
||||
<div class="user-name">R. Torres</div>
|
||||
<div class="user-role">Vendedor</div>
|
||||
</button>
|
||||
|
||||
<div class="users-grid" id="usersGrid" role="radiogroup" aria-label="Usuarios disponibles">
|
||||
<!-- Employees loaded dynamically from API -->
|
||||
<div style="text-align:center;padding:var(--space-4);color:var(--color-text-muted);">Cargando empleados...</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1105,28 +1022,8 @@
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
USER SELECTION
|
||||
USER SELECTION — handled dynamically in loadEmployees()
|
||||
------------------------------------------------------------------ */
|
||||
userBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
// Deselect all
|
||||
userBtns.forEach(b => {
|
||||
b.classList.remove('selected');
|
||||
b.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
|
||||
// Select clicked
|
||||
btn.classList.add('selected');
|
||||
btn.setAttribute('aria-checked', 'true');
|
||||
state.selectedUser = btn.dataset.user;
|
||||
state.pin = [];
|
||||
|
||||
// Enable PIN pad
|
||||
enablePinPad();
|
||||
updatePinDisplay();
|
||||
updateLoginButton();
|
||||
});
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
PIN PAD LOGIC
|
||||
@@ -1373,6 +1270,64 @@
|
||||
}
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
LOAD REAL EMPLOYEES FROM API
|
||||
------------------------------------------------------------------ */
|
||||
function loadEmployees() {
|
||||
if (!tenantId) {
|
||||
document.getElementById('usersGrid').innerHTML = '<div style="text-align:center;padding:var(--space-4);color:var(--color-error);">No se especificó tenant. Agrega ?tenant=ID a la URL.</div>';
|
||||
return;
|
||||
}
|
||||
fetch('/pos/api/auth/employees/' + tenantId)
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
var grid = document.getElementById('usersGrid');
|
||||
var employees = data.data || [];
|
||||
if (!employees.length) {
|
||||
grid.innerHTML = '<div style="text-align:center;padding:var(--space-4);color:var(--color-text-muted);">No hay empleados registrados.</div>';
|
||||
return;
|
||||
}
|
||||
grid.innerHTML = '';
|
||||
employees.forEach(function(emp) {
|
||||
var btn = document.createElement('button');
|
||||
btn.className = 'user-avatar-btn';
|
||||
btn.setAttribute('data-user', emp.initials);
|
||||
btn.setAttribute('data-name', emp.name);
|
||||
btn.setAttribute('data-role', emp.role_label);
|
||||
btn.setAttribute('role', 'radio');
|
||||
btn.setAttribute('aria-checked', 'false');
|
||||
btn.setAttribute('aria-label', emp.name + ', ' + emp.role_label);
|
||||
btn.innerHTML = '<div class="user-initials" aria-hidden="true">' + emp.initials + '</div>'
|
||||
+ '<div class="user-name">' + emp.name + '</div>'
|
||||
+ '<div class="user-role">' + emp.role_label + '</div>';
|
||||
btn.addEventListener('click', function() {
|
||||
// Deselect all
|
||||
grid.querySelectorAll('.user-avatar-btn').forEach(function(b) {
|
||||
b.classList.remove('selected');
|
||||
b.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
// Select this one
|
||||
btn.classList.add('selected');
|
||||
btn.setAttribute('aria-checked', 'true');
|
||||
state.selectedUser = emp.initials;
|
||||
state.pin = [];
|
||||
enablePinPad();
|
||||
updatePinDisplay();
|
||||
updateLoginButton();
|
||||
});
|
||||
grid.appendChild(btn);
|
||||
});
|
||||
|
||||
// If only 1 employee, auto-select
|
||||
if (employees.length === 1) {
|
||||
grid.querySelector('.user-avatar-btn').click();
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
document.getElementById('usersGrid').innerHTML = '<div style="text-align:center;padding:var(--space-4);color:var(--color-error);">Error al cargar empleados.</div>';
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
INIT
|
||||
------------------------------------------------------------------ */
|
||||
@@ -1380,6 +1335,8 @@
|
||||
disablePinPad();
|
||||
updatePinDisplay();
|
||||
updateLoginButton();
|
||||
// Load real employees
|
||||
loadEmployees();
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user