Autosave: 20260812-001715

This commit is contained in:
Flatlogic Bot 2026-08-12 00:15:21 +00:00
parent 13e46e1545
commit 65e286876e
3 changed files with 336 additions and 24 deletions

View File

@ -397,6 +397,12 @@ $viewCards = $isTuaniRecuperablesStore ? [
'stat_key' => 'nuevos_hoy',
'active_class' => 'bg-primary-subtle border border-primary',
],
'por_llamar' => [
'label' => 'Por llamar',
'heading' => 'Pedidos por llamar',
'stat_key' => 'por_llamar',
'active_class' => 'bg-info-subtle border border-info',
],
'ultimos_3_dias' => [
'label' => 'Últimos 4 días hábiles',
'heading' => 'Pedidos asignados de los últimos 4 días hábiles',
@ -494,6 +500,7 @@ $stats = [
'total' => 0,
'pendientes_hoy' => 0,
'nuevos_hoy' => 0,
'por_llamar' => 0,
'ultimos_3_dias' => 0,
'confirmados' => 0,
'recuperados' => 0,
@ -502,6 +509,13 @@ $stats = [
'observados' => 0,
'cerrados' => 0,
];
$assessorFloatingAlertEnabled = false;
$assessorFloatingPorLlamarCount = 0;
$assessorFloatingNewAssignedCount = 0;
$assessorFloatingOpenCount = 0;
$assessorFloatingAlertSignature = '';
$assessorFloatingAlertStorageKey = '';
$assessorFloatingAlertTargetView = 'pendientes_hoy';
$repartoSettings = [
'store_key' => $storeKey,
@ -829,6 +843,9 @@ try {
if ($order['es_pendiente_hoy']) {
$stats['pendientes_hoy']++;
}
if ($order['estado'] === 'POR LLAMAR' && !$order['es_promo_final']) {
$stats['por_llamar']++;
}
if (in_array($order['estado'], cc_test_confirmed_states(), true)) {
$stats['confirmados']++;
}
@ -930,6 +947,7 @@ try {
return match ($view) {
'pendientes_hoy' => (bool) ($order['es_pendiente_hoy'] ?? false),
'nuevos_hoy' => (bool) ($order['es_nuevo_hoy'] ?? false),
'por_llamar' => ($order['estado'] ?? '') === 'POR LLAMAR' && !($order['es_promo_final'] ?? false),
'ultimos_3_dias' => (bool) ($order['es_ultimos_3_dias'] ?? false),
'confirmados' => in_array(($order['estado'] ?? ''), cc_test_confirmed_states(), true),
'recuperados' => cc_test_is_recovered_today($order, $todayStart),
@ -1018,7 +1036,7 @@ try {
}
}
if ($view === "pendientes_hoy") {
if ($view === "pendientes_hoy" || $view === 'por_llamar') {
$aPendientePromoFinal = !empty($a['promo_final_pendiente']);
$bPendientePromoFinal = !empty($b['promo_final_pendiente']);
if ($aPendientePromoFinal !== $bPendientePromoFinal) {
@ -1112,6 +1130,44 @@ try {
? 'Tienes ' . $assessorInboxLockedCount . ' pedido' . ($assessorInboxLockedCount === 1 ? '' : 's') . ' pendiente' . ($assessorInboxLockedCount === 1 ? '' : 's') . ' de Promo Final. Mientras existan pendientes de sustento, los pedidos de Día 1 mantienen el número oculto; cuando ya no queden pendientes, el celular volverá a mostrarse.'
: '';
if (!$isAdmin && !$isTuaniRecuperablesStore) {
$assessorFloatingAlertSourceTokens = [];
foreach ($orders as $floatingOrder) {
$floatingState = cc_test_normalize_state((string) ($floatingOrder['estado'] ?? ''));
$floatingIsPromoFinal = !empty($floatingOrder['es_promo_final']);
$floatingIsOpen = !$floatingIsPromoFinal && in_array($floatingState, $openStates, true);
if (!$floatingIsOpen) {
continue;
}
$assessorFloatingOpenCount++;
if ($floatingState === 'POR LLAMAR') {
$assessorFloatingPorLlamarCount++;
}
if (!empty($floatingOrder['es_nuevo_hoy'])) {
$assessorFloatingNewAssignedCount++;
}
$assessorFloatingAlertSourceTokens[] = implode('|', [
(string) ($floatingOrder['source_key'] ?? ''),
$floatingState,
trim((string) ($floatingOrder['assigned_at'] ?? '')),
]);
}
sort($assessorFloatingAlertSourceTokens);
$assessorFloatingAlertEnabled = $assessorFloatingOpenCount > 0;
$assessorFloatingAlertSignature = $assessorFloatingAlertEnabled
? sha1(json_encode($assessorFloatingAlertSourceTokens, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))
: '';
$assessorFloatingAlertStorageKey = 'cc-assessor-floating-alert:v1:' . $storeKey . ':' . $currentUserId;
$assessorFloatingAlertTargetView = $assessorFloatingPorLlamarCount > 0
? 'por_llamar'
: ($assessorFloatingNewAssignedCount > 0 ? 'nuevos_hoy' : 'pendientes_hoy');
}
$visibleOrdersTotalCount = count($visibleOrders);
$todosPageSize = 200;
$todosPage = 1;
@ -1182,6 +1238,7 @@ if ($selectedAssessorFilter !== '') {
}
$panelOrdersSignature = isset($visibleOrders) && is_array($visibleOrders) ? cc_test_panel_orders_signature($visibleOrders) : '';
$panelInboxSignature = (!$isAdmin && !$isTuaniRecuperablesStore) ? $assessorFloatingAlertSignature : '';
if (!empty($_GET['ajax_refresh'])) {
header('Content-Type: application/json; charset=utf-8');
if ($errorMessage !== null) {
@ -1193,6 +1250,11 @@ if (!empty($_GET['ajax_refresh'])) {
echo json_encode([
'success' => true,
'signature' => $panelOrdersSignature,
'inbox_signature' => $panelInboxSignature,
'alert_signature' => $assessorFloatingAlertSignature,
'por_llamar_count' => $assessorFloatingPorLlamarCount,
'new_assigned_count' => $assessorFloatingNewAssignedCount,
'open_count' => $assessorFloatingOpenCount,
'count' => $visibleOrdersTotalCount,
'view' => $view,
'store' => $storeKey,
@ -1410,9 +1472,171 @@ require_once 'layout_header.php';
color: var(--bs-primary-text-emphasis) !important;
}
.cc-assessor-floating-alert {
position: fixed;
right: 0;
top: 42%;
z-index: 1055;
width: min(18rem, calc(100vw - 1rem));
color: #fff;
background: linear-gradient(135deg, #0f766e 0%, #0ea5e9 100%);
border-radius: 1.35rem 0 0 1.35rem;
box-shadow: 0 1.15rem 2.5rem rgba(14, 116, 144, .28);
overflow: hidden;
isolation: isolate;
transition: transform .2s ease, box-shadow .2s ease;
}
.cc-assessor-floating-alert::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at 15% 0%, rgba(255, 255, 255, .28), transparent 38%);
pointer-events: none;
z-index: -1;
}
.cc-assessor-floating-alert:hover {
transform: translateX(-.28rem);
box-shadow: 0 1.35rem 2.8rem rgba(14, 116, 144, .34);
}
.cc-assessor-floating-alert.is-dismissed {
display: none;
}
.cc-assessor-floating-alert__link {
display: block;
padding: 1rem 1rem 1rem 1.15rem;
color: inherit;
text-decoration: none;
}
.cc-assessor-floating-alert__kicker {
display: inline-flex;
align-items: center;
gap: .35rem;
font-size: .72rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: .06em;
opacity: .88;
}
.cc-assessor-floating-alert__number {
font-size: 2.25rem;
line-height: 1;
font-weight: 900;
letter-spacing: -.04em;
}
.cc-assessor-floating-alert__title {
font-weight: 800;
line-height: 1.15;
}
.cc-assessor-floating-alert__text {
font-size: .82rem;
opacity: .9;
}
.cc-assessor-floating-alert__chips {
display: flex;
flex-wrap: wrap;
gap: .35rem;
margin-top: .75rem;
}
.cc-assessor-floating-alert__chip {
display: inline-flex;
align-items: center;
gap: .25rem;
border: 1px solid rgba(255, 255, 255, .34);
background: rgba(255, 255, 255, .16);
border-radius: 999px;
padding: .28rem .55rem;
font-size: .74rem;
font-weight: 700;
backdrop-filter: blur(8px);
}
.cc-assessor-floating-alert__close {
position: absolute;
top: .42rem;
right: .42rem;
z-index: 2;
width: 1.6rem;
height: 1.6rem;
border: 1px solid rgba(255, 255, 255, .28);
border-radius: 999px;
background: rgba(15, 23, 42, .18);
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
}
.cc-assessor-floating-alert__close:hover {
background: rgba(15, 23, 42, .28);
}
@media (max-width: 767.98px) {
.cc-assessor-floating-alert {
top: auto;
right: .75rem;
bottom: .85rem;
left: .75rem;
width: auto;
border-radius: 1.25rem;
}
.cc-assessor-floating-alert:hover {
transform: translateY(-.15rem);
}
}
</style>
<main class="container-fluid py-4" data-cc-panel-root="1">
<?php if ($assessorFloatingAlertEnabled): ?>
<?php
if ($assessorFloatingPorLlamarCount > 0) {
$assessorFloatingAlertMainCount = $assessorFloatingPorLlamarCount;
$assessorFloatingAlertTitle = 'Por llamar';
$assessorFloatingAlertText = 'Prioriza estos pedidos pendientes.';
} else {
$assessorFloatingAlertMainCount = $assessorFloatingOpenCount;
$assessorFloatingAlertTitle = 'Pendientes';
$assessorFloatingAlertText = 'Revisa tu bandeja principal.';
}
$assessorFloatingAlertUrl = cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => $assessorFloatingAlertTargetView]));
?>
<aside id="assessorFloatingAlert"
class="cc-assessor-floating-alert"
role="status"
aria-live="polite"
data-alert-signature="<?php echo htmlspecialchars($assessorFloatingAlertSignature); ?>"
data-alert-storage-key="<?php echo htmlspecialchars($assessorFloatingAlertStorageKey); ?>">
<button type="button" class="cc-assessor-floating-alert__close" id="assessorFloatingAlertClose" aria-label="Ocultar aviso de pedidos asignados">
<i class="bi bi-x-lg" aria-hidden="true"></i>
</button>
<a href="<?php echo htmlspecialchars($assessorFloatingAlertUrl); ?>" class="cc-assessor-floating-alert__link">
<span class="cc-assessor-floating-alert__kicker"><i class="bi bi-bell-fill" aria-hidden="true"></i> Aviso de bandeja</span>
<div class="d-flex align-items-end gap-2 mt-2 pe-4">
<span class="cc-assessor-floating-alert__number"><?php echo (int) $assessorFloatingAlertMainCount; ?></span>
<div class="pb-1">
<div class="cc-assessor-floating-alert__title"><?php echo htmlspecialchars($assessorFloatingAlertTitle); ?></div>
<div class="cc-assessor-floating-alert__text"><?php echo htmlspecialchars($assessorFloatingAlertText); ?></div>
</div>
</div>
<div class="cc-assessor-floating-alert__chips" aria-label="Resumen de pedidos pendientes">
<span class="cc-assessor-floating-alert__chip"><strong><?php echo (int) $assessorFloatingOpenCount; ?></strong> pendientes</span>
<span class="cc-assessor-floating-alert__chip"><strong><?php echo (int) $assessorFloatingPorLlamarCount; ?></strong> por llamar</span>
</div>
</a>
</aside>
<?php endif; ?>
<section class="mb-4">
<div class="d-flex flex-column flex-xl-row justify-content-between align-items-xl-center gap-3">
<div>
@ -2474,9 +2698,11 @@ ccCallCenterPanelBaseUrl.hash = '';
const ccCallCenterPanelRefreshUrl = new URL(ccCallCenterPanelBaseUrl.toString());
ccCallCenterPanelRefreshUrl.searchParams.set('ajax_refresh', '1');
const ccCallCenterPanelInitialSignature = <?php echo json_encode((string) ($panelOrdersSignature ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
const ccCallCenterPanelInitialInboxSignature = <?php echo json_encode((string) ($panelInboxSignature ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
const ccCallCenterPanelRefreshMs = 300000;
let ccCallCenterPanelRefreshInFlight = false;
let ccCallCenterPanelLastSignature = ccCallCenterPanelInitialSignature;
let ccCallCenterPanelLastInboxSignature = ccCallCenterPanelInitialInboxSignature;
const ccCallCenterTimerStorageKey = 'cc-callcenter-modal-timers:v1';
@ -3569,6 +3795,41 @@ function ccCallCenterPanelRefreshNow(event) {
return false;
}
function ccAssessorFloatingAlertInit() {
const alert = document.getElementById('assessorFloatingAlert');
if (!alert) {
return;
}
const signature = String(alert.dataset.alertSignature || '');
const storageKey = String(alert.dataset.alertStorageKey || '');
try {
if (signature && storageKey && window.localStorage.getItem(storageKey) === signature) {
alert.classList.add('is-dismissed');
}
} catch (error) {
// Si localStorage no está disponible, la alerta solo queda visible en esta sesión.
}
const closeButton = document.getElementById('assessorFloatingAlertClose');
if (!closeButton) {
return;
}
closeButton.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
try {
if (signature && storageKey) {
window.localStorage.setItem(storageKey, signature);
}
} catch (error) {
// No bloqueamos la pantalla si el navegador impide guardar preferencias.
}
alert.classList.add('is-dismissed');
});
}
function ccCallCenterPanelMarkDirty(event) {
const target = event.target;
if (!target || !target.form) {
@ -3604,7 +3865,7 @@ function ccCallCenterPanelBindDirtyTracking() {
}
async function ccCallCenterPanelCheckForUpdates() {
if (ccCallCenterPanelRefreshInFlight || document.visibilityState !== 'visible' || ccCallCenterPanelShouldPauseRefresh() || !ccCallCenterPanelInitialSignature) {
if (ccCallCenterPanelRefreshInFlight || document.visibilityState !== 'visible' || ccCallCenterPanelShouldPauseRefresh()) {
return;
}
@ -3628,12 +3889,21 @@ async function ccCallCenterPanelCheckForUpdates() {
return;
}
const inboxSignature = String(data.inbox_signature || '');
if (inboxSignature && inboxSignature !== ccCallCenterPanelLastInboxSignature) {
ccCallCenterPanelNavigate();
return;
}
const signature = String(data.signature || '');
if (signature && signature !== ccCallCenterPanelLastSignature) {
ccCallCenterPanelNavigate();
return;
}
if (inboxSignature) {
ccCallCenterPanelLastInboxSignature = inboxSignature;
}
if (signature) {
ccCallCenterPanelLastSignature = signature;
}
@ -3645,6 +3915,7 @@ async function ccCallCenterPanelCheckForUpdates() {
}
syncServerAssessorSelects();
ccAssessorFloatingAlertInit();
ccCallCenterPanelBindDirtyTracking();
ccCallCenterPanelRestoreScroll();
window.addEventListener('beforeunload', ccCallCenterPanelSaveScroll);

View File

@ -703,7 +703,7 @@ try {
}
}
// Contamos cuántos pedidos siguen activos (POR LLAMAR / DEVOLVER LLAMADA / OBSERVADO).
// Contamos cuántos pedidos siguen activos (POR LLAMAR / DEVOLVER LLAMADA / OBSERVADO / SE ENVIO NUMERO DE CUENTA).
foreach ($orders as $o) {
if ($o['user_id'] === null) {
continue;
@ -1377,6 +1377,47 @@ require_once 'layout_header.php';
border-left: .45rem solid var(--cc-assessor-accent, #adb5bd) !important;
background-color: rgba(var(--cc-assessor-accent-rgb, 173, 181, 189), .16);
box-shadow: inset 0 0 0 1px rgba(var(--cc-assessor-accent-rgb, 173, 181, 189), .22);
position: relative;
}
.cc-callcenter-assessor-summary-card .card-body {
position: relative;
}
.cc-callcenter-assessor-summary-card.has-pending .card-body {
padding-bottom: 3.45rem !important;
}
.cc-callcenter-pending-tab {
position: absolute;
right: .9rem;
bottom: .9rem;
z-index: 2;
display: inline-flex;
align-items: center;
gap: .35rem;
padding: .42rem .82rem;
border: 1px solid rgba(255, 193, 7, .28) !important;
background: linear-gradient(135deg, rgba(255, 193, 7, .22), rgba(255, 255, 255, .92));
color: #8a5a00;
font-weight: 700;
line-height: 1;
white-space: nowrap;
letter-spacing: .01em;
box-shadow: 0 .85rem 1.65rem rgba(15, 23, 42, .16), inset 0 1px 0 rgba(255, 255, 255, .55);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
transform: translateY(24%);
}
.cc-callcenter-pending-tab::before {
content: "";
width: .5rem;
height: .5rem;
border-radius: 999px;
background: #f0ad4e;
box-shadow: 0 0 0 .18rem rgba(255, 193, 7, .16);
flex: 0 0 auto;
}
.cc-callcenter-assign-form {
@ -1852,21 +1893,21 @@ require_once 'layout_header.php';
<?php foreach ($activeAssessorSummaryCards as $assessorCard): ?>
<?php $pendingCount = (int) ($assessorCard['pending'] ?? 0); ?>
<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="card border-0 shadow-sm h-100 cc-callcenter-assessor-summary-card<?php echo $pendingCount > 0 ? " has-pending" : ""; ?>" style="<?php echo htmlspecialchars((string) $assessorCard['accent_style']); ?>">
<div class="card-body d-flex flex-column h-100">
<div>
<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>
<?php if ($pendingCount > 0): ?>
<span class="badge rounded-pill border cc-callcenter-assessor-badge text-nowrap">Pendientes: <?php echo $pendingCount; ?></span>
<?php endif; ?>
</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 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 if ($pendingCount > 0): ?>
<span class="badge rounded-pill text-nowrap cc-callcenter-pending-tab" aria-label="Pendientes: <?php echo $pendingCount; ?>" title="Pendientes: <?php echo $pendingCount; ?>">Pendientes: <?php echo $pendingCount; ?></span>
<?php endif; ?>
</div>
</div>
</div>
@ -1909,21 +1950,21 @@ require_once 'layout_header.php';
<?php foreach ($inactiveAssessorSummaryCards as $assessorCard): ?>
<?php $pendingCount = (int) ($assessorCard['pending'] ?? 0); ?>
<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="card border-0 shadow-sm h-100 cc-callcenter-assessor-summary-card is-inactive<?php echo $pendingCount > 0 ? " has-pending" : ""; ?>" style="<?php echo htmlspecialchars((string) $assessorCard['accent_style']); ?>">
<div class="card-body d-flex flex-column h-100">
<div>
<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>
<?php if ($pendingCount > 0): ?>
<span class="badge rounded-pill border cc-callcenter-assessor-badge text-nowrap">Pendientes: <?php echo $pendingCount; ?></span>
<?php endif; ?>
</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 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 if ($pendingCount > 0): ?>
<span class="badge rounded-pill text-nowrap cc-callcenter-pending-tab" aria-label="Pendientes: <?php echo $pendingCount; ?>" title="Pendientes: <?php echo $pendingCount; ?>">Pendientes: <?php echo $pendingCount; ?></span>
<?php endif; ?>
</div>
</div>
</div>

View File

@ -1420,7 +1420,7 @@ function cc_test_valid_states(): array
if (!function_exists('cc_test_open_states')) {
function cc_test_open_states(): array
{
return ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'];
return cc_test_followup_tracking_states();
}
}