Autosave: 20260809-182140

This commit is contained in:
Flatlogic Bot 2026-08-09 18:19:50 +00:00
parent dd197310a1
commit a225a6809d
4 changed files with 190 additions and 25 deletions

View File

@ -332,6 +332,7 @@ $recoverablesMainStoreKey = trim((string) ($storeConfig['main_store_key'] ?? '')
$recoverablesMainStoreLabel = $recoverablesMainStoreKey !== '' && isset($availableStores[$recoverablesMainStoreKey]) ? (string) ($availableStores[$recoverablesMainStoreKey]['label'] ?? strtoupper($recoverablesMainStoreKey)) : $storeLabel;
$recoverablesStoreLabelHtml = htmlspecialchars($storeLabel);
$recoverablesMainStoreLabelHtml = htmlspecialchars($recoverablesMainStoreLabel);
$canOpenRecoverablesStore = $recoverablesStoreKey !== '' && isset($availableStores[$recoverablesStoreKey]);
$usesIncrementalSync = !empty($storeConfig['incremental_sync']);
$storeNav = drive_test_store_navigation_state($availableStores, $storeKey);
$storeNavGroups = $storeNav['groups'];
@ -347,7 +348,7 @@ if ($isTuaniRecuperablesStore && trim((string) ($_GET['view'] ?? '')) === '') {
}
$canShowTuaniLogisticaButton = $isMainTuaniStore || $isTuaniRecuperablesStore;
$postedAction = trim((string) ($_POST['action'] ?? ''));
$recoverablesResyncRequested = $isTuaniRecuperablesStore && $postedAction === 'reimport_recoverables_queue';
$recoverablesResyncRequested = $isTuaniRecuperablesStore && $isAdmin && $postedAction === 'reimport_recoverables_queue';
$recoverablesResyncRow = $isTuaniRecuperablesStore ? (int) ($storeConfig['startRow'] ?? 2000) : null;
$viewCardColumnClass = $isTuaniRecuperablesStore ? 'col-md-6 col-xl-2' : 'col-md-6 col-xl-2';
$defaultView = cc_test_callcenter_pro_default_view_key($storeKey);
@ -587,6 +588,7 @@ try {
$recoverablesFirstSourceRow = $recoverablesSummary['first_source_row'] !== null ? (int) $recoverablesSummary['first_source_row'] : null;
$recoverablesLastSourceRow = $recoverablesSummary['last_source_row'] !== null ? (int) $recoverablesSummary['last_source_row'] : null;
$recoverablesNotice = $recoverablesSummary['notice'] ?? null;
$recoverablesNotice = null;
} else {
$recoverablesImportCount = count($orders);
$recoverablesFirstSourceRow = null;
@ -854,9 +856,23 @@ try {
}));
}
$performanceValueOverride = '';
$performanceMetaTitleOverride = '';
$performanceDashboard = cc_test_build_performance_dashboard_period($performanceOrders, $assessors, $kpiPeriodStart, $kpiPeriodEnd);
if ($isTuaniRecuperablesStore) {
$performanceDashboard = cc_test_filter_dashboard_status_chart($performanceDashboard, ['Confirmados nuevos', 'Recuperados']);
$recoverablesStatusDataset = (array) (($performanceDashboard['status_chart']['datasets'][0] ?? []) ?: []);
$recoverablesStatusValues = array_map(static function ($value) {
return (int) $value;
}, array_values((array) ($recoverablesStatusDataset['data'] ?? [])));
if (array_sum($recoverablesStatusValues) > 0) {
$performanceValueOverride = '';
$performanceMetaTitleOverride = 'Confirmados nuevos + recuperados cubren el periodo mostrado.';
}
}
$performanceStatusHeading = $kpiPeriodKey === 'today' ? 'Detalle del KPI de hoy (%)' : 'Detalle del KPI del periodo (%)';
$performanceSubtitle = $isTuaniRecuperablesStore
? 'Los indicadores de esta bandeja se calculan sobre todos los pedidos visibles para cualquier asesora en el periodo seleccionado; los repetidos solo suman cantidad y no bajan el porcentaje.'
? 'En carritos recuperables solo se muestran confirmados nuevos y recuperados; cuando hay datos, esas dos categorías visibles cubren el periodo mostrado y los repetidos solo suman cantidad.'
: ($isAdmin
? 'Confirmación = pedidos asignados en el periodo confirmados en el mismo periodo, sin contar repetidos. Recuperación = pedidos asignados antes del periodo confirmados dentro del periodo. Rendimiento = confirmados totales (nuevos + recuperados) sobre asignados efectivos sin repetidos.'
: 'Tus KPIs del periodo seleccionado se calculan sobre tus pedidos asignados en ese rango. Confirmación = asignados del periodo confirmados en el mismo periodo, sin contar repetidos. Recuperación = asignados antes del periodo confirmados dentro del periodo. Rendimiento = confirmados totales (nuevos + recuperados) sobre asignados efectivos sin repetidos.');
@ -865,11 +881,15 @@ try {
$performanceTitle = $kpiPeriodKey === 'today'
? 'KPI compartido de recuperación'
: 'KPI compartido de recuperación · ' . $kpiPeriodLabel;
$performanceChart1Title = 'Distribución de estados de la bandeja';
$performanceChart1Note = 'La dona separa los pedidos del periodo seleccionado por estado: confirmados nuevos, recuperados, POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO, SE ENVIO NUMERO DE CUENTA, repetidos (solo cantidad) y cancelados.';
$performanceChart1Title = 'Confirmados nuevos vs recuperados';
$performanceChart1Note = 'La dona muestra solo confirmados nuevos y recuperados en esta bandeja.';
$performanceChart2Title = $kpiPeriodKey === 'today' ? 'Evolución de hoy' : 'Evolución de ' . $kpiPeriodTitleLabel;
$performanceChart2Note = 'La línea muestra cómo se mueve la bandeja compartida en el periodo seleccionado.';
$performanceFootnote = 'Los indicadores se calculan sobre todos los pedidos visibles de esta bandeja compartida; los repetidos solo suman cantidad y no bajan el porcentaje.';
$performanceLabel = 'Rendimiento del periodo';
$performanceValueOverride = '';
$performanceMetaTitleOverride = 'Confirmados nuevos + recuperados cubren el periodo mostrado.';
$performanceStatusHeading = 'Detalle del KPI del periodo (%)';
} else {
if ($isAdmin) {
$performanceTitle = $selectedAssessorFilterLabel !== ''
@ -895,8 +915,11 @@ try {
'chart2_title' => $performanceChart2Title,
'chart2_note' => $performanceChart2Note,
'footnote' => $performanceFootnote,
'performance_label' => $kpiPeriodKey === 'today' ? 'Rendimiento del día' : 'Rendimiento del periodo',
'status_detail_heading' => $kpiPeriodKey === 'today' ? 'Detalle del KPI de hoy (%)' : 'Detalle del KPI del periodo (%)',
'performance_label' => $performanceLabel,
'performance_value_hidden' => $isTuaniRecuperablesStore,
'status_detail_heading' => $performanceStatusHeading,
'performance_value' => $performanceValueOverride,
'performance_meta_title' => $performanceMetaTitleOverride,
]);
$visibleOrders = array_values(array_filter($orders, static function (array $order) use ($view, $selectedAssessorFilter, $todayStart): bool {
@ -1398,7 +1421,7 @@ require_once 'layout_header.php';
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis border px-3 py-2">Tienda activa: <?php echo htmlspecialchars($storeLabel); ?></span>
<?php if ($isTuaniRecuperablesStore): ?>
<span class="badge rounded-pill bg-success-subtle text-success-emphasis border px-3 py-2">Solo pedidos válidos: IDs #D...</span>
<?php else: ?>
<?php elseif ($canOpenRecoverablesStore): ?>
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', ['store' => $recoverablesStoreKey, 'view' => 'todos'])); ?>" class="btn btn-sm btn-outline-success">Abrir <?php echo htmlspecialchars($recoverablesStoreLabel !== '' ? $recoverablesStoreLabel : 'Carritos recuperables'); ?> (#D...)</a>
<?php endif; ?>
</div>
@ -1439,7 +1462,7 @@ require_once 'layout_header.php';
<span class="badge rounded-pill text-bg-light border px-3 py-2">Actualización en vivo cada 5 min</span>
<a href="test_importar_drive.php?store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
<button type="button" class="btn btn-primary btn-sm" onclick="return ccCallCenterPanelRefreshNow(event);"><i class="bi bi-arrow-clockwise me-1"></i>Actualizar pedidos</button>
<?php if ($isTuaniRecuperablesStore): ?>
<?php if ($isTuaniRecuperablesStore && $isAdmin): ?>
<form method="post" class="d-inline" onsubmit="return confirm('Esto volverá a leer la cola desde la fila <?php echo (int) $recoverablesResyncRow; ?>. ¿Continuar?');">
<input type="hidden" name="action" value="reimport_recoverables_queue">
<input type="hidden" name="from_row" value="<?php echo (int) $recoverablesResyncRow; ?>">

View File

@ -229,6 +229,7 @@ $recoverablesMainStoreKey = trim((string) ($storeConfig['main_store_key'] ?? '')
$recoverablesMainStoreLabel = $recoverablesMainStoreKey !== '' && isset($availableStores[$recoverablesMainStoreKey]) ? (string) ($availableStores[$recoverablesMainStoreKey]['label'] ?? strtoupper($recoverablesMainStoreKey)) : $storeLabel;
$recoverablesStoreLabelHtml = htmlspecialchars($storeLabel);
$recoverablesMainStoreLabelHtml = htmlspecialchars($recoverablesMainStoreLabel);
$canOpenRecoverablesStore = $recoverablesStoreKey !== '' && isset($availableStores[$recoverablesStoreKey]);
$usesIncrementalSync = !empty($storeConfig['incremental_sync']);
$storeNav = drive_test_store_navigation_state($availableStores, $storeKey);
$storeNavGroups = $storeNav['groups'];
@ -244,7 +245,7 @@ if ($isTuaniRecuperablesStore && trim((string) ($_GET['view'] ?? '')) === '') {
}
$canShowTuaniLogisticaButton = $isMainTuaniStore || $isTuaniRecuperablesStore;
$postedAction = trim((string) ($_POST['action'] ?? ''));
$recoverablesResyncRequested = $isTuaniRecuperablesStore && $postedAction === 'reimport_recoverables_queue';
$recoverablesResyncRequested = $isTuaniRecuperablesStore && $isAdmin && $postedAction === 'reimport_recoverables_queue';
$recoverablesResyncRow = $isTuaniRecuperablesStore ? (int) ($storeConfig['startRow'] ?? 2000) : null;
$viewCardColumnClass = $isTuaniRecuperablesStore ? 'col-md-6 col-xl-2' : 'col-md-6 col-xl-2';
$callCenterDefaultView = ($isMainTuaniStore || $isTuaniRecuperablesStore) ? cc_test_callcenter_pro_default_view_key($storeKey) : $view;
@ -515,6 +516,7 @@ try {
$recoverablesFirstSourceRow = $recoverablesSummary['first_source_row'] !== null ? (int) $recoverablesSummary['first_source_row'] : null;
$recoverablesLastSourceRow = $recoverablesSummary['last_source_row'] !== null ? (int) $recoverablesSummary['last_source_row'] : null;
$recoverablesNotice = $recoverablesSummary['notice'] ?? null;
$recoverablesNotice = null;
} else {
$recoverablesImportCount = count($orders);
$recoverablesFirstSourceRow = null;
@ -1037,15 +1039,35 @@ try {
}))
: $orders;
$performanceValueOverride = '';
$performanceMetaTitleOverride = '';
$performanceDashboard = cc_test_build_performance_dashboard($performanceOrders, $assessors, 7);
if ($isTuaniRecuperablesStore) {
$performanceDashboard = cc_test_filter_dashboard_status_chart($performanceDashboard, ['Confirmados nuevos', 'Recuperados']);
$recoverablesStatusDataset = (array) (($performanceDashboard['status_chart']['datasets'][0] ?? []) ?: []);
$recoverablesStatusValues = array_map(static function ($value) {
return (int) $value;
}, array_values((array) ($recoverablesStatusDataset['data'] ?? [])));
if (array_sum($recoverablesStatusValues) > 0) {
$performanceValueOverride = '';
$performanceMetaTitleOverride = 'Confirmados nuevos + recuperados cubren el periodo mostrado.';
}
}
$performanceDashboardHtml = cc_test_render_performance_dashboard($performanceDashboard, [
'title' => 'KPI diario por asesora',
'subtitle' => 'Confirmación = pedidos asignados hoy confirmados hoy, sin contar repetidos. Recuperación = pedidos asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados efectivos sin repetidos.',
'chart1_title' => 'Distribución de estados',
'chart1_note' => 'La dona separa los pedidos de hoy por estado: confirmados nuevos, recuperados, POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO, SE ENVIO NUMERO DE CUENTA, repetidos (solo cantidad) y cancelados.',
'subtitle' => $isTuaniRecuperablesStore
? 'En carritos recuperables solo se muestran confirmados nuevos y recuperados; cuando hay datos, esas dos categorías visibles cubren el periodo mostrado y los repetidos solo suman cantidad.'
: 'Confirmación = pedidos asignados hoy confirmados hoy, sin contar repetidos. Recuperación = pedidos asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados efectivos sin repetidos.',
'chart1_title' => $isTuaniRecuperablesStore ? 'Confirmados nuevos vs recuperados' : 'Distribución de estados',
'chart1_note' => $isTuaniRecuperablesStore ? 'La dona muestra solo confirmados nuevos y recuperados en esta bandeja.' : 'La dona separa los pedidos de hoy por estado: confirmados nuevos, recuperados, POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO, SE ENVIO NUMERO DE CUENTA, repetidos (solo cantidad) y cancelados.',
'chart2_title' => 'Evolución de los últimos 7 días',
'chart2_note' => 'La línea de confirmados separa pedidos asignados hoy y pedidos asignados antes de hoy.',
'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.',
'performance_label' => $isTuaniRecuperablesStore ? 'Rendimiento del periodo' : 'Rendimiento del día',
'performance_value_hidden' => $isTuaniRecuperablesStore,
'status_detail_heading' => $isTuaniRecuperablesStore ? 'Detalle del KPI del periodo (%)' : 'Detalle del KPI de hoy (%)',
'performance_value' => $isTuaniRecuperablesStore ? '' : '',
'performance_meta_title' => $isTuaniRecuperablesStore ? 'Confirmados nuevos + recuperados cubren el periodo mostrado.' : '',
]);
$orderedAssessorKeys = cc_test_ordered_assessor_keys($assessors);
@ -1654,7 +1676,7 @@ require_once 'layout_header.php';
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis border px-3 py-2">Tienda activa: <?php echo htmlspecialchars($storeLabel); ?></span>
<?php if ($isTuaniRecuperablesStore): ?>
<span class="badge rounded-pill bg-success-subtle text-success-emphasis border px-3 py-2">Solo pedidos válidos: IDs #D...</span>
<?php else: ?>
<?php elseif ($canOpenRecoverablesStore): ?>
<a href="<?php echo htmlspecialchars(cc_test_build_url('gestiones_callcenter.php', ['store' => $recoverablesStoreKey, 'view' => 'todos'])); ?>" class="btn btn-sm btn-outline-success">Abrir <?php echo htmlspecialchars($recoverablesStoreLabel !== '' ? $recoverablesStoreLabel : 'Carritos recuperables'); ?> (#D...)</a>
<?php endif; ?>
</div>
@ -1697,7 +1719,7 @@ require_once 'layout_header.php';
<?php endif; ?>
<a href="test_importar_drive.php?store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
<button type="button" class="btn btn-primary btn-sm" onclick="window.location.reload();"><i class="bi bi-arrow-clockwise me-1"></i>Actualizar pedidos</button>
<?php if ($isTuaniRecuperablesStore): ?>
<?php if ($isTuaniRecuperablesStore && $isAdmin): ?>
<form method="post" class="d-inline" onsubmit="return confirm('Esto volverá a leer la cola desde la fila <?php echo (int) $recoverablesResyncRow; ?>. ¿Continuar?');">
<input type="hidden" name="action" value="reimport_recoverables_queue">
<input type="hidden" name="from_row" value="<?php echo (int) $recoverablesResyncRow; ?>">

View File

@ -1306,6 +1306,40 @@ function cc_test_current_user_can_access_module(?PDO $pdo = null): bool
return cc_test_is_allowed_module_user($role, $username, $nombreAsesor);
}
function cc_test_current_user_identity_keys(): array
{
$keys = [];
foreach ([(string) ($_SESSION['nombre_asesor'] ?? ''), (string) ($_SESSION['username'] ?? '')] as $candidate) {
$key = cc_test_normalize_user_key($candidate);
if ($key !== '' && !in_array($key, $keys, true)) {
$keys[] = $key;
}
}
return $keys;
}
function cc_test_current_user_can_view_flower_recoverables(): bool
{
$role = trim((string) ($_SESSION['user_role'] ?? ''));
if (in_array($role, ['Administrador', 'admin'], true)) {
return true;
}
return !in_array('CARMEN', cc_test_current_user_identity_keys(), true);
}
function cc_test_filter_visible_drive_stores(array $availableStores): array
{
if (cc_test_current_user_can_view_flower_recoverables()) {
return $availableStores;
}
unset($availableStores['flower_recuperables']);
return $availableStores;
}
function cc_test_upsert_assignee(PDO $pdo, string $sourceKey, ?int $userId): void
{
cc_test_ensure_tracking_table($pdo);
@ -2599,6 +2633,74 @@ function cc_test_build_performance_dashboard_period(array $orders, array $assess
];
}
function cc_test_filter_dashboard_status_chart(array $dashboard, array $keepLabels): array
{
$keepLookup = [];
foreach ($keepLabels as $label) {
$label = trim((string) $label);
if ($label !== '') {
$keepLookup[$label] = true;
}
}
if ($keepLookup === []) {
return $dashboard;
}
$statusChart = (array) ($dashboard['status_chart'] ?? []);
$labels = array_values((array) ($statusChart['labels'] ?? []));
$datasets = array_values((array) ($statusChart['datasets'] ?? []));
if ($labels === [] || $datasets === []) {
return $dashboard;
}
$keptIndexes = [];
$filteredLabels = [];
foreach ($labels as $index => $label) {
$label = (string) $label;
if (!isset($keepLookup[$label])) {
continue;
}
$keptIndexes[] = $index;
$filteredLabels[] = $label;
}
if ($filteredLabels === []) {
return $dashboard;
}
$filteredDatasets = [];
foreach ($datasets as $dataset) {
$dataset = (array) $dataset;
$filteredDataset = $dataset;
foreach (['data', 'backgroundColor', 'borderColor'] as $field) {
if (!isset($dataset[$field]) || !is_array($dataset[$field])) {
continue;
}
$values = array_values($dataset[$field]);
$filteredValues = [];
foreach ($keptIndexes as $index) {
if (array_key_exists($index, $values)) {
$filteredValues[] = $values[$index];
}
}
$filteredDataset[$field] = $filteredValues;
}
$filteredDatasets[] = $filteredDataset;
}
$statusChart['labels'] = $filteredLabels;
$statusChart['datasets'] = $filteredDatasets;
$dashboard['status_chart'] = $statusChart;
return $dashboard;
}
function cc_test_render_performance_dashboard(array $dashboard, array $options = []): string
{
$title = trim((string) ($options['title'] ?? 'KPI diario por asesora'));
@ -2639,15 +2741,25 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
$confirmedTodayTotal = (int) ($summary['confirmed_today_total'] ?? 0);
$effectiveAssignedToday = (int) ($summary['assigned_effective_today'] ?? max(0, (int) ($summary['assigned_today'] ?? 0) - (int) ($summary['repeated_today'] ?? 0)));
$performanceTodayRate = (float) ($summary['performance_today_rate'] ?? 0);
$performanceTodayMetaTitle = sprintf(
'%d confirmados sobre %d asignados efectivos = %d nuevos + %d recuperado%s',
$confirmedTodayTotal,
$effectiveAssignedToday,
$confirmedTodayNew,
$confirmedTodayRecovered,
$confirmedTodayRecovered === 1 ? '' : 's'
);
$performanceTodayValue = number_format($performanceTodayRate, 1) . '%';
$hidePerformanceTodayValue = !empty($options['performance_value_hidden']);
$performanceTodayMetaTitle = trim((string) ($options['performance_meta_title'] ?? ''));
if ($performanceTodayMetaTitle === '') {
$performanceTodayMetaTitle = sprintf(
'%d confirmados sobre %d asignados efectivos = %d nuevos + %d recuperado%s',
$confirmedTodayTotal,
$effectiveAssignedToday,
$confirmedTodayNew,
$confirmedTodayRecovered,
$confirmedTodayRecovered === 1 ? '' : 's'
);
}
$performanceTodayValue = '';
if (!$hidePerformanceTodayValue) {
$performanceTodayValue = trim((string) ($options['performance_value'] ?? ''));
if ($performanceTodayValue === '') {
$performanceTodayValue = number_format($performanceTodayRate, 1) . '%';
}
}
$statusLegendItems = [];
if ($statusTotal > 0) {
@ -2748,7 +2860,9 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
<div class="cc-kpi-pill cc-kpi-pill-total cc-kpi-performance-box mb-0" title="<?php echo htmlspecialchars($performanceTodayMetaTitle); ?>" style="flex-direction:column; align-items:flex-start; gap:.1rem;">
<div class="cc-kpi-performance-head">
<span class="cc-kpi-label"><?php echo htmlspecialchars($performanceLabel); ?></span>
<span class="cc-kpi-value"><?php echo htmlspecialchars($performanceTodayValue); ?></span>
<?php if ($performanceTodayValue !== ''): ?>
<span class="cc-kpi-value"><?php echo htmlspecialchars($performanceTodayValue); ?></span>
<?php endif; ?>
</div>
<div class="cc-kpi-meta"><?php echo htmlspecialchars($performanceTodayMetaTitle); ?></div>
</div>

View File

@ -12,7 +12,7 @@ function drive_test_normalize_header(string $header): string
function drive_test_available_stores(): array
{
return [
$stores = [
// Hoja original que ya venía funcionando.
'flower' => [
'label' => 'Flower',
@ -81,6 +81,12 @@ function drive_test_available_stores(): array
'incremental_sync' => true,
],
];
if (function_exists('cc_test_filter_visible_drive_stores')) {
$stores = cc_test_filter_visible_drive_stores($stores);
}
return $stores;
}
function drive_test_store_navigation_groups(array $availableStores): array