From 9a2a8b59ef78bb803f8f5ec7f1f15ae21b2fb32e Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 4 Aug 2026 05:30:57 +0000 Subject: [PATCH] Autosave: 20260804-053238 --- call_center_pro.php | 16 +++---- gestiones_callcenter.php | 8 ++-- includes/callcenter_test_helpers.php | 71 ++++++++++++++++------------ update_callcenter_test_tracking.php | 2 +- 4 files changed, 55 insertions(+), 42 deletions(-) diff --git a/call_center_pro.php b/call_center_pro.php index bf787f9d..c634a609 100644 --- a/call_center_pro.php +++ b/call_center_pro.php @@ -457,7 +457,7 @@ $_SESSION[cc_test_callcenter_view_session_key()] = cc_test_normalize_context_key $viewStatesNote = $isTuaniRecuperablesStore ? 'Estados disponibles: Carritos recuperables, Seguimiento, Observados, Confirmados, Recuperados y Todos. Esta bandeja compartida está disponible para cualquier asesora y, cuando una asesora gestiona un carrito recuperable, el sistema lo asigna automáticamente a quien lo trabaja. Si el teléfono ya existe en TUANI principal, se marca en gris como Pedido repetido en tienda solo en esta bandeja.' - : 'Estados disponibles: Por llamar, Devolver llamada, Observado, Se envió número de cuenta, Confirmado contraentrega, Confirmado envío, Cancelado y Repetido. Promo Final se habilita desde el Día 3 para pedidos nuevos desde el 29/07/2026 y desde el Día 4 para pedidos anteriores; desde ese momento el número queda visible para llamar y gestionar, y el sustento solo se usa para mover el pedido a Promo Final. Además, esta bandeja principal incluye reparto intercalado diario: en modo automático reparte pedidos sin asignar entre asesoras activas; en modo manual el supervisor los mueve pedido por pedido. También puedes usar la vista Últimos 3 días hábiles para concentrar el seguimiento reciente.'; + : 'Estados disponibles: Por llamar, Devolver llamada, Observado, Se envió número de cuenta, Confirmado contraentrega, Confirmado envío, Cancelado y Repetido. Promo Final se habilita desde el Día 3 para todos los pedidos; desde ese momento el número queda visible para llamar y gestionar, y el sustento solo se usa para mover el pedido a Promo Final. Además, esta bandeja principal incluye reparto intercalado diario: en modo automático reparte pedidos sin asignar entre asesoras activas; en modo manual el supervisor los mueve pedido por pedido. También puedes usar la vista Últimos 3 días hábiles para concentrar el seguimiento reciente.'; $errorMessage = null; $noticeMessage = null; @@ -800,20 +800,20 @@ try { $performanceDashboard = cc_test_build_performance_dashboard_period($performanceOrders, $assessors, $kpiPeriodStart, $kpiPeriodEnd); $performanceSubtitle = $isTuaniRecuperablesStore - ? 'Los indicadores de esta bandeja se calculan sobre todos los pedidos visibles para cualquier asesora en el periodo seleccionado.' + ? '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.' : ($isAdmin - ? 'Confirmación = pedidos asignados en el periodo confirmados en el mismo periodo. Recuperación = pedidos asignados antes del periodo confirmados dentro del periodo. Rendimiento = confirmados totales (nuevos + recuperados) sobre asignados del periodo.' - : 'Tus KPIs del periodo seleccionado se calculan sobre tus pedidos asignados en ese rango. Confirmación = asignados del periodo confirmados en el mismo periodo. Recuperación = asignados antes del periodo confirmados dentro del periodo. Rendimiento = confirmados totales (nuevos + recuperados) sobre asignados del periodo.'); + ? '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.'); if ($isTuaniRecuperablesStore) { $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 y cancelados.'; + $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.'; $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.'; + $performanceFootnote = 'Los indicadores se calculan sobre todos los pedidos visibles de esta bandeja compartida; los repetidos solo suman cantidad y no bajan el porcentaje.'; } else { if ($isAdmin) { $performanceTitle = $selectedAssessorFilterLabel !== '' @@ -823,7 +823,7 @@ try { $performanceTitle = $kpiPeriodKey === 'today' ? 'Mi KPI diario' : 'Mi KPI de ' . $kpiPeriodTitleLabel; } $performanceChart1Title = $isAdmin ? 'Distribución de estados' : 'Mi distribución de estados'; - $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 y cancelados.'; + $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.'; $performanceChart2Title = $kpiPeriodKey === 'today' ? ($isAdmin ? 'Evolución de hoy' : 'Tu evolución de hoy') : ($isAdmin ? 'Evolución de ' . $kpiPeriodTitleLabel : 'Tu evolución de ' . $kpiPeriodTitleLabel); @@ -2004,7 +2004,7 @@ require_once 'layout_header.php';
-
Obligatoria al mover el pedido a Promo Final desde el Día . La supervisora podrá revisarla.
+
Obligatoria al mover el pedido a Promo Final desde el Día . La supervisora podrá revisarla.
Sustento actual: ver imagen · . diff --git a/gestiones_callcenter.php b/gestiones_callcenter.php index 63b0eb87..2cb10110 100644 --- a/gestiones_callcenter.php +++ b/gestiones_callcenter.php @@ -344,7 +344,7 @@ $_SESSION[cc_test_callcenter_view_session_key()] = cc_test_normalize_context_key $viewStatesNote = $isTuaniRecuperablesStore ? 'Estados disponibles: Carritos recuperables, Seguimiento, Observados, Confirmados, Recuperados y Todos. Esta bandeja compartida está disponible para cualquier asesora y, cuando una asesora gestiona un carrito recuperable, el sistema lo asigna automáticamente a quien lo trabaja. Si el teléfono ya existe en TUANI principal, se marca en gris como Pedido repetido en tienda solo en esta bandeja.' - : 'Estados disponibles: Por llamar, Devolver llamada, Observado, Se envió número de cuenta, Confirmado contraentrega, Confirmado envío, Cancelado y Repetido. Promo Final se habilita desde el Día 3 para pedidos nuevos desde el 29/07/2026 y desde el Día 4 para pedidos anteriores; desde ese momento el número se mantiene visible para llamar y gestionar, y el sustento solo se usa para mover el pedido a Promo Final. En las vistas Todos y Últimos 3 días hábiles, la llamada y la gestión siguen disponibles desde el panel.'; + : 'Estados disponibles: Por llamar, Devolver llamada, Observado, Se envió número de cuenta, Confirmado contraentrega, Confirmado envío, Cancelado y Repetido. Promo Final se habilita desde el Día 3 para todos los pedidos; desde ese momento el número se mantiene visible para llamar y gestionar, y el sustento solo se usa para mover el pedido a Promo Final. En las vistas Todos y Últimos 3 días hábiles, la llamada y la gestión siguen disponibles desde el panel.'; $errorMessage = null; $noticeMessage = null; @@ -980,9 +980,9 @@ try { $performanceDashboard = cc_test_build_performance_dashboard($performanceOrders, $assessors, 7); $performanceDashboardHtml = cc_test_render_performance_dashboard($performanceDashboard, [ 'title' => 'KPI diario por asesora', - 'subtitle' => 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados hoy.', + '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 y cancelados.', + '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.', '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.', @@ -2467,7 +2467,7 @@ require_once 'layout_header.php';
-
Obligatoria al mover el pedido a Promo Final desde el Día . La supervisora podrá revisarla.
+
Obligatoria al mover el pedido a Promo Final desde el Día . La supervisora podrá revisarla.
Sustento actual: ver imagen · . diff --git a/includes/callcenter_test_helpers.php b/includes/callcenter_test_helpers.php index 240f344a..2c926d48 100644 --- a/includes/callcenter_test_helpers.php +++ b/includes/callcenter_test_helpers.php @@ -83,6 +83,7 @@ function cc_test_assessor_default_color_hex(string $assessorKey): string 'CARMEN' => '#FF00FF', 'MIRELLA' => '#EFBF04', 'LUCIBOT' => '#FD7E14', + 'MARIA', 'MARÍA' => '#800080', default => $palette[$paletteIndex], }; } @@ -1353,13 +1354,7 @@ function cc_test_followup_is_from_promo_final_cutoff(array $order): bool function cc_test_followup_promo_final_threshold_day(array $order): int { - $startedAt = cc_test_followup_started_at($order); - if (!$startedAt instanceof DateTimeImmutable) { - return 4; - } - - $cutoff = cc_test_followup_promo_final_cutoff(); - return $startedAt->format('Y-m-d') >= $cutoff->format('Y-m-d') ? 3 : 4; + return 3; } function cc_test_followup_day_info(array $order): ?array @@ -1404,13 +1399,13 @@ function cc_test_followup_day_info(array $order): ?array } elseif ($dayNumber === 2) { $badgeClass = 'bg-info-subtle text-info-emphasis border'; $textClass = 'text-info-emphasis'; - $description = 'Aún está dentro de los 3 días de seguimiento.'; + $description = 'Aún está dentro de los primeros ' . $promoFinalThresholdDay . ' días de seguimiento.'; } return [ 'day_number' => $dayNumber, - 'display_label' => $dayNumber >= 4 ? 'Día 4+' : 'Día ' . $dayNumber, - 'compact_label' => $dayNumber >= 4 ? '4+' : (string) $dayNumber, + 'display_label' => $dayNumber >= $promoFinalThresholdDay ? 'Día ' . $promoFinalThresholdDay . '+' : 'Día ' . $dayNumber, + 'compact_label' => $dayNumber >= $promoFinalThresholdDay ? $promoFinalThresholdDay . '+' : (string) $dayNumber, 'badge_class' => $badgeClass, 'text_class' => $textClass, 'description' => $description, @@ -1679,6 +1674,7 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in $summary = [ 'scope_total' => 0, 'assigned_today' => 0, + 'assigned_effective_today' => 0, 'confirmed_today_new' => 0, 'confirmed_today_recovered' => 0, 'confirmed_today_total' => 0, @@ -1795,14 +1791,16 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in } $summary['confirmed_today_total'] = $summary['confirmed_today_new'] + $summary['confirmed_today_recovered']; - $summary['confirm_rate_today'] = $summary['assigned_today'] > 0 - ? round(($summary['confirmed_today_new'] / $summary['assigned_today']) * 100, 1) + $summary['assigned_effective_today'] = max(0, (int) ($summary['assigned_today'] ?? 0) - (int) ($summary['repeated_today'] ?? 0)); + $effectiveAssignedToday = (int) ($summary['assigned_effective_today'] ?? 0); + $summary['confirm_rate_today'] = $effectiveAssignedToday > 0 + ? round(($summary['confirmed_today_new'] / $effectiveAssignedToday) * 100, 1) : 0.0; $summary['recovery_rate_today'] = $summary['confirmed_today_total'] > 0 ? round(($summary['confirmed_today_recovered'] / $summary['confirmed_today_total']) * 100, 1) : 0.0; - $summary['performance_today_rate'] = $summary['assigned_today'] > 0 - ? round(($summary['confirmed_today_total'] / $summary['assigned_today']) * 100, 1) + $summary['performance_today_rate'] = $effectiveAssignedToday > 0 + ? round(($summary['confirmed_today_total'] / $effectiveAssignedToday) * 100, 1) : 0.0; $advisorLabels = []; @@ -2040,6 +2038,7 @@ function cc_test_build_performance_dashboard_period(array $orders, array $assess $summary = [ 'scope_total' => 0, 'assigned_today' => 0, + 'assigned_effective_today' => 0, 'confirmed_today_new' => 0, 'confirmed_today_recovered' => 0, 'confirmed_today_total' => 0, @@ -2156,14 +2155,16 @@ function cc_test_build_performance_dashboard_period(array $orders, array $assess } $summary['confirmed_today_total'] = $summary['confirmed_today_new'] + $summary['confirmed_today_recovered']; - $summary['confirm_rate_today'] = $summary['assigned_today'] > 0 - ? round(($summary['confirmed_today_new'] / $summary['assigned_today']) * 100, 1) + $summary['assigned_effective_today'] = max(0, (int) ($summary['assigned_today'] ?? 0) - (int) ($summary['repeated_today'] ?? 0)); + $effectiveAssignedToday = (int) ($summary['assigned_effective_today'] ?? 0); + $summary['confirm_rate_today'] = $effectiveAssignedToday > 0 + ? round(($summary['confirmed_today_new'] / $effectiveAssignedToday) * 100, 1) : 0.0; $summary['recovery_rate_today'] = $summary['confirmed_today_total'] > 0 ? round(($summary['confirmed_today_recovered'] / $summary['confirmed_today_total']) * 100, 1) : 0.0; - $summary['performance_today_rate'] = $summary['assigned_today'] > 0 - ? round(($summary['confirmed_today_total'] / $summary['assigned_today']) * 100, 1) + $summary['performance_today_rate'] = $effectiveAssignedToday > 0 + ? round(($summary['confirmed_today_total'] / $effectiveAssignedToday) * 100, 1) : 0.0; $advisorLabels = []; @@ -2363,9 +2364,9 @@ function cc_test_build_performance_dashboard_period(array $orders, array $assess function cc_test_render_performance_dashboard(array $dashboard, array $options = []): string { $title = trim((string) ($options['title'] ?? 'KPI diario por asesora')); - $subtitle = trim((string) ($options['subtitle'] ?? 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados hoy.')); + $subtitle = trim((string) ($options['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.')); $chart1Title = trim((string) ($options['chart1_title'] ?? 'Distribución de estados de hoy')); - $chart1Note = trim((string) ($options['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 y cancelados.')); + $chart1Note = trim((string) ($options['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.')); $chart2Title = trim((string) ($options['chart2_title'] ?? 'Evolución de los últimos 7 días')); $chart2Note = trim((string) ($options['chart2_note'] ?? 'La línea de confirmados separa pedidos asignados hoy y pedidos asignados antes de hoy.')); $footnote = trim((string) ($options['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.')); @@ -2388,13 +2389,22 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options = ? array_values($statusChartDataset['backgroundColor']) : []; $statusTotal = array_sum($statusChartValues); + $statusRepeatedTotal = 0; + foreach ($statusChartLabels as $index => $label) { + if (preg_match('/repetid/i', (string) $label) === 1) { + $statusRepeatedTotal += (int) ($statusChartValues[$index] ?? 0); + } + } + $statusPercentageTotal = max(0, $statusTotal - $statusRepeatedTotal); $confirmedTodayNew = (int) ($summary['confirmed_today_new'] ?? 0); $confirmedTodayRecovered = (int) ($summary['confirmed_today_recovered'] ?? 0); $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 = %d nuevos + %d recuperado%s', + '%d confirmados sobre %d asignados efectivos = %d nuevos + %d recuperado%s', $confirmedTodayTotal, + $effectiveAssignedToday, $confirmedTodayNew, $confirmedTodayRecovered, $confirmedTodayRecovered === 1 ? '' : 's' @@ -2404,10 +2414,13 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options = $statusLegendItems = []; if ($statusTotal > 0) { foreach ($statusChartValues as $index => $value) { + $label = (string) ($statusChartLabels[$index] ?? ('Estado ' . ($index + 1))); + $isQuantityOnly = preg_match('/repetid/i', $label) === 1; $statusLegendItems[] = [ - 'label' => (string) ($statusChartLabels[$index] ?? ('Estado ' . ($index + 1))), + 'label' => $label, 'value' => (int) $value, - 'percentage' => round(((float) $value / $statusTotal) * 100, 1), + 'percentage' => $isQuantityOnly ? null : ($statusPercentageTotal > 0 ? round(((float) $value / $statusPercentageTotal) * 100, 1) : 0.0), + 'is_quantity_only' => $isQuantityOnly, 'color' => (string) ($statusChartColors[$index] ?? 'rgba(108, 117, 125, 0.65)'), ]; } @@ -2509,7 +2522,7 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
pedidos
-
%
+
@@ -2567,6 +2580,7 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options = return sum + value; }, 0); const statusHasData = statusTotal > 0; + const statusPercentageBase = ; const statusBackgroundColors = Array.isArray(statusDataset.backgroundColor) && statusDataset.backgroundColor.length === statusValues.length ? statusDataset.backgroundColor : [ @@ -2636,11 +2650,10 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options = } const label = context.label || ''; const value = typeof context.raw === 'number' ? context.raw : Number(context.raw || 0); - const data = Array.isArray(context.dataset.data) ? context.dataset.data : []; - const total = data.reduce(function (sum, item) { - return sum + (Number(item) || 0); - }, 0); - const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0'; + if (label.toLowerCase().includes('repetid')) { + return label + ': ' + value; + } + const percentage = statusPercentageBase > 0 ? ((value / statusPercentageBase) * 100).toFixed(1) : '0.0'; return label + ': ' + value + ' (' + percentage + '%)'; } } diff --git a/update_callcenter_test_tracking.php b/update_callcenter_test_tracking.php index a13f43df..cb197ca2 100644 --- a/update_callcenter_test_tracking.php +++ b/update_callcenter_test_tracking.php @@ -1076,7 +1076,7 @@ try { 'assigned_at' => $currentTracking['assigned_at'] ?? null, ])); if (empty($promoFinalDayInfo['is_promo_final'])) { - $promoFinalThresholdDay = (int) ($promoFinalDayInfo['promo_final_threshold_day'] ?? 4); + $promoFinalThresholdDay = (int) ($promoFinalDayInfo['promo_final_threshold_day'] ?? 3); throw new RuntimeException('Este pedido aún no puede pasar a Promo Final. Recién se habilita desde el Día ' . $promoFinalThresholdDay . '.'); }