From b009b4b95c5235c204d95f527a3895d2be418d13 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 30 Jul 2026 06:53:54 +0000 Subject: [PATCH] Autosave: 20260730-065528 --- call_center_pro.php | 177 ++++++++++++++++++++++------ gestiones_callcenter.php | 64 +++++++--- includes/drive_test_orders.php | 93 ++++++++++++++- update_callcenter_test_tracking.php | 2 +- 4 files changed, 279 insertions(+), 57 deletions(-) diff --git a/call_center_pro.php b/call_center_pro.php index d95b008c..5a1cd562 100644 --- a/call_center_pro.php +++ b/call_center_pro.php @@ -14,7 +14,7 @@ $provinciasPorDepartamentoContraentrega = contraentregaProvinciasPorDepartamento $distritosPorProvinciaContraentrega = contraentregaDistritosPorProvincia(); $departamentosContraentrega = array_keys($provinciasPorDepartamentoContraentrega); $pageTitle = 'Pedidos Asignados | Bandeja de la asesora'; -$pageDescription = 'Bandeja para que cada asesora gestione sus pedidos asignados con la misma interfaz, estados, historial y acciones.'; +$pageDescription = 'Bandeja para que cada asesora gestione sus pedidos asignados con vistas de hoy, últimos 3 días hábiles, estados, historial y acciones.'; $kpiPeriodConfig = cc_test_resolve_kpi_period((string) ($_GET['kpi_period'] ?? 'today'), (string) ($_GET['kpi_date'] ?? '')); $kpiPeriodKey = (string) ($kpiPeriodConfig['key'] ?? 'today'); @@ -83,7 +83,57 @@ function cc_test_format_date(?string $value, string $fallback = 'Sin fecha'): st function cc_test_format_date_input(?string $value): string { $date = cc_test_parse_datetime($value); - return $date ? $date->format('Y-m-d') : ''; + return $date ? $date->format('Y-m-d\TH:i') : ''; +} + +function cc_test_is_working_day(DateTimeImmutable $date): bool +{ + return (int) $date->format('N') !== 7; +} + +function cc_test_last_working_day_keys(DateTimeImmutable $todayStart, int $days = 3): array +{ + $days = max(1, $days); + $cursor = $todayStart->setTime(0, 0, 0); + + while (!cc_test_is_working_day($cursor)) { + $cursor = $cursor->sub(new DateInterval('P1D')); + } + + $keys = []; + while (count($keys) < $days) { + if (cc_test_is_working_day($cursor)) { + $keys[] = $cursor->format('Y-m-d'); + } + $cursor = $cursor->sub(new DateInterval('P1D')); + } + + return $keys; +} + +function cc_test_recent_followup_anchor(array $order): ?DateTimeImmutable +{ + $assignedDate = cc_test_parse_datetime($order['assigned_at'] ?? null); + if ($assignedDate instanceof DateTimeImmutable) { + return $assignedDate; + } + + return cc_test_followup_started_at($order); +} + +function cc_test_is_within_last_days_window(array $order, DateTimeImmutable $todayStart, int $days = 3): bool +{ + if ($days < 1) { + return false; + } + + $anchorDate = cc_test_recent_followup_anchor($order); + if (!$anchorDate instanceof DateTimeImmutable) { + return false; + } + + $anchorKey = $anchorDate->setTime(0, 0, 0)->format('Y-m-d'); + return in_array($anchorKey, cc_test_last_working_day_keys($todayStart, $days), true); } function cc_test_format_price(?string $value): string @@ -309,6 +359,24 @@ $viewCards = $isTuaniRecuperablesStore ? [ 'stat_key' => 'nuevos_hoy', 'active_class' => 'bg-primary-subtle border border-primary', ], + 'ultimos_3_dias' => [ + 'label' => 'Últimos 3 días hábiles', + 'heading' => 'Pedidos asignados de los últimos 3 días hábiles', + 'stat_key' => 'ultimos_3_dias', + 'active_class' => 'bg-info-subtle border border-info', + ], + 'observados' => [ + 'label' => 'Observados', + 'heading' => 'Observados', + 'stat_key' => 'observados', + 'active_class' => 'bg-warning-subtle border border-warning', + ], + 'seguimiento' => [ + 'label' => 'Seguimiento', + 'heading' => 'Seguimiento', + 'stat_key' => 'seguimiento', + 'active_class' => 'bg-primary-subtle border border-primary', + ], 'confirmados' => [ 'label' => 'Confirmados', 'heading' => 'Confirmados', @@ -321,24 +389,12 @@ $viewCards = $isTuaniRecuperablesStore ? [ 'stat_key' => 'recuperados', 'active_class' => 'bg-warning-subtle border border-warning', ], - 'seguimiento' => [ - 'label' => 'Seguimiento', - 'heading' => 'Seguimiento', - 'stat_key' => 'seguimiento', - 'active_class' => 'bg-primary-subtle border border-primary', - ], 'promo_final' => [ 'label' => 'Promo Final', 'heading' => 'Promo Final', 'stat_key' => 'promo_final', 'active_class' => 'bg-danger-subtle border border-danger', ], - 'observados' => [ - 'label' => 'Observados', - 'heading' => 'Observados', - 'stat_key' => 'observados', - 'active_class' => 'bg-warning-subtle border border-warning', - ], 'cerrados' => [ 'label' => 'Cerrados', 'heading' => 'Cerrados / descartados', @@ -366,7 +422,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 4 y exige imagen de sustento para mover el pedido. 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.'; + : '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 4 y exige imagen de sustento para mover el pedido. 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; @@ -386,6 +442,7 @@ $stats = [ 'total' => 0, 'pendientes_hoy' => 0, 'nuevos_hoy' => 0, + 'ultimos_3_dias' => 0, 'confirmados' => 0, 'recuperados' => 0, 'seguimiento' => 0, @@ -649,6 +706,7 @@ try { $order['pendiente_logistica'] = $order['pendiente_logistica_destino'] !== null; $order['es_nuevo_hoy'] = $assignedDate ? $assignedDate->format('Y-m-d') === $todayStart->format('Y-m-d') : false; + $order['es_ultimos_3_dias'] = cc_test_is_within_last_days_window($order, $todayStart, 3); $order['es_pendiente_hoy'] = !$order['es_promo_final'] && (in_array($order['estado'], $openStates, true) || $order['pendiente_logistica']); $order['es_cerrado'] = in_array($order['estado'], $closedStates, true); $order['assessor_key'] = cc_test_find_assessor_key_by_user_id(isset($order['user_id']) ? (int) $order['user_id'] : null, $assessors); @@ -665,6 +723,9 @@ try { if ($order['es_nuevo_hoy']) { $stats['nuevos_hoy']++; } + if ($order['es_ultimos_3_dias']) { + $stats['ultimos_3_dias']++; + } if ($order['es_promo_final']) { $stats['promo_final']++; } @@ -751,6 +812,7 @@ try { return match ($view) { 'pendientes_hoy' => (bool) ($order['es_pendiente_hoy'] ?? false), 'nuevos_hoy' => (bool) ($order['es_nuevo_hoy'] ?? 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), 'seguimiento' => ($order['estado'] ?? '') === 'SE ENVIO NUMERO DE CUENTA' && !($order['es_promo_final'] ?? false), @@ -779,6 +841,25 @@ try { } } + if ($view === 'ultimos_3_dias') { + $aRecent = cc_test_recent_followup_anchor($a); + $bRecent = cc_test_recent_followup_anchor($b); + $aRecentTs = $aRecent instanceof DateTimeImmutable ? $aRecent->getTimestamp() : null; + $bRecentTs = $bRecent instanceof DateTimeImmutable ? $bRecent->getTimestamp() : null; + if ($aRecentTs !== null && $bRecentTs !== null && $aRecentTs !== $bRecentTs) { + return $aRecentTs <=> $bRecentTs; + } + if ($aRecentTs !== null && $bRecentTs === null) { + return -1; + } + if ($aRecentTs === null && $bRecentTs !== null) { + return 1; + } + if ($aImport !== $bImport) { + return $bImport <=> $aImport; + } + } + if ($view === 'recuperados') { $aRecovered = cc_test_dashboard_action_datetime($a); $bRecovered = cc_test_dashboard_action_datetime($b); @@ -946,7 +1027,7 @@ if ($isTuaniRecuperablesStore) { $pageDescription = 'Bandeja compartida para que cualquier asesora gestione carritos recuperables con vistas de seguimiento y observados desde Drive.'; } else { $pageTitle = 'Pedidos Asignados | Bandeja de la asesora'; - $pageDescription = 'Bandeja para que cada asesora gestione sus pedidos asignados con la misma interfaz, estados, historial y acciones.'; + $pageDescription = 'Bandeja para que cada asesora gestione sus pedidos asignados con vistas de hoy, últimos 3 días hábiles, estados, historial y acciones.'; } $callCenterSectionLabel = $isTuaniRecuperablesStore ? 'Carritos recuperables' : 'Pedidos Asignados'; @@ -1586,7 +1667,7 @@ require_once 'layout_header.php';