feat(pos): configuracion funcional — sucursales, empleados, tema

Replace hardcoded demo data in the config page with real API calls.
Branches and employees now load from /pos/api/config/* endpoints,
with create modals for both. Business data tab reads tenant_config
(read-only). Theme selector was already working and is preserved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 02:15:24 +00:00
parent f85e53cee8
commit cc2336ca06
3 changed files with 546 additions and 156 deletions

View File

@@ -1332,40 +1332,34 @@
<div class="form-grid">
<div class="form-group">
<label class="form-label">Razón Social</label>
<input class="form-input" type="text" value="Nexus Autoparts S.A. de C.V." />
<input class="form-input" id="biz-razon-social" type="text" value="" readonly />
</div>
<div class="form-group">
<label class="form-label">Nombre Comercial</label>
<input class="form-input" type="text" value="Nexus Autoparts" />
<input class="form-input" id="biz-nombre" type="text" value="" readonly />
</div>
<div class="form-group">
<label class="form-label">RFC</label>
<input class="form-input" type="text" value="NAU260101ABC" />
<input class="form-input" id="biz-rfc" type="text" value="" readonly />
</div>
<div class="form-group">
<label class="form-label">Régimen Fiscal</label>
<select class="form-select">
<option selected>601 - General de Ley Personas Morales</option>
<option>603 - Personas Morales con Fines no Lucrativos</option>
<option>612 - Personas Físicas con Actividades Empresariales</option>
</select>
<input class="form-input" id="biz-regimen" type="text" value="" readonly />
</div>
<div class="form-group form-group--full">
<label class="form-label">Dirección Fiscal</label>
<input class="form-input" type="text" value="Av. Insurgentes Sur 1234, Col. Del Valle, Benito Juárez, CDMX, C.P. 03100" />
<input class="form-input" id="biz-direccion" type="text" value="" readonly />
</div>
<div class="form-group">
<label class="form-label">Teléfono</label>
<input class="form-input" type="tel" value="(55) 1234-5678" />
<input class="form-input" id="biz-telefono" type="tel" value="" readonly />
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input class="form-input" type="email" value="contacto@nexusautoparts.mx" />
<input class="form-input" id="biz-email" type="email" value="" readonly />
</div>
</div>
<div class="btn-group">
<button class="btn btn--primary btn--sm">Guardar</button>
</div>
<p class="form-hint" style="margin-top: var(--space-3);">Datos configurados durante el aprovisionamiento del tenant. Contacta soporte para cambios.</p>
</div>
</div>
@@ -1385,10 +1379,10 @@
<div class="settings-card" style="padding: 0;">
<div style="padding: var(--space-4) var(--space-5); display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--color-border);">
<span style="font-weight: var(--font-weight-semibold); color: var(--color-text-primary);">5 usuarios activos</span>
<button class="btn btn--primary btn--sm">
<span id="employees-count" style="font-weight: var(--font-weight-semibold); color: var(--color-text-primary);">Cargando...</span>
<button class="btn btn--primary btn--sm" id="btn-new-employee">
<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
Nuevo Usuario
Nuevo Empleado
</button>
</div>
<div class="table-wrapper" style="border: none; border-radius: 0; box-shadow: none;">
@@ -1400,81 +1394,12 @@
<th>Rol</th>
<th>Sucursal</th>
<th>Estado</th>
<th>Último Acceso</th>
<th>Desc. Max</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="user-cell">
<div class="user-cell__avatar">AM</div>
<span class="user-cell__name">Adrián Morales</span>
</div>
</td>
<td>admin@nexusautoparts.mx</td>
<td><span class="badge badge--admin">Admin</span></td>
<td>Todas</td>
<td><span class="badge badge--ok">Activo</span></td>
<td>Hoy, 14:32</td>
<td><button class="btn btn--ghost btn--sm">Editar</button></td>
</tr>
<tr>
<td>
<div class="user-cell">
<div class="user-cell__avatar">CM</div>
<span class="user-cell__name">Carlos Mendoza</span>
</div>
</td>
<td>carlos@nexusautoparts.mx</td>
<td><span class="badge badge--pending">Contador</span></td>
<td>Matriz</td>
<td><span class="badge badge--ok">Activo</span></td>
<td>Hoy, 12:15</td>
<td><button class="btn btn--ghost btn--sm">Editar</button></td>
</tr>
<tr>
<td>
<div class="user-cell">
<div class="user-cell__avatar">LR</div>
<span class="user-cell__name">Laura Ríos</span>
</div>
</td>
<td>laura@nexusautoparts.mx</td>
<td><span class="badge badge--ok">Vendedor</span></td>
<td>Matriz</td>
<td><span class="badge badge--ok">Activo</span></td>
<td>Hoy, 15:48</td>
<td><button class="btn btn--ghost btn--sm">Editar</button></td>
</tr>
<tr>
<td>
<div class="user-cell">
<div class="user-cell__avatar">JP</div>
<span class="user-cell__name">Jorge Pérez</span>
</div>
</td>
<td>jorge@nexusautoparts.mx</td>
<td><span class="badge badge--ok">Vendedor</span></td>
<td>Sucursal Norte</td>
<td><span class="badge badge--ok">Activo</span></td>
<td>Ayer, 18:20</td>
<td><button class="btn btn--ghost btn--sm">Editar</button></td>
</tr>
<tr>
<td>
<div class="user-cell">
<div class="user-cell__avatar">RG</div>
<span class="user-cell__name">Roberto García</span>
</div>
</td>
<td>roberto@nexusautoparts.mx</td>
<td><span class="badge badge--ok">Bodega</span></td>
<td>Matriz</td>
<td><span class="badge badge--ok">Activo</span></td>
<td>Hoy, 09:30</td>
<td><button class="btn btn--ghost btn--sm">Editar</button></td>
</tr>
<tbody id="employees-tbody">
<tr><td colspan="7" style="text-align:center; padding: var(--space-5);">Cargando empleados...</td></tr>
</tbody>
</table>
</div>
@@ -1568,50 +1493,8 @@
</div>
</div>
<div class="device-grid">
<div class="device-card">
<div class="device-card__icon">
<svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
</div>
<div class="device-card__body">
<div class="device-card__name">Matriz — Centro</div>
<div class="device-card__detail">
<span class="badge badge--ok" style="padding: 0 4px; font-size: 0.625rem;">Principal</span>
</div>
<div class="device-card__detail">Av. Insurgentes Sur 1234, Col. Del Valle</div>
<div class="device-card__detail">3 empleados · 2 terminales POS</div>
<div class="device-card__actions">
<button class="btn btn--ghost btn--sm">Editar</button>
</div>
</div>
</div>
<div class="device-card">
<div class="device-card__icon">
<svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
</div>
<div class="device-card__body">
<div class="device-card__name">Sucursal Norte</div>
<div class="device-card__detail">
<span class="badge badge--ok" style="padding: 0 4px; font-size: 0.625rem;">Activa</span>
</div>
<div class="device-card__detail">Blvd. Ávila Camacho 456, Naucalpan</div>
<div class="device-card__detail">2 empleados · 1 terminal POS</div>
<div class="device-card__actions">
<button class="btn btn--ghost btn--sm">Editar</button>
</div>
</div>
</div>
<div class="device-card" style="border-style: dashed; cursor: pointer;">
<div class="device-card__icon" style="background: transparent; border: 2px dashed var(--color-border);">
<svg viewBox="0 0 24 24" style="stroke: var(--color-text-muted);"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</div>
<div class="device-card__body">
<div class="device-card__name" style="color: var(--color-text-muted);">Agregar Sucursal</div>
<div class="device-card__detail">Configura una nueva ubicación</div>
</div>
</div>
<div class="device-grid" id="branches-grid">
<div class="device-card"><div class="device-card__body" style="text-align:center;">Cargando sucursales...</div></div>
</div>
</div>
@@ -1823,6 +1706,176 @@
</main>
</div><!-- /app-shell -->
<!-- =====================================================================
MODALS
===================================================================== -->
<!-- Modal: Nueva Sucursal -->
<div class="cfg-modal-overlay" id="modal-branch" style="display:none;">
<div class="cfg-modal">
<div class="cfg-modal__header">
<h3 class="cfg-modal__title">Nueva Sucursal</h3>
<button class="cfg-modal__close" onclick="Config.closeModal('modal-branch')">&times;</button>
</div>
<div class="cfg-modal__body">
<div class="form-grid">
<div class="form-group form-group--full">
<label class="form-label">Nombre</label>
<input class="form-input" id="branch-name" type="text" placeholder="Ej. Sucursal Norte" />
</div>
<div class="form-group form-group--full">
<label class="form-label">Direccion</label>
<input class="form-input" id="branch-address" type="text" placeholder="Calle, Colonia, Ciudad" />
</div>
<div class="form-group form-group--full">
<label class="form-label">Telefono</label>
<input class="form-input" id="branch-phone" type="tel" placeholder="(55) 1234-5678" />
</div>
</div>
</div>
<div class="cfg-modal__footer">
<button class="btn btn--ghost" onclick="Config.closeModal('modal-branch')">Cancelar</button>
<button class="btn btn--primary" id="btn-save-branch">Guardar Sucursal</button>
</div>
</div>
</div>
<!-- Modal: Nuevo Empleado -->
<div class="cfg-modal-overlay" id="modal-employee" style="display:none;">
<div class="cfg-modal">
<div class="cfg-modal__header">
<h3 class="cfg-modal__title">Nuevo Empleado</h3>
<button class="cfg-modal__close" onclick="Config.closeModal('modal-employee')">&times;</button>
</div>
<div class="cfg-modal__body">
<div class="form-grid">
<div class="form-group">
<label class="form-label">Nombre Completo *</label>
<input class="form-input" id="emp-name" type="text" placeholder="Nombre y apellido" />
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input class="form-input" id="emp-email" type="email" placeholder="correo@empresa.mx" />
</div>
<div class="form-group">
<label class="form-label">Telefono</label>
<input class="form-input" id="emp-phone" type="tel" placeholder="(55) 1234-5678" />
</div>
<div class="form-group">
<label class="form-label">Rol *</label>
<select class="form-select" id="emp-role">
<option value="">-- Seleccionar --</option>
<option value="admin">Administrador</option>
<option value="cashier">Cajero</option>
<option value="warehouse">Almacenista</option>
<option value="accountant">Contador</option>
</select>
</div>
<div class="form-group">
<label class="form-label">PIN (4 digitos) *</label>
<input class="form-input" id="emp-pin" type="password" maxlength="4" pattern="\d{4}" placeholder="0000" />
</div>
<div class="form-group">
<label class="form-label">Sucursal</label>
<select class="form-select" id="emp-branch">
<option value="">-- Todas --</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Descuento Max. (%)</label>
<input class="form-input" id="emp-discount" type="number" min="0" max="100" value="0" />
</div>
</div>
</div>
<div class="cfg-modal__footer">
<button class="btn btn--ghost" onclick="Config.closeModal('modal-employee')">Cancelar</button>
<button class="btn btn--primary" id="btn-save-employee">Guardar Empleado</button>
</div>
</div>
</div>
<style>
/* Modal styles */
.cfg-modal-overlay {
position: fixed;
inset: 0;
z-index: 9999;
background: rgba(0,0,0,0.6);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
animation: fadeIn var(--duration-fast) var(--ease-in-out);
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.cfg-modal {
background: var(--color-bg-elevated);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
width: 520px;
max-width: 90vw;
max-height: 80vh;
overflow-y: auto;
box-shadow: var(--shadow-lg);
}
.cfg-modal__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-4) var(--space-5);
border-bottom: 1px solid var(--color-border);
}
.cfg-modal__title {
font-family: var(--font-heading);
font-weight: var(--heading-weight-secondary);
font-size: var(--text-h5);
color: var(--color-text-primary);
}
.cfg-modal__close {
background: none;
border: none;
font-size: 1.5rem;
color: var(--color-text-muted);
cursor: pointer;
line-height: 1;
}
.cfg-modal__close:hover { color: var(--color-text-primary); }
.cfg-modal__body { padding: var(--space-5); }
.cfg-modal__footer {
display: flex;
justify-content: flex-end;
gap: var(--space-3);
padding: var(--space-4) var(--space-5);
border-top: 1px solid var(--color-border);
}
/* Extra badge colors for roles */
.badge--owner { background: rgba(245,166,35,0.15); color: var(--color-primary); }
.badge--blue { background: rgba(99,102,241,0.15); color: #818cf8; }
.badge--green { background: rgba(34,197,94,0.15); color: var(--color-success); }
.badge--yellow { background: rgba(234,179,8,0.15); color: #eab308; }
.badge--purple { background: rgba(168,85,247,0.15); color: #a855f7; }
/* Toast notification */
.cfg-toast {
position: fixed;
bottom: var(--space-5);
right: var(--space-5);
z-index: 10000;
padding: var(--space-3) var(--space-5);
border-radius: var(--radius-md);
font-size: var(--text-body-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-inverse);
animation: slideUp var(--duration-normal) var(--ease-in-out);
box-shadow: var(--shadow-lg);
}
.cfg-toast--ok { background: var(--color-success); }
.cfg-toast--error { background: var(--color-error, #ef4444); }
@keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
</style>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/config.js"></script>