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';
-
+
Llamar / Gestionar
@@ -1636,12 +1717,18 @@ require_once 'layout_header.php';
-
+
@@ -1663,12 +1750,18 @@ require_once 'layout_header.php';
-
+
@@ -1991,6 +2084,21 @@ require_once 'layout_header.php';
+
+
+
@@ -2323,15 +2431,14 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
- document.querySelectorAll('select[id^="confirmacion_producto-"]').forEach(select => {
- const sourceKey = select.id.replace('confirmacion_producto-', '');
+ document.querySelectorAll('input[id^="confirmacion_producto-"], input[id^="confirmacion_producto_extra-"]').forEach(input => {
const syncTitle = () => {
- const selectedOption = select.selectedOptions && select.selectedOptions[0] ? select.selectedOptions[0] : null;
- const label = selectedOption && selectedOption.textContent ? selectedOption.textContent.trim() : (select.value || 'Sin producto seleccionado');
- select.title = label;
+ const value = (input.value || '').trim();
+ input.title = value !== '' ? value : (input.getAttribute('placeholder') || 'Escribe para buscar un producto');
};
syncTitle();
- select.addEventListener('change', syncTitle);
+ input.addEventListener('input', syncTitle);
+ input.addEventListener('change', syncTitle);
});
document.querySelectorAll('.js-toggle-confirmacion-extra').forEach(button => {
diff --git a/gestiones_callcenter.php b/gestiones_callcenter.php
index d3df3400..2ae77e17 100644
--- a/gestiones_callcenter.php
+++ b/gestiones_callcenter.php
@@ -2195,7 +2195,7 @@ require_once 'layout_header.php';
-
+
Llamar / Gestionar
@@ -2245,12 +2245,18 @@ require_once 'layout_header.php';
-
+
@@ -2272,12 +2278,18 @@ require_once 'layout_header.php';
-
+
@@ -2605,6 +2617,21 @@ require_once 'layout_header.php';
+
+
+
@@ -2981,15 +3008,14 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
- document.querySelectorAll('select[id^="confirmacion_producto-"]').forEach(select => {
- const sourceKey = select.id.replace('confirmacion_producto-', '');
+ document.querySelectorAll('input[id^="confirmacion_producto-"], input[id^="confirmacion_producto_extra-"]').forEach(input => {
const syncTitle = () => {
- const selectedOption = select.selectedOptions && select.selectedOptions[0] ? select.selectedOptions[0] : null;
- const label = selectedOption && selectedOption.textContent ? selectedOption.textContent.trim() : (select.value || 'Sin producto seleccionado');
- select.title = label;
+ const value = (input.value || '').trim();
+ input.title = value !== '' ? value : (input.getAttribute('placeholder') || 'Escribe para buscar un producto');
};
syncTitle();
- select.addEventListener('change', syncTitle);
+ input.addEventListener('input', syncTitle);
+ input.addEventListener('change', syncTitle);
});
document.querySelectorAll('.js-toggle-confirmacion-extra').forEach(button => {
diff --git a/includes/drive_test_orders.php b/includes/drive_test_orders.php
index 9ab0b3c4..da943eed 100644
--- a/includes/drive_test_orders.php
+++ b/includes/drive_test_orders.php
@@ -36,6 +36,8 @@ function drive_test_available_stores(): array
'status_sync_column' => 'V',
'advisor_sync_start_row' => 7207,
'advisor_sync_column' => 'X',
+ 'delivery_sync_start_row' => 7291,
+ 'delivery_sync_column' => 'W',
],
// Bandeja compartida de carritos recuperables: misma estructura, pero desde la fila 2000.
'tuani_recuperables' => [
@@ -188,6 +190,70 @@ function drive_test_advisor_sync_column(string $storeKey): string
return preg_match('/^[A-Z]+$/', $column) ? $column : 'R';
}
+function drive_test_delivery_sync_start_row(string $storeKey): ?int
+{
+ $storeConfig = drive_test_get_store_config($storeKey);
+ if (!array_key_exists('delivery_sync_start_row', $storeConfig)) {
+ return null;
+ }
+
+ $startRow = (int) $storeConfig['delivery_sync_start_row'];
+ return $startRow > 0 ? $startRow : null;
+}
+
+function drive_test_delivery_sync_column(string $storeKey): string
+{
+ $storeConfig = drive_test_get_store_config($storeKey);
+ $column = strtoupper(trim((string) ($storeConfig['delivery_sync_column'] ?? 'W')));
+
+ return preg_match('/^[A-Z]+$/', $column) ? $column : 'W';
+}
+
+function drive_test_format_programmed_delivery_label(?string $deliveryDate): string
+{
+ $deliveryDate = trim((string) $deliveryDate);
+ if ($deliveryDate === '') {
+ return '';
+ }
+
+ $parsedDeliveryDate = DateTimeImmutable::createFromFormat('Y-m-d', $deliveryDate);
+ if (!$parsedDeliveryDate) {
+ return '';
+ }
+
+ return 'PROGRAMADO ' . $parsedDeliveryDate->format('d');
+}
+
+function drive_test_format_sheet_w_label(string $estado, ?string $advisorName = null, ?string $deliveryDate = null): string
+{
+ $estado = cc_test_normalize_state(trim($estado));
+ $advisorName = drive_test_compact_text($advisorName);
+
+ if ($estado === 'CONFIRMADO CONTRAENTREGA') {
+ return drive_test_format_programmed_delivery_label($deliveryDate);
+ }
+
+ $labelMap = [
+ 'SE ENVIO NUMERO DE CUENTA' => 'SE ENVIO NUMERO DE CUENTA',
+ 'DEVOLVER LLAMADA' => 'DEVOLVER LLAMADA',
+ 'OBSERVADO' => 'OBSERVADO',
+ 'CANCELADO' => 'CANCELADO',
+ 'REPETIDO' => 'REPETIDO',
+ 'CONFIRMADO ENVIO' => 'ENVIO',
+ ];
+
+ if (!isset($labelMap[$estado])) {
+ return '';
+ }
+
+ $label = $labelMap[$estado];
+ if ($advisorName !== '') {
+ $label .= ' - ' . $advisorName;
+ }
+
+ return $label;
+}
+
function drive_test_status_sync_reference_datetime(array $sourceOrder): ?DateTimeImmutable
{
$candidates = [
@@ -241,7 +307,7 @@ function drive_test_should_sync_status_to_sheet(array $sourceOrder): bool
return $referenceDate->format('Y-m-d') >= $cutoff->format('Y-m-d');
}
-function drive_test_sync_status_to_sheet(array $sourceOrder, string $estado, ?string $advisorName = null): array
+function drive_test_sync_status_to_sheet(array $sourceOrder, string $estado, ?string $advisorName = null, ?string $deliveryDate = null): array
{
$storeKey = trim((string) ($sourceOrder['store_key'] ?? ''));
if ($storeKey === '') {
@@ -290,6 +356,16 @@ function drive_test_sync_status_to_sheet(array $sourceOrder, string $estado, ?st
$service = new Google\Service\Sheets($client);
$statusRange = drive_test_resolve_sheet_a1_range($service, $spreadsheetId, $sheetGid, $statusColumn . $sourceRow);
$estadoNormalizado = cc_test_normalize_state(trim($estado));
+
+ $deliveryDate = trim((string) $deliveryDate);
+ if ($deliveryDate !== '') {
+ $parsedDeliveryDate = DateTimeImmutable::createFromFormat('Y-m-d', $deliveryDate);
+ $deliveryDate = $parsedDeliveryDate ? $parsedDeliveryDate->format('Y-m-d') : '';
+ }
+
+ $advisorName = drive_test_compact_text($advisorName);
+ $deliveryLabel = drive_test_format_sheet_w_label($estadoNormalizado, $advisorName, $deliveryDate);
+
$updates = [
new Google\Service\Sheets\ValueRange([
'range' => $statusRange,
@@ -297,7 +373,17 @@ function drive_test_sync_status_to_sheet(array $sourceOrder, string $estado, ?st
]),
];
- $advisorName = drive_test_compact_text($advisorName);
+ $deliveryRange = null;
+ $deliveryStartRow = drive_test_delivery_sync_start_row($storeKey);
+ if ($deliveryStartRow !== null && $sourceRow >= $deliveryStartRow) {
+ $deliveryColumn = drive_test_delivery_sync_column($storeKey);
+ $deliveryRange = drive_test_resolve_sheet_a1_range($service, $spreadsheetId, $sheetGid, $deliveryColumn . $sourceRow);
+ $updates[] = new Google\Service\Sheets\ValueRange([
+ 'range' => $deliveryRange,
+ 'values' => [[ $deliveryLabel ]],
+ ]);
+ }
+
$advisorRange = null;
$advisorStartRow = drive_test_advisor_sync_start_row($storeKey);
if ($advisorName !== '' && $advisorStartRow !== null && $sourceRow >= $advisorStartRow) {
@@ -322,8 +408,11 @@ function drive_test_sync_status_to_sheet(array $sourceOrder, string $estado, ?st
'source_row' => $sourceRow,
'range' => $statusRange,
'advisor_range' => $advisorRange,
+ 'delivery_range' => $deliveryRange,
'estado' => $estadoNormalizado,
'advisor_name' => $advisorRange !== null ? $advisorName : null,
+ 'delivery_date' => $deliveryRange !== null ? $deliveryDate : null,
+ 'delivery_label' => $deliveryRange !== null ? $deliveryLabel : null,
];
}
diff --git a/update_callcenter_test_tracking.php b/update_callcenter_test_tracking.php
index 6c769fd0..aa67c6ec 100644
--- a/update_callcenter_test_tracking.php
+++ b/update_callcenter_test_tracking.php
@@ -1368,7 +1368,7 @@ try {
$driveSyncResult = null;
if ($sourceOrderForAuth) {
try {
- $driveSyncResult = drive_test_sync_status_to_sheet($sourceOrderForAuth, $estado, $advisorNameForDrive);
+ $driveSyncResult = drive_test_sync_status_to_sheet($sourceOrderForAuth, $estado, $advisorNameForDrive, $fechaEntrega);
} catch (Throwable $driveException) {
error_log('update_callcenter_test_tracking.php drive sync: ' . $driveException->getMessage());
$driveSyncResult = [