Autosave: 20260724-091841

This commit is contained in:
Flatlogic Bot 2026-07-24 09:17:16 +00:00
parent 23af7294d3
commit 5437326dee
4 changed files with 371 additions and 90 deletions

View File

@ -1081,7 +1081,7 @@ require_once 'layout_header.php';
<h1 class="h2 fw-bold mb-1"><i class="bi bi-headset text-primary"></i> <?php echo htmlspecialchars($callCenterSectionLabel); ?></h1>
<div class="d-flex flex-wrap gap-2 mt-3">
<?php if ($isAdmin): ?>
<a href="<?php echo htmlspecialchars(cc_test_build_url('gestiones_callcenter.php', $callCenterParams)); ?>" class="btn btn-sm btn-outline-primary">Base de Datos Pedidos</a>
<a href="<?php echo htmlspecialchars(cc_test_build_url('gestiones_callcenter.php', $callCenterBaseParams)); ?>" class="btn btn-sm btn-outline-primary">Base de Datos Pedidos</a>
<?php endif; ?>
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', $callCenterParams)); ?>" class="btn btn-sm btn-primary"><?php echo htmlspecialchars($callCenterSectionLabel); ?></a>
</div>
@ -1167,8 +1167,9 @@ require_once 'layout_header.php';
</select>
</div>
<div class="col-auto d-flex gap-2">
<div class="col-auto d-flex flex-wrap gap-2">
<button type="submit" class="btn btn-primary">Aplicar filtro</button>
<a href="<?php echo htmlspecialchars(cc_test_build_url('manage_users.php', ['role' => 'Asesor']) . '#crear-asesora'); ?>" class="btn btn-outline-primary">Agregar asesora</a>
<?php if ($selectedAssessorFilter !== ''): ?>
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterBaseParams, ['assessor' => 'TODAS']))); ?>" class="btn btn-outline-secondary">Ver todas</a>
<?php endif; ?>
@ -1368,7 +1369,7 @@ require_once 'layout_header.php';
: ($assignedAssessorKey !== null ? 'Guardar asignación' : 'Desasignar');
?>
<form method="post" class="<?php echo htmlspecialchars($assignFormClass); ?>"<?php echo $assignFormStyle !== '' ? ' style="' . htmlspecialchars($assignFormStyle, ENT_QUOTES) . '"' : ''; ?>>
<form method="post" class="<?php echo htmlspecialchars($assignFormClass); ?>"<?php echo $assignFormStyle !== '' ? ' style="' . htmlspecialchars($assignFormStyle, ENT_QUOTES) . '"' : ''; ?> autocomplete="off">
<input type="hidden" name="action" value="assign_assessor">
<input type="hidden" name="source_key" value="<?php echo htmlspecialchars($order['source_key']); ?>">
@ -1387,7 +1388,8 @@ require_once 'layout_header.php';
</div>
<div class="cc-callcenter-assign-row">
<select name="target_assessor" class="form-select form-select-sm">
<input type="hidden" name="target_assessor" class="js-assessor-hidden" value="<?php echo htmlspecialchars((string) $selectedAssessorKeyForSelect); ?>">
<select class="form-select form-select-sm js-assessor-select" data-server-assessor="<?php echo htmlspecialchars((string) $selectedAssessorKeyForSelect); ?>" autocomplete="off" data-lpignore="true" data-1p-ignore="true" data-bwignore="true">
<option value=""<?php echo ($selectedAssessorKeyForSelect === '') ? ' selected' : ''; ?>>Sin asignar</option>
<?php foreach ($assessors as $assessorKey => $assessor): ?>
@ -2547,6 +2549,52 @@ if (airDroidCopyButton) {
});
}
function syncServerAssessorSelect(select) {
if (!select) {
return;
}
const form = select.closest('form');
const hiddenInput = form ? form.querySelector('.js-assessor-hidden') : null;
const serverAssessor = typeof select.dataset.serverAssessor === 'string'
? (select.dataset.serverAssessor || '')
: (hiddenInput ? (hiddenInput.value || '') : '');
if ((select.value || '') !== serverAssessor) {
select.value = serverAssessor;
}
if (hiddenInput && (hiddenInput.value || '') !== (select.value || '')) {
hiddenInput.value = select.value || '';
}
}
function syncServerAssessorSelects() {
document.querySelectorAll('.cc-callcenter-assign-form').forEach(function (form) {
const select = form.querySelector('.js-assessor-select');
const hiddenInput = form.querySelector('.js-assessor-hidden');
syncServerAssessorSelect(select);
if (select && hiddenInput && !form.dataset.assessorSyncBound) {
select.addEventListener('change', function () {
hiddenInput.value = select.value || '';
});
form.addEventListener('submit', function () {
hiddenInput.value = select.value || '';
});
form.dataset.assessorSyncBound = '1';
}
});
}
syncServerAssessorSelects();
window.addEventListener('pageshow', syncServerAssessorSelects);
window.addEventListener('load', syncServerAssessorSelects);
window.requestAnimationFrame(syncServerAssessorSelects);
window.setTimeout(syncServerAssessorSelects, 0);
window.setTimeout(syncServerAssessorSelects, 120);
window.setTimeout(syncServerAssessorSelects, 600);
const ccPanelLiveEnabled = <?php echo ($isAdmin || $isTuaniRecuperablesStore) ? 'false' : 'true'; ?>;
if (ccPanelLiveEnabled) {
const ccPanelRoot = document.querySelector('[data-cc-panel-root="1"]');

View File

@ -378,13 +378,11 @@ try {
cc_test_ensure_tracking_table($pdo);
cc_test_ensure_historial_llamadas_table($pdo);
$assessors = cc_test_fetch_assessors($pdo);
$selectedAssessorFilter = cc_test_resolve_callcenter_assessor_filter($assessors, $_GET['assessor'] ?? null, true, true);
$selectedAssessorFilter = cc_test_resolve_callcenter_assessor_filter($assessors, $_GET['assessor'] ?? null, false, false);
$selectedAssessorFilterLabel = $selectedAssessorFilter !== ''
? (string) ($assessors[$selectedAssessorFilter]['label'] ?? $selectedAssessorFilter)
: '';
$persistedAssessorParams = $selectedAssessorFilter !== ''
? ['assessor' => $selectedAssessorFilter]
: [];
$persistedAssessorParams = [];
if ($selectedAssessorFilterLabel !== '') {
$pageTitle = 'Base de Datos Pedidos | Asignación · ' . $selectedAssessorFilterLabel;
}
@ -704,7 +702,7 @@ try {
});
$orderedAssessorIds = [];
foreach (cc_test_allowed_module_user_keys() as $assessorKey) {
foreach (cc_test_ordered_assessor_keys($assessors) as $assessorKey) {
if (isset($assessors[$assessorKey])) {
$orderedAssessorIds[] = (int) ($assessors[$assessorKey]['id'] ?? 0);
}
@ -945,13 +943,7 @@ try {
'footnote' => 'Seguimiento abierto = pedidos asignados hoy en POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO y SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.',
]);
$orderedAssessorKeys = array_values(array_filter(cc_test_allowed_module_user_keys(), static function (string $key) use ($assessors): bool {
return isset($assessors[$key]);
}));
if (empty($orderedAssessorKeys)) {
$orderedAssessorKeys = array_keys($assessors);
}
$orderedAssessorKeys = cc_test_ordered_assessor_keys($assessors);
$recommendedAssessorKey = null;
$minPending = PHP_INT_MAX;
@ -998,6 +990,28 @@ try {
];
}
usort($assessorSummaryCards, static function (array $a, array $b): int {
$aPending = (int) ($a['pending'] ?? 0);
$bPending = (int) ($b['pending'] ?? 0);
if ($aPending !== $bPending) {
return $bPending <=> $aPending;
}
return strcmp((string) ($a['label'] ?? ''), (string) ($b['label'] ?? ''));
});
$activeAssessorSummaryCards = array_values(array_filter(
$assessorSummaryCards,
static fn (array $card): bool => (int) ($card['pending'] ?? 0) > 0
));
$inactiveAssessorSummaryCards = array_values(array_filter(
$assessorSummaryCards,
static fn (array $card): bool => (int) ($card['pending'] ?? 0) <= 0
));
$activeAssessorSummaryCount = count($activeAssessorSummaryCards);
$inactiveAssessorSummaryCount = count($inactiveAssessorSummaryCards);
$assessorUiCatalog = [];
foreach ($orderedAssessorKeys as $assessorKey) {
if (!isset($assessors[$assessorKey])) {
@ -1336,6 +1350,59 @@ require_once 'layout_header.php';
flex: 0 0 auto;
}
.cc-callcenter-assessor-summary-card.is-inactive {
opacity: .82;
filter: saturate(.92);
}
.cc-callcenter-assessor-add-card {
border: 1px dashed rgba(13, 110, 253, .28) !important;
background: linear-gradient(180deg, rgba(13, 110, 253, .06), rgba(13, 110, 253, .02));
box-shadow: inset 0 0 0 1px rgba(13, 110, 253, .06);
transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.cc-callcenter-assessor-add-card:hover {
transform: translateY(-2px);
border-color: rgba(13, 110, 253, .55) !important;
box-shadow: 0 .75rem 1.5rem rgba(13, 110, 253, .08), inset 0 0 0 1px rgba(13, 110, 253, .08);
}
.cc-callcenter-add-icon {
width: 2.4rem;
height: 2.4rem;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(13, 110, 253, .12);
color: #0d6efd;
flex: 0 0 auto;
}
.cc-callcenter-empty-assessor-state {
border: 1px dashed rgba(13, 110, 253, .2);
background: linear-gradient(180deg, rgba(13, 110, 253, .05), rgba(13, 110, 253, .02));
}
.cc-callcenter-details-summary {
list-style: none;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: .5rem;
padding: .55rem .9rem;
border-radius: 999px;
background: #F8F9FA;
border: 1px solid rgba(0, 0, 0, .08);
color: #495057;
font-weight: 600;
}
.cc-callcenter-details-summary::-webkit-details-marker {
display: none;
}
.cc-callcenter-action-btn {
padding: .2rem .55rem;
line-height: 1.1;
@ -1343,6 +1410,31 @@ require_once 'layout_header.php';
white-space: nowrap;
}
.cc-reparto-card .card-body {
padding: .85rem !important;
}
.cc-reparto-chip {
padding: .32rem .55rem;
font-size: .72rem;
line-height: 1.15;
}
.cc-reparto-summary-note {
font-size: .82rem;
line-height: 1.35;
}
.cc-reparto-state-grid {
max-height: 18rem;
overflow: auto;
padding-right: .25rem;
}
.cc-reparto-state-card {
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .36);
}
@media (min-width: 992px) {
.cc-callcenter-table tbody td {
padding-top: .42rem;
@ -1474,40 +1566,105 @@ require_once 'layout_header.php';
<section class="mb-4">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
<div>
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-start gap-3 mb-3">
<div class="flex-grow-1">
<h2 class="h5 fw-bold mb-1">
<i class="bi bi-activity text-primary"></i> Rendimiento por asesora
</h2>
<div class="text-muted small">Pendientes = <strong>POR LLAMAR</strong> / <strong>DEVOLVER LLAMADA</strong> / <strong>OBSERVADO</strong></div>
<div class="text-muted small">Mostramos primero las asesoras con actividad para que la vista se vea más limpia. Si necesitas sumar otra, usa la tarjeta de alta al final.</div>
<div class="d-flex flex-wrap gap-2 mt-2">
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis border">Activas: <?php echo (int) $activeAssessorSummaryCount; ?></span>
<span class="badge rounded-pill bg-light text-dark border">Sin actividad: <?php echo (int) $inactiveAssessorSummaryCount; ?></span>
</div>
</div>
<div class="d-flex flex-wrap gap-2 align-items-stretch justify-content-lg-end">
<?php foreach ($assessorSummaryCards as $assessorCard): ?>
<div class="border rounded-3 bg-light px-3 py-2 cc-callcenter-assessor-summary-card" style="min-width: 240px; <?php echo htmlspecialchars((string) $assessorCard['accent_style']); ?>">
<div class="d-flex justify-content-between align-items-center gap-2">
<div class="fw-bold d-flex align-items-center gap-2">
<span class="cc-callcenter-color-dot" aria-hidden="true"></span>
<span><?php echo htmlspecialchars((string) $assessorCard['label']); ?></span>
<form method="post" class="d-flex flex-column gap-2 align-items-stretch" style="min-width: 220px;">
<input type="hidden" name="action" value="auto_assign_cupo">
<button type="submit" class="btn btn-primary btn-sm w-100" <?php echo $tieneCupos ? '' : 'disabled'; ?>>
Rellenar cupos
</button>
<div class="small text-muted">Asigna pedidos sin asignar hasta completar el cupo.</div>
</form>
</div>
<?php if ($activeAssessorSummaryCount > 0): ?>
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3">
<?php foreach ($activeAssessorSummaryCards as $assessorCard): ?>
<div class="col">
<div class="card border-0 shadow-sm h-100 cc-callcenter-assessor-summary-card" style="<?php echo htmlspecialchars((string) $assessorCard['accent_style']); ?>">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center gap-2">
<div class="fw-bold d-flex align-items-center gap-2">
<span class="cc-callcenter-color-dot" aria-hidden="true"></span>
<span><?php echo htmlspecialchars((string) $assessorCard['label']); ?></span>
</div>
<span class="badge border cc-callcenter-assessor-badge">Pendientes: <?php echo (int) $assessorCard['pending']; ?></span>
</div>
<div class="mt-2">
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
<div class="h3 fw-bold mb-0"><?php echo (int) $assessorCard['remaining']; ?></div>
</div>
</div>
<span class="badge border cc-callcenter-assessor-badge">Pendientes: <?php echo (int) $assessorCard['pending']; ?></span>
</div>
<div class="mt-2">
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
<div class="h3 fw-bold mb-0"><?php echo (int) $assessorCard['remaining']; ?></div>
</div>
</div>
<?php endforeach; ?>
<form method="post" class="d-flex flex-column gap-2" style="min-width: 220px;">
<input type="hidden" name="action" value="auto_assign_cupo">
<button type="submit" class="btn btn-primary btn-sm w-100" <?php echo $tieneCupos ? '' : 'disabled'; ?>>
Rellenar cupos
</button>
<div class="small text-muted">Asigna pedidos sin asignar hasta completar el cupo.</div>
</form>
<div class="col">
<a href="<?php echo htmlspecialchars(cc_test_build_url('manage_users.php', ['role' => 'Asesor']) . '#crear-asesora'); ?>" class="card border-0 shadow-sm h-100 text-decoration-none cc-callcenter-assessor-add-card">
<div class="card-body d-flex flex-column justify-content-between h-100">
<div>
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis border">Opcional</span>
<h3 class="h6 fw-bold mt-3 mb-1">Agregar más asesoras</h3>
<p class="text-muted small mb-0">Abre el alta de usuarios cuando quieras sumar otra persona al equipo.</p>
</div>
<div class="d-inline-flex align-items-center gap-2 mt-3 fw-semibold">
<span class="cc-callcenter-add-icon" aria-hidden="true"><i class="bi bi-person-plus"></i></span>
<span>Abrir alta</span>
</div>
</div>
</a>
</div>
</div>
</div>
<?php else: ?>
<div class="cc-callcenter-empty-assessor-state rounded-4 p-4">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3">
<div>
<div class="fw-semibold mb-1">No hay asesoras con actividad en este momento</div>
<div class="text-muted small mb-0">Cuando una asesora reciba pedidos, aparecerá aquí. Mientras tanto puedes crear una nueva o revisar las existentes sin actividad.</div>
</div>
<a href="<?php echo htmlspecialchars(cc_test_build_url('manage_users.php', ['role' => 'Asesor']) . '#crear-asesora'); ?>" class="btn btn-outline-primary">
<i class="bi bi-person-plus me-1"></i> Agregar asesora
</a>
</div>
</div>
<?php endif; ?>
<?php if ($inactiveAssessorSummaryCount > 0): ?>
<details class="mt-3">
<summary class="cc-callcenter-details-summary">Ver <?php echo (int) $inactiveAssessorSummaryCount; ?> asesora<?php echo $inactiveAssessorSummaryCount === 1 ? '' : 's'; ?> sin actividad</summary>
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3 mt-2">
<?php foreach ($inactiveAssessorSummaryCards as $assessorCard): ?>
<div class="col">
<div class="card border-0 shadow-sm h-100 cc-callcenter-assessor-summary-card is-inactive" style="<?php echo htmlspecialchars((string) $assessorCard['accent_style']); ?>">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center gap-2">
<div class="fw-bold d-flex align-items-center gap-2">
<span class="cc-callcenter-color-dot" aria-hidden="true"></span>
<span><?php echo htmlspecialchars((string) $assessorCard['label']); ?></span>
</div>
<span class="badge border cc-callcenter-assessor-badge">Pendientes: <?php echo (int) $assessorCard['pending']; ?></span>
</div>
<div class="mt-2">
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
<div class="h3 fw-bold mb-0"><?php echo (int) $assessorCard['remaining']; ?></div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</details>
<?php endif; ?>
</div>
</div>
</section>
@ -1525,53 +1682,55 @@ require_once 'layout_header.php';
? cc_test_format_date((string) $repartoSettings['rotation_date'], 'Sin fecha')
: 'Sin ciclo aún';
?>
<section class="card border-0 shadow-sm mb-4" id="repartoIntercalado">
<section class="card border-0 shadow-sm mb-3 cc-reparto-card" id="repartoIntercalado">
<div class="card-body">
<div class="row g-3 align-items-start">
<div class="col-12 col-xl-7">
<div class="d-flex flex-wrap align-items-center gap-2 mb-2">
<span class="badge rounded-pill <?php echo htmlspecialchars($repartoModeClass); ?>">Reparto <?php echo htmlspecialchars($repartoModeLabel); ?></span>
<div class="row g-2 align-items-start">
<div class="col-12 col-xl-6">
<div class="d-flex flex-wrap align-items-center gap-1 mb-2">
<span class="badge rounded-pill cc-reparto-chip <?php echo htmlspecialchars($repartoModeClass); ?>">Reparto <?php echo htmlspecialchars($repartoModeLabel); ?></span>
<?php if ($repartoNextLabel !== ''): ?>
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis border">Siguiente: <?php echo htmlspecialchars($repartoNextLabel); ?></span>
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis border cc-reparto-chip">Siguiente: <?php echo htmlspecialchars($repartoNextLabel); ?></span>
<?php else: ?>
<span class="badge rounded-pill bg-warning-subtle text-warning-emphasis border">Sin asesoras disponibles</span>
<span class="badge rounded-pill bg-warning-subtle text-warning-emphasis border cc-reparto-chip">Sin asesoras disponibles</span>
<?php endif; ?>
<span class="badge rounded-pill bg-light text-dark border">Sin asignar: <?php echo (int) $repartoUnassignedCount; ?></span>
<span class="badge rounded-pill bg-light text-dark border">Elegibles para reparto: <?php echo (int) $repartoEligibleUnassignedCount; ?></span>
<span class="badge rounded-pill bg-light text-dark border cc-reparto-chip">Sin asignar: <?php echo (int) $repartoUnassignedCount; ?></span>
<span class="badge rounded-pill bg-light text-dark border cc-reparto-chip">Elegibles: <?php echo (int) $repartoEligibleUnassignedCount; ?></span>
</div>
<div class="d-flex flex-wrap align-items-center gap-2 mb-2">
<h2 class="h6 fw-bold mb-0">Reparto de pedidos pendientes</h2>
<span class="text-muted small cc-reparto-summary-note">Vista compacta para revisar y cambiar el reparto sin ocupar tanto alto.</span>
</div>
<h2 class="h5 fw-bold mb-2">Reparto de pedidos pendientes</h2>
<p class="text-muted small mb-3">El historial anterior se mantiene visible. El reparto manual trabaja sobre los pedidos actuales sin ocultar los anteriores.</p>
<div class="d-flex flex-wrap gap-2">
<span class="badge rounded-pill bg-light text-dark border">Asesoras totales: <?php echo (int) $repartoTotalCount; ?></span>
<span class="badge rounded-pill bg-light text-dark border">Disponibles: <?php echo (int) $repartoAvailableCount; ?></span>
<span class="badge rounded-pill bg-light text-dark border">Ciclo: <?php echo htmlspecialchars($repartoRotationDateLabel); ?></span>
<span class="badge rounded-pill bg-light text-dark border cc-reparto-chip">Asesoras totales: <?php echo (int) $repartoTotalCount; ?></span>
<span class="badge rounded-pill bg-light text-dark border cc-reparto-chip">Disponibles: <?php echo (int) $repartoAvailableCount; ?></span>
<span class="badge rounded-pill bg-light text-dark border cc-reparto-chip">Ciclo: <?php echo htmlspecialchars($repartoRotationDateLabel); ?></span>
</div>
<div class="d-flex flex-wrap gap-2 mt-3">
<div class="d-flex flex-wrap gap-2 mt-2">
<?php foreach (($repartoPreview['sequence'] ?? []) as $item): ?>
<span class="badge rounded-pill <?php echo htmlspecialchars((string) ($item['badge_class'] ?? 'bg-light text-dark border')); ?>">
<span class="badge rounded-pill <?php echo htmlspecialchars((string) ($item['badge_class'] ?? 'bg-light text-dark border')); ?> cc-reparto-chip">
<?php echo htmlspecialchars((string) ($item['label'] ?? '')); ?> · <?php echo htmlspecialchars((string) ($item['state_label'] ?? '')); ?>
</span>
<?php endforeach; ?>
</div>
</div>
<div class="col-12 col-xl-5">
<form method="post" class="border rounded-4 p-3 bg-light h-100">
<div class="col-12 col-xl-6">
<form method="post" class="border rounded-4 p-2 bg-light h-100">
<input type="hidden" name="action" value="save_reparto_config">
<input type="hidden" name="store" value="<?php echo htmlspecialchars($storeKey); ?>">
<input type="hidden" name="view" value="<?php echo htmlspecialchars($view); ?>">
<div class="mb-3">
<label class="form-label small text-uppercase text-muted fw-semibold mb-1" for="repartoMode">Modo de reparto</label>
<select name="reparto_mode" id="repartoMode" class="form-select">
<div class="mb-2">
<label class="form-label small text-muted fw-semibold mb-1" for="repartoMode">Modo de reparto</label>
<select name="reparto_mode" id="repartoMode" class="form-select form-select-sm">
<option value="manual"<?php echo ($repartoPreview['mode'] ?? 'manual') === 'manual' ? ' selected' : ''; ?>>Manual</option>
</select>
</div>
<div class="row g-2">
<div class="row row-cols-1 row-cols-md-2 g-2 cc-reparto-state-grid">
<?php foreach (($repartoPreview['sequence'] ?? []) as $item): ?>
<div class="col-12">
<div class="bg-white border rounded-4 p-2">
<div class="col">
<div class="bg-white border rounded-3 p-2 h-100 cc-reparto-state-card">
<div class="d-flex justify-content-between align-items-center gap-2 mb-2">
<div class="fw-semibold"><?php echo htmlspecialchars((string) ($item['label'] ?? '')); ?></div>
<span class="badge rounded-pill <?php echo htmlspecialchars((string) ($item['badge_class'] ?? 'bg-light text-dark border')); ?>"><?php echo htmlspecialchars((string) ($item['state_label'] ?? '')); ?></span>
<div class="fw-semibold small"><?php echo htmlspecialchars((string) ($item['label'] ?? '')); ?></div>
<span class="badge rounded-pill <?php echo htmlspecialchars((string) ($item['badge_class'] ?? 'bg-light text-dark border')); ?> cc-reparto-chip"><?php echo htmlspecialchars((string) ($item['state_label'] ?? '')); ?></span>
</div>
<select name="assessor_state[<?php echo htmlspecialchars((string) ($item['key'] ?? '')); ?>]" class="form-select form-select-sm">
<option value="disponible"<?php echo ($item['state'] ?? 'disponible') === 'disponible' ? ' selected' : ''; ?>>Disponible</option>
@ -1582,15 +1741,15 @@ require_once 'layout_header.php';
</div>
<?php endforeach; ?>
</div>
<div class="d-flex flex-wrap gap-2 mt-3">
<button type="submit" class="btn btn-primary">Guardar configuración</button>
<div class="d-flex flex-wrap gap-2 mt-2">
<button type="submit" class="btn btn-primary btn-sm">Guardar configuración</button>
</div>
</form>
<form method="post" class="mt-3">
<form method="post" class="mt-2">
<input type="hidden" name="action" value="run_daily_reparto">
<input type="hidden" name="store" value="<?php echo htmlspecialchars($storeKey); ?>">
<input type="hidden" name="view" value="<?php echo htmlspecialchars($view); ?>">
<button type="submit" class="btn btn-outline-primary w-100">Distribuir pedidos pendientes</button>
<button type="submit" class="btn btn-outline-primary btn-sm w-100">Distribuir pedidos pendientes</button>
</form>
</div>
</div>
@ -1756,7 +1915,7 @@ require_once 'layout_header.php';
: (string) ($assessors[$assignedAssessorKey]['label'] ?? ($assignedAssessorKey ?? 'Asignado'));
?>
<form method="post" class="border rounded-3 p-1 text-start cc-callcenter-assign-form<?php echo $assignFormIsUnassigned ? ' is-unassigned' : ''; ?>" style="<?php echo htmlspecialchars($assignFormStyle); ?>">
<form method="post" class="border rounded-3 p-1 text-start cc-callcenter-assign-form<?php echo $assignFormIsUnassigned ? ' is-unassigned' : ''; ?>" style="<?php echo htmlspecialchars($assignFormStyle); ?>" autocomplete="off">
<input type="hidden" name="action" value="assign_assessor">
<input type="hidden" name="source_key" value="<?php echo htmlspecialchars($order['source_key']); ?>">
@ -1769,7 +1928,8 @@ require_once 'layout_header.php';
</div>
<div class="cc-callcenter-assign-row">
<select name="target_assessor" class="form-select form-select-sm js-assessor-select">
<input type="hidden" name="target_assessor" class="js-assessor-hidden" value="<?php echo htmlspecialchars((string) $selectedAssessorKeyForSelect); ?>">
<select class="form-select form-select-sm js-assessor-select" data-server-assessor="<?php echo htmlspecialchars((string) $selectedAssessorKeyForSelect); ?>" autocomplete="off" data-lpignore="true" data-1p-ignore="true" data-bwignore="true">
<option value=""<?php echo ($selectedAssessorKeyForSelect === '') ? ' selected' : ''; ?>>Sin asignar</option>
<?php foreach ($assessors as $assessorKey => $assessor): ?>
@ -3156,9 +3316,52 @@ function syncAssessorFormAccent(form) {
}
}
function restoreServerAssessorSelection(select) {
if (!select) {
return;
}
const form = select.closest('form');
const hiddenInput = form ? form.querySelector('.js-assessor-hidden') : null;
const serverAssessor = typeof select.dataset.serverAssessor === 'string'
? (select.dataset.serverAssessor || '')
: (hiddenInput ? (hiddenInput.value || '') : '');
if ((select.value || '') !== serverAssessor) {
select.value = serverAssessor;
}
if (hiddenInput && (hiddenInput.value || '') !== (select.value || '')) {
hiddenInput.value = select.value || '';
}
}
function syncAssessorHiddenInput(form) {
if (!form) {
return;
}
const select = form.querySelector('.js-assessor-select');
const hiddenInput = form.querySelector('.js-assessor-hidden');
if (!select || !hiddenInput) {
return;
}
hiddenInput.value = select.value || '';
}
function syncAllAssessorFormsFromServer() {
document.querySelectorAll('.cc-callcenter-assign-form').forEach(function (form) {
const select = form.querySelector('.js-assessor-select');
restoreServerAssessorSelection(select);
syncAssessorFormAccent(form);
});
}
function openAssessorColorModal(trigger) {
const form = trigger ? trigger.closest('form') : null;
const select = form ? form.querySelector('.js-assessor-select') : null;
restoreServerAssessorSelection(select);
const assessorKey = select ? (select.value || '') : '';
const config = assessorKey && ccAssessorCatalog[assessorKey] ? ccAssessorCatalog[assessorKey] : null;
@ -3188,15 +3391,21 @@ function openAssessorColorModal(trigger) {
document.querySelectorAll('.cc-callcenter-assign-form').forEach(function (form) {
const select = form.querySelector('.js-assessor-select');
restoreServerAssessorSelection(select);
syncAssessorFormAccent(form);
const select = form.querySelector('.js-assessor-select');
if (select) {
select.addEventListener('change', function () {
syncAssessorHiddenInput(form);
syncAssessorFormAccent(form);
});
}
form.addEventListener('submit', function () {
syncAssessorHiddenInput(form);
});
const colorButton = form.querySelector('.js-assessor-color-trigger');
if (colorButton) {
colorButton.addEventListener('click', function () {
@ -3205,6 +3414,17 @@ document.querySelectorAll('.cc-callcenter-assign-form').forEach(function (form)
}
});
window.addEventListener('pageshow', function () {
syncAllAssessorFormsFromServer();
});
window.addEventListener('load', function () {
syncAllAssessorFormsFromServer();
});
window.requestAnimationFrame(syncAllAssessorFormsFromServer);
window.setTimeout(syncAllAssessorFormsFromServer, 0);
window.setTimeout(syncAllAssessorFormsFromServer, 120);
window.setTimeout(syncAllAssessorFormsFromServer, 600);
if (ccAssessorColorInput) {
ccAssessorColorInput.addEventListener('input', function () {
const label = ccAssessorColorAssessorLabel ? (ccAssessorColorAssessorLabel.value || 'Asesora') : 'Asesora';

View File

@ -68,11 +68,19 @@ function cc_test_hex_to_rgb_triplet(string $hex): ?array
function cc_test_assessor_default_color_hex(string $assessorKey): string
{
return match (cc_test_normalize_user_key($assessorKey)) {
$normalizedKey = cc_test_normalize_user_key($assessorKey);
if ($normalizedKey === '') {
return '#6C757D';
}
$palette = ['#0D6EFD', '#198754', '#FD7E14', '#DC3545', '#20C997', '#FFC107', '#D63384'];
$paletteIndex = abs((int) crc32($normalizedKey)) % count($palette);
return match ($normalizedKey) {
'KARINA' => '#0D6EFD',
'ESTEFANYA' => '#198754',
'CARMEN' => '#FF00FF',
default => '#6C757D',
default => $palette[$paletteIndex],
};
}
@ -772,8 +780,9 @@ function cc_test_apply_daily_reparto(PDO $pdo, string $storeKey, array $orders,
function cc_test_fetch_assessors(PDO $pdo, array $allowedNames = []): array
{
if (empty($allowedNames)) {
$allowedNames = cc_test_default_assessor_keys();
$allowedLookup = [];
if (!empty($allowedNames)) {
$allowedLookup = array_fill_keys(array_map('cc_test_normalize_user_key', $allowedNames), true);
}
$hasColorColumn = cc_test_column_exists($pdo, 'users', 'color_hex');
@ -781,7 +790,6 @@ function cc_test_fetch_assessors(PDO $pdo, array $allowedNames = []): array
$stmt = $pdo->prepare("SELECT id, username, nombre_asesor{$selectColorColumn} FROM users WHERE role = 'Asesor'");
$stmt->execute();
$allowedLookup = array_fill_keys(array_map('strtoupper', $allowedNames), true);
$byKey = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$key = trim(mb_strtoupper((string) ($row['nombre_asesor'] ?: $row['username'] ?: '')));
@ -893,6 +901,10 @@ function cc_test_is_allowed_module_user(?string $role = null, ?string $username
return true;
}
if ($role === 'Asesor') {
return true;
}
$allowedLookup = array_fill_keys(cc_test_allowed_module_user_keys(), true);
foreach ([$username, $nombreAsesor] as $candidate) {
$key = cc_test_normalize_user_key($candidate);
@ -1329,12 +1341,7 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
$trendLabels[] = $day->format('d/m');
}
$orderedAssessorKeys = array_values(array_filter(cc_test_allowed_module_user_keys(), static function (string $key) use ($assessors): bool {
return isset($assessors[$key]);
}));
if (empty($orderedAssessorKeys)) {
$orderedAssessorKeys = array_keys($assessors);
}
$orderedAssessorKeys = cc_test_ordered_assessor_keys($assessors);
$bucketStats = [];
foreach ($orderedAssessorKeys as $assessorKey) {

View File

@ -14,6 +14,12 @@ if (isset($_SESSION['message'])) {
unset($_SESSION['message']);
}
$defaultRole = trim((string) ($_GET['role'] ?? ''));
$allowedDefaultRoles = ['Administrador', 'Asesor', 'Control Logistico', 'Soporte Logistico', 'Verificador de Pagos', 'Logistica'];
if ($defaultRole !== '' && !in_array($defaultRole, $allowedDefaultRoles, true)) {
$defaultRole = '';
}
// Handle form submission for creating a new user
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_user'])) {
$username = trim($_POST['username']);
@ -124,7 +130,7 @@ include 'layout_header.php';
<div class="row">
<div class="col-md-5">
<div class="card">
<div class="card" id="crear-asesora">
<div class="card-header">
<h3 class="card-title">Crear Nuevo Usuario</h3>
</div>
@ -150,12 +156,12 @@ include 'layout_header.php';
<label for="role">Rol</label>
<select class="form-control" id="role" name="role" required>
<option value="">Seleccione un rol</option>
<option value="Administrador">Administrador</option>
<option value="Asesor">Asesor</option>
<option value="Control Logistico">Control Logístico</option>
<option value="Soporte Logistico">Soporte Logístico</option>
<option value="Verificador de Pagos">Verificador de Pagos</option>
<option value="Logistica">Logistica</option>
<option value="Administrador"<?php echo $defaultRole === 'Administrador' ? ' selected' : ''; ?>>Administrador</option>
<option value="Asesor"<?php echo $defaultRole === 'Asesor' ? ' selected' : ''; ?>>Asesor</option>
<option value="Control Logistico"<?php echo $defaultRole === 'Control Logistico' ? ' selected' : ''; ?>>Control Logístico</option>
<option value="Soporte Logistico"<?php echo $defaultRole === 'Soporte Logistico' ? ' selected' : ''; ?>>Soporte Logístico</option>
<option value="Verificador de Pagos"<?php echo $defaultRole === 'Verificador de Pagos' ? ' selected' : ''; ?>>Verificador de Pagos</option>
<option value="Logistica"<?php echo $defaultRole === 'Logistica' ? ' selected' : ''; ?>>Logistica</option>
</select>
</div>
<button type="submit" name="create_user" class="btn btn-primary w-100">Crear Usuario</button>