diff --git a/.gitignore b/.gitignore
index e427ff3c..a5867014 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
node_modules/
*/node_modules/
*/build/
+
+assets/uploads/
diff --git a/call_center_pro.php b/call_center_pro.php
new file mode 100644
index 00000000..dd6dc46c
--- /dev/null
+++ b/call_center_pro.php
@@ -0,0 +1,1349 @@
+
Acceso denegado.
";
+ require_once 'layout_footer.php';
+ exit();
+}
+
+
+function cc_test_parse_datetime(?string $value): ?DateTimeImmutable
+{
+ $value = trim((string) $value);
+ if ($value === '') {
+ return null;
+ }
+
+ try {
+ return new DateTimeImmutable($value);
+ } catch (Throwable $exception) {
+ return null;
+ }
+}
+
+function cc_test_format_datetime(?string $value, string $fallback = 'Sin programar'): string
+{
+ $date = cc_test_parse_datetime($value);
+ return $date ? $date->format('d/m/Y h:i A') : $fallback;
+}
+
+function cc_test_format_datetime_input(?string $value): string
+{
+ $date = cc_test_parse_datetime($value);
+ return $date ? $date->format('Y-m-d\TH:i') : '';
+}
+
+function cc_test_format_date(?string $value, string $fallback = 'Sin fecha'): string
+{
+ $date = cc_test_parse_datetime($value);
+ return $date ? $date->format('d/m/Y') : $fallback;
+}
+
+function cc_test_format_date_input(?string $value): string
+{
+ $date = cc_test_parse_datetime($value);
+ return $date ? $date->format('Y-m-d') : '';
+}
+
+function cc_test_format_price(?string $value): string
+{
+ $value = trim((string) $value);
+ return $value !== '' ? 'S/ ' . $value : 'No registrado';
+}
+
+function cc_test_display_value(?string $value, string $fallback = 'No registrado'): string
+{
+ $value = trim((string) $value);
+ return $value !== '' ? $value : $fallback;
+}
+
+function cc_test_order_label(array $order): string
+{
+ $codigo = trim((string) ($order['codigo'] ?? ''));
+ if ($codigo !== '') {
+ return '#' . ltrim($codigo, '#');
+ }
+
+ return 'Sin número';
+}
+
+function cc_test_badge_class(string $estado): string
+{
+ return match (cc_test_normalize_state($estado)) {
+ 'CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO' => 'bg-success-subtle text-success-emphasis',
+ 'DEVOLVER LLAMADA' => 'bg-info-subtle text-info-emphasis',
+ 'OBSERVADO' => 'bg-warning-subtle text-warning-emphasis',
+ 'CANCELADO' => 'bg-danger-subtle text-danger-emphasis',
+ 'REPETIDO' => 'bg-secondary-subtle text-secondary-emphasis',
+ 'SE ENVIO NUMERO DE CUENTA' => 'bg-primary-subtle text-primary-emphasis',
+ default => 'bg-dark-subtle text-dark-emphasis',
+ };
+}
+
+function cc_test_order_time(array $order): int
+{
+ foreach (['proxima_llamada_at', 'numero_cuenta_enviado_at', 'ultima_gestion_at', 'seguimiento_actualizado', 'import_id'] as $field) {
+ $date = cc_test_parse_datetime($order[$field] ?? null);
+ if ($date) {
+ return $date->getTimestamp();
+ }
+ }
+
+ return 0;
+}
+
+function cc_test_followup_semaforo(array $order): ?array
+{
+ if (cc_test_normalize_state((string) ($order['estado'] ?? '')) !== 'SE ENVIO NUMERO DE CUENTA') {
+ return null;
+ }
+
+ return cc_test_account_followup_semaforo($order['numero_cuenta_enviado_at'] ?? null);
+}
+
+$view = $_GET['view'] ?? 'pendientes_hoy';
+$allowedViews = [
+ 'pendientes_hoy' => 'Pendientes de hoy',
+ 'nuevos_hoy' => 'Nuevos de hoy',
+ 'confirmados' => 'Confirmados',
+ 'seguimiento' => 'Seguimiento',
+ 'observados' => 'Observados',
+ 'cerrados' => 'Cerrados / descartados',
+ 'todos' => 'Todos los pedidos cargados',
+];
+if (!isset($allowedViews[$view])) {
+ $view = 'pendientes_hoy';
+}
+
+$errorMessage = null;
+$noticeMessage = null;
+$assessors = [];
+$orders = [];
+$visibleOrders = [];
+$modalsHtml = [];
+$totalRows = 0;
+$loadLimit = 10;
+$startRow = 5552;
+$catalogoProductos = [];
+$stats = [
+ 'total' => 0,
+ 'pendientes_hoy' => 0,
+ 'nuevos_hoy' => 0,
+ 'confirmados' => 0,
+ 'seguimiento' => 0,
+ 'observados' => 0,
+ 'cerrados' => 0,
+];
+
+try {
+ $pdo = db();
+ cc_test_ensure_tracking_table($pdo);
+ cc_test_ensure_historial_llamadas_table($pdo);
+ $assessors = cc_test_fetch_assessors($pdo);
+
+ try {
+ $stmtProductos = $pdo->query("SELECT id, nombre FROM products ORDER BY nombre ASC");
+ $catalogoProductos = $stmtProductos->fetchAll(PDO::FETCH_ASSOC);
+ } catch (Throwable $exception) {
+ $catalogoProductos = [];
+ }
+
+ $preview = drive_test_fetch_orders($loadLimit, $startRow);
+ $totalRows = (int) ($preview['total_rows'] ?? 0);
+ $orders = $preview['orders'] ?? [];
+
+ $tracking = drive_test_fetch_tracking($pdo, array_column($orders, 'source_key'));
+ $orders = drive_test_merge_tracking($orders, $tracking);
+
+ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'assign_assessor') {
+ $sourceKey = trim((string) ($_POST['source_key'] ?? ''));
+ $targetValue = trim((string) ($_POST['target_assessor'] ?? ''));
+ if ($sourceKey === '' || !preg_match('/^[a-f0-9]{40}$/', $sourceKey)) {
+ throw new RuntimeException('Pedido de prueba inválido para asignar.');
+ }
+
+ $targetKey = trim(mb_strtoupper($targetValue));
+ $targetKey = preg_replace('/\s+/', ' ', $targetKey) ?? $targetKey;
+ $selectedAssessor = $targetKey !== '' ? ($assessors[$targetKey] ?? null) : null;
+ if ($targetKey !== '' && !$selectedAssessor) {
+ throw new RuntimeException('No se encontró la asesora seleccionada.');
+ }
+
+ cc_test_upsert_assignee($pdo, $sourceKey, $selectedAssessor ? (int) $selectedAssessor['id'] : null);
+ $noticeMessage = $selectedAssessor
+ ? 'Pedido asignado a ' . $selectedAssessor['label'] . '.'
+ : 'Pedido dejado sin asignar.';
+
+ $tracking = drive_test_fetch_tracking($pdo, array_column($orders, 'source_key'));
+ $orders = drive_test_merge_tracking($orders, $tracking);
+ }
+
+ $sourceKeys = array_column($orders, 'source_key');
+ $callCounts = [];
+ if (!empty($sourceKeys)) {
+ $placeholders = implode(',', array_fill(0, count($sourceKeys), '?'));
+ $stmtCalls = $pdo->prepare("SELECT pedido_id, COUNT(*) as total FROM historial_llamadas WHERE pedido_id IN ($placeholders) GROUP BY pedido_id");
+ $stmtCalls->execute($sourceKeys);
+ $callCounts = $stmtCalls->fetchAll(PDO::FETCH_KEY_PAIR);
+ }
+
+ $role = (string) ($_SESSION['user_role'] ?? '');
+ $currentUserId = (int) ($_SESSION['user_id'] ?? 0);
+ if (!in_array($role, ['Administrador', 'admin'], true)) {
+ $orders = array_values(array_filter($orders, static function (array $order) use ($currentUserId): bool {
+ return (int) ($order['user_id'] ?? 0) === $currentUserId;
+ }));
+ }
+
+ $todayStart = new DateTimeImmutable('today');
+ $todayEnd = $todayStart->setTime(23, 59, 59);
+ $openStates = cc_test_open_states();
+ $closedStates = cc_test_closed_states();
+
+ foreach ($orders as &$order) {
+ $order['estado'] = cc_test_normalize_state((string) ($order['estado'] ?? ''));
+ $order['total_llamadas'] = (int) ($callCounts[$order['source_key']] ?? 0);
+ $importDate = cc_test_parse_datetime($order['import_id'] ?? null);
+ $proximaDate = cc_test_parse_datetime($order['proxima_llamada_at'] ?? null);
+
+ $order['es_nuevo_hoy'] = $importDate ? $importDate->format('Y-m-d') === $todayStart->format('Y-m-d') : false;
+ $order['es_pendiente_hoy'] = in_array($order['estado'], $openStates, true)
+ && ($proximaDate === null || $proximaDate <= $todayEnd);
+ $order['es_cerrado'] = in_array($order['estado'], $closedStates, true);
+
+ $stats['total']++;
+ if ($order['es_nuevo_hoy']) {
+ $stats['nuevos_hoy']++;
+ }
+ if ($order['es_pendiente_hoy']) {
+ $stats['pendientes_hoy']++;
+ }
+ if (in_array($order['estado'], cc_test_confirmed_states(), true)) {
+ $stats['confirmados']++;
+ }
+ if ($order['estado'] === 'SE ENVIO NUMERO DE CUENTA') {
+ $stats['seguimiento']++;
+ }
+ if ($order['estado'] === 'OBSERVADO') {
+ $stats['observados']++;
+ }
+ if ($order['es_cerrado']) {
+ $stats['cerrados']++;
+ }
+ }
+ unset($order);
+
+ $visibleOrders = array_values(array_filter($orders, static function (array $order) use ($view): bool {
+ return match ($view) {
+ 'pendientes_hoy' => (bool) ($order['es_pendiente_hoy'] ?? false),
+ 'nuevos_hoy' => (bool) ($order['es_nuevo_hoy'] ?? false),
+ 'confirmados' => in_array(($order['estado'] ?? ''), cc_test_confirmed_states(), true),
+ 'seguimiento' => ($order['estado'] ?? '') === 'SE ENVIO NUMERO DE CUENTA',
+ 'observados' => ($order['estado'] ?? '') === 'OBSERVADO',
+ 'cerrados' => (bool) ($order['es_cerrado'] ?? false),
+ default => true,
+ };
+ }));
+
+ usort($visibleOrders, static function (array $a, array $b) use ($view): int {
+ if ($view === 'pendientes_hoy') {
+ $aDue = cc_test_parse_datetime($a['proxima_llamada_at'] ?? null);
+ $bDue = cc_test_parse_datetime($b['proxima_llamada_at'] ?? null);
+ if ($aDue && $bDue && $aDue != $bDue) {
+ return $aDue <=> $bDue;
+ }
+ if ($aDue && !$bDue) {
+ return 1;
+ }
+ if (!$aDue && $bDue) {
+ return -1;
+ }
+ }
+
+ return cc_test_order_time($b) <=> cc_test_order_time($a);
+ });
+} catch (Throwable $exception) {
+ $errorMessage = $exception->getMessage();
+}
+
+require_once 'layout_header.php';
+?>
+
+
@@ -436,6 +466,20 @@ require_once 'layout_header.php';
+
+
+
Producto del sistema, cantidad y precio
-
-
+
+
Producto
Seleccione un producto del sistema
@@ -532,7 +576,6 @@ require_once 'layout_header.php';
>
-
Producto seleccionado:
Cantidad
@@ -543,6 +586,34 @@ require_once 'layout_header.php';
+
+
+
+
@@ -970,17 +1041,56 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('select[id^="confirmacion_producto-"]').forEach(select => {
const sourceKey = select.id.replace('confirmacion_producto-', '');
- const resumen = document.getElementById('confirmacion_producto_resumen-' + sourceKey);
- const syncResumen = () => {
+ 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');
- if (resumen) {
- resumen.textContent = 'Producto seleccionado: ' + label;
- }
select.title = label;
};
- syncResumen();
- select.addEventListener('change', syncResumen);
+ syncTitle();
+ select.addEventListener('change', syncTitle);
+ });
+
+ document.querySelectorAll('.js-toggle-confirmacion-extra').forEach(button => {
+ button.addEventListener('click', () => {
+ const sourceKey = button.dataset.sourceKey || '';
+ const block = document.getElementById('confirmacion_extra_block-' + sourceKey);
+ if (!block) return;
+ block.classList.remove('d-none');
+ button.classList.add('d-none');
+ });
+ });
+
+ document.querySelectorAll('.js-remove-confirmacion-extra').forEach(button => {
+ button.addEventListener('click', () => {
+ const sourceKey = button.dataset.sourceKey || '';
+ const block = document.getElementById('confirmacion_extra_block-' + sourceKey);
+ const toggle = document.querySelector('.js-toggle-confirmacion-extra[data-source-key="' + sourceKey + '"]');
+ if (block) {
+ block.classList.add('d-none');
+ const productoExtra = document.getElementById('confirmacion_producto_extra-' + sourceKey);
+ const cantidadExtra = document.getElementById('confirmacion_cantidad_extra-' + sourceKey);
+ const precioExtra = document.getElementById('confirmacion_precio_extra-' + sourceKey);
+ if (productoExtra) productoExtra.value = '';
+ if (cantidadExtra) cantidadExtra.value = '';
+ if (precioExtra) precioExtra.value = '';
+ }
+ if (toggle) {
+ toggle.classList.remove('d-none');
+ }
+ });
+ });
+
+ document.querySelectorAll('.js-confirmacion-extra-block').forEach(block => {
+ const sourceKey = block.id.replace('confirmacion_extra_block-', '');
+ const productoExtra = document.getElementById('confirmacion_producto_extra-' + sourceKey);
+ const cantidadExtra = document.getElementById('confirmacion_cantidad_extra-' + sourceKey);
+ const precioExtra = document.getElementById('confirmacion_precio_extra-' + sourceKey);
+ const toggle = document.querySelector('.js-toggle-confirmacion-extra[data-source-key="' + sourceKey + '"]');
+ const hasValue = [productoExtra, cantidadExtra, precioExtra].some(input => (input?.value || '').trim() !== '');
+ if (hasValue) {
+ block.classList.remove('d-none');
+ if (toggle) toggle.classList.add('d-none');
+ }
});
});
@@ -1005,7 +1115,10 @@ function guardarGestion(sourceKey, trigger) {
precio: document.getElementById('precio-' + sourceKey)?.value || '',
confirmacion_producto: document.getElementById('confirmacion_producto-' + sourceKey)?.value || '',
confirmacion_cantidad: document.getElementById('confirmacion_cantidad-' + sourceKey)?.value || '',
- confirmacion_precio: document.getElementById('confirmacion_precio-' + sourceKey)?.value || ''
+ confirmacion_precio: document.getElementById('confirmacion_precio-' + sourceKey)?.value || '',
+ confirmacion_producto_extra: document.getElementById('confirmacion_producto_extra-' + sourceKey)?.value || '',
+ confirmacion_cantidad_extra: document.getElementById('confirmacion_cantidad_extra-' + sourceKey)?.value || '',
+ confirmacion_precio_extra: document.getElementById('confirmacion_precio_extra-' + sourceKey)?.value || ''
});
if (trigger) {
@@ -1218,6 +1331,10 @@ if (airDroidCopyButton) {
});
});
}
+
+window.setInterval(function () {
+ window.location.reload();
+}, 600000);
diff --git a/includes/callcenter_test_helpers.php b/includes/callcenter_test_helpers.php
index a05c343d..61835d1a 100644
--- a/includes/callcenter_test_helpers.php
+++ b/includes/callcenter_test_helpers.php
@@ -45,6 +45,9 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
`confirmacion_producto` VARCHAR(255) NULL,
`confirmacion_cantidad` VARCHAR(50) NULL,
`confirmacion_precio` VARCHAR(80) NULL,
+ `confirmacion_producto_extra` VARCHAR(255) NULL,
+ `confirmacion_cantidad_extra` VARCHAR(50) NULL,
+ `confirmacion_precio_extra` VARCHAR(80) NULL,
`proxima_llamada_at` DATETIME NULL,
`fecha_entrega_programada` DATE NULL,
`numero_cuenta_enviado_at` DATETIME NULL,
@@ -72,6 +75,9 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_producto', 'VARCHAR(255) NULL AFTER `precio`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_cantidad', 'VARCHAR(50) NULL AFTER `confirmacion_producto`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_precio', 'VARCHAR(80) NULL AFTER `confirmacion_cantidad`');
+ cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_producto_extra', 'VARCHAR(255) NULL AFTER `confirmacion_precio`');
+ cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_cantidad_extra', 'VARCHAR(50) NULL AFTER `confirmacion_producto_extra`');
+ cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_precio_extra', 'VARCHAR(80) NULL AFTER `confirmacion_cantidad_extra`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'proxima_llamada_at', 'DATETIME NULL AFTER `confirmacion_precio`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'fecha_entrega_programada', 'DATE NULL AFTER `proxima_llamada_at`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'numero_cuenta_enviado_at', 'DATETIME NULL AFTER `fecha_entrega_programada`');
@@ -80,6 +86,107 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
$checked = true;
}
+function cc_test_fetch_assessors(PDO $pdo, array $allowedNames = ['KARINA', 'ESTEFANYA']): array
+{
+ $stmt = $pdo->prepare("SELECT id, username, nombre_asesor FROM users WHERE role = 'Asesor'");
+ $stmt->execute();
+
+ $allowedLookup = array_fill_keys(array_map('strtoupper', $allowedNames), true);
+ $byKey = [];
+ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
+ $key = trim(mb_strtoupper((string) ($row['nombre_asesor'] ?: $row['username'] ?: '')));
+ $key = preg_replace('/\s+/', ' ', $key) ?? $key;
+ if ($key === '' || (!empty($allowedLookup) && !isset($allowedLookup[$key]))) {
+ continue;
+ }
+
+ $byKey[$key] = [
+ 'id' => (int) $row['id'],
+ 'label' => trim((string) ($row['nombre_asesor'] ?: $row['username'] ?: ('Asesor #' . (int) $row['id']))),
+ ];
+ }
+
+ return $byKey;
+}
+
+function cc_test_normalize_user_key(?string $value): string
+{
+ $value = trim((string) $value);
+ if ($value === '') {
+ return '';
+ }
+
+ $value = preg_replace('/\s+/', ' ', $value) ?? $value;
+
+ return mb_strtoupper($value);
+}
+
+function cc_test_allowed_module_user_keys(): array
+{
+ return ['KARINA', 'ESTEFANYA'];
+}
+
+function cc_test_is_allowed_module_user(?string $role = null, ?string $username = null, ?string $nombreAsesor = null): bool
+{
+ $role = trim((string) $role);
+ if (in_array($role, ['Administrador', 'admin'], true)) {
+ return true;
+ }
+
+ $allowedLookup = array_fill_keys(cc_test_allowed_module_user_keys(), true);
+ foreach ([$username, $nombreAsesor] as $candidate) {
+ $key = cc_test_normalize_user_key($candidate);
+ if ($key !== '' && isset($allowedLookup[$key])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function cc_test_current_user_can_access_module(?PDO $pdo = null): bool
+{
+ $role = $_SESSION['user_role'] ?? '';
+ $username = $_SESSION['username'] ?? '';
+ $nombreAsesor = null;
+
+ if ($pdo instanceof PDO && !empty($_SESSION['user_id']) && !in_array($role, ['Administrador', 'admin'], true)) {
+ try {
+ $stmt = $pdo->prepare('SELECT username, nombre_asesor FROM users WHERE id = ? LIMIT 1');
+ $stmt->execute([$_SESSION['user_id']]);
+ if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+ $username = $row['username'] ?? $username;
+ $nombreAsesor = $row['nombre_asesor'] ?? null;
+ }
+ } catch (Throwable $exception) {
+ // Fall back to the session values if the lookup fails.
+ }
+ }
+
+ return cc_test_is_allowed_module_user($role, $username, $nombreAsesor);
+}
+
+function cc_test_upsert_assignee(PDO $pdo, string $sourceKey, ?int $userId): void
+{
+ cc_test_ensure_tracking_table($pdo);
+
+ $stmt = $pdo->prepare("INSERT INTO callcenter_test_tracking (source_key, user_id, ultima_gestion_at)
+ VALUES (:source_key, :user_id, CURRENT_TIMESTAMP)
+ ON DUPLICATE KEY UPDATE
+ user_id = VALUES(user_id),
+ ultima_gestion_at = VALUES(ultima_gestion_at),
+ updated_at = CURRENT_TIMESTAMP");
+
+ $stmt->bindValue(':source_key', $sourceKey, PDO::PARAM_STR);
+ if ($userId === null) {
+ $stmt->bindValue(':user_id', null, PDO::PARAM_NULL);
+ } else {
+ $stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
+ }
+
+ $stmt->execute();
+}
+
function cc_test_ensure_historial_llamadas_table(PDO $pdo): void
{
static $checked = false;
@@ -127,9 +234,11 @@ function cc_test_valid_states(): array
];
}
-function cc_test_open_states(): array
-{
- return ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'];
+if (!function_exists('cc_test_open_states')) {
+ function cc_test_open_states(): array
+ {
+ return ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'];
+ }
}
function cc_test_confirmed_states(): array
@@ -137,9 +246,11 @@ function cc_test_confirmed_states(): array
return ['CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO'];
}
-function cc_test_closed_states(): array
-{
- return ['CANCELADO', 'REPETIDO'];
+if (!function_exists('cc_test_closed_states')) {
+ function cc_test_closed_states(): array
+ {
+ return ['CANCELADO', 'REPETIDO'];
+ }
}
function cc_test_requires_delivery_date(string $estado): bool
@@ -200,17 +311,19 @@ function cc_test_state_label(string $estado): string
};
}
-function cc_test_table_exists(PDO $pdo, string $tableName): bool
-{
- static $cache = [];
- if (array_key_exists($tableName, $cache)) {
+if (!function_exists('cc_test_table_exists')) {
+ function cc_test_table_exists(PDO $pdo, string $tableName): bool
+ {
+ static $cache = [];
+ if (array_key_exists($tableName, $cache)) {
+ return $cache[$tableName];
+ }
+
+ $stmt = $pdo->prepare('SHOW TABLES LIKE ?');
+ $stmt->execute([$tableName]);
+ $cache[$tableName] = (bool) $stmt->fetchColumn();
return $cache[$tableName];
}
-
- $stmt = $pdo->prepare('SHOW TABLES LIKE ?');
- $stmt->execute([$tableName]);
- $cache[$tableName] = (bool) $stmt->fetchColumn();
- return $cache[$tableName];
}
function cc_test_fetch_historial(PDO $pdo, string $pedidoId): array
diff --git a/includes/callcenter_test_management.php b/includes/callcenter_test_management.php
index ef7a75cc..80858ae8 100644
--- a/includes/callcenter_test_management.php
+++ b/includes/callcenter_test_management.php
@@ -1,4 +1,5 @@
prepare('SHOW TABLES LIKE ?');
+ $stmt->execute([$tableName]);
+ $cache[$tableName] = (bool) $stmt->fetchColumn();
- if (array_key_exists($tableName, $cache)) {
return $cache[$tableName];
}
-
- $stmt = $pdo->prepare('SHOW TABLES LIKE ?');
- $stmt->execute([$tableName]);
- $cache[$tableName] = (bool) $stmt->fetchColumn();
-
- return $cache[$tableName];
}
function cc_test_column_exists(PDO $pdo, string $tableName, string $columnName): bool
diff --git a/includes/drive_test_orders.php b/includes/drive_test_orders.php
index 0e28222a..d2b9ec1b 100644
--- a/includes/drive_test_orders.php
+++ b/includes/drive_test_orders.php
@@ -22,7 +22,7 @@ function drive_test_get_cell(array $row, array $indexes, array $aliases, string
return $default;
}
-function drive_test_fetch_orders(int $limit = 10): array
+function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552): array
{
$credentialsPath = __DIR__ . '/../google_credentials.json';
$spreadsheetId = '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c';
@@ -59,7 +59,16 @@ function drive_test_fetch_orders(int $limit = 10): array
}
$dataRows = array_slice($values, 1);
- $previewRows = array_reverse(array_slice($dataRows, -$limit));
+ $startIndex = max(0, $startRow - 2);
+ if ($startIndex > 0) {
+ $dataRows = array_slice($dataRows, $startIndex);
+ }
+
+ if ($limit > 0) {
+ $previewRows = array_reverse(array_slice($dataRows, -$limit));
+ } else {
+ $previewRows = array_reverse($dataRows);
+ }
$orders = [];
foreach ($previewRows as $row) {
@@ -122,7 +131,7 @@ function drive_test_fetch_tracking(PDO $pdo, array $sourceKeys): array
cc_test_ensure_tracking_table($pdo);
$placeholders = implode(',', array_fill(0, count($sourceKeys), '?'));
- $stmt = $pdo->prepare("SELECT source_key, estado, nota_seguimiento, direccion, referencia, agencia, sede_agencia, sede, ciudad, distrito, dni, observaciones, producto, cantidad, precio, confirmacion_producto, confirmacion_cantidad, confirmacion_precio, proxima_llamada_at, fecha_entrega_programada, numero_cuenta_enviado_at, ultima_gestion_at, updated_at FROM callcenter_test_tracking WHERE source_key IN ($placeholders)");
+ $stmt = $pdo->prepare("SELECT source_key, estado, nota_seguimiento, user_id, direccion, referencia, agencia, sede_agencia, sede, ciudad, distrito, dni, observaciones, producto, cantidad, precio, confirmacion_producto, confirmacion_cantidad, confirmacion_precio, proxima_llamada_at, fecha_entrega_programada, numero_cuenta_enviado_at, ultima_gestion_at, updated_at FROM callcenter_test_tracking WHERE source_key IN ($placeholders)");
$stmt->execute($sourceKeys);
$tracking = [];
@@ -141,6 +150,7 @@ function drive_test_merge_tracking(array $orders, array $tracking): array
$current = $tracking[$order['source_key']] ?? null;
$order['estado'] = $current['estado'] ?? 'POR LLAMAR';
$order['nota_seguimiento'] = $current['nota_seguimiento'] ?? '';
+ $order['user_id'] = array_key_exists('user_id', (array) $current) ? ($current['user_id'] !== null ? (int) $current['user_id'] : null) : null;
$order['seguimiento_actualizado'] = $current['updated_at'] ?? null;
$order['proxima_llamada_at'] = $current['proxima_llamada_at'] ?? null;
$order['fecha_entrega_programada'] = $current['fecha_entrega_programada'] ?? null;
diff --git a/layout_header.php b/layout_header.php
index d5a4b9a3..6d6ac51a 100644
--- a/layout_header.php
+++ b/layout_header.php
@@ -10,7 +10,29 @@ if (!isset($_SESSION['user_id'])) {
exit();
}
+require_once __DIR__ . '/db/config.php';
+require_once __DIR__ . '/includes/callcenter_test_helpers.php';
+
$userRole = $_SESSION['user_role'] ?? '';
+$currentUserId = (int) ($_SESSION['user_id'] ?? 0);
+$currentUserUsername = $_SESSION['username'] ?? '';
+$currentUserNombreAsesor = null;
+
+if ($currentUserId > 0 && !in_array($userRole, ['Administrador', 'admin'], true)) {
+ try {
+ $pdo = db();
+ $stmt = $pdo->prepare('SELECT username, nombre_asesor FROM users WHERE id = ? LIMIT 1');
+ $stmt->execute([$currentUserId]);
+ if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+ $currentUserUsername = $row['username'] ?? $currentUserUsername;
+ $currentUserNombreAsesor = $row['nombre_asesor'] ?? null;
+ }
+ } catch (Throwable $exception) {
+ // Use the session values as a fallback.
+ }
+}
+
+$canAccessTestModule = cc_test_is_allowed_module_user($userRole, $currentUserUsername, $currentUserNombreAsesor);
// Define navigation items for each role
$navItems = [
@@ -281,6 +303,12 @@ $navItems = [
'text' => 'Call Center (Mis Gestiones)',
'roles' => ['Administrador', 'admin', 'Asesor']
],
+ 'call_center_pro' => [
+ 'url' => 'call_center_pro.php',
+ 'icon' => 'fa-headset',
+ 'text' => 'Call Center Pro',
+ 'roles' => ['Administrador', 'admin', 'Asesor']
+ ],
'simular_asignacion' => [
'url' => 'simular_asignacion.php',
'icon' => 'fa-magic',
@@ -352,7 +380,12 @@ $navItems = [
$currentPage = basename($_SERVER['PHP_SELF']);
foreach ($navItems as $key => $item):
- if (in_array($userRole, $item['roles'])):
+ $isTestModule = ($key === 'modulo_pruebas_group');
+ if ($isTestModule && !$canAccessTestModule) {
+ continue;
+ }
+
+ if ($isTestModule || in_array($userRole, $item['roles'])):
if (isset($item['submenu'])):
$isSubmenuActive = false;
foreach ($item['submenu'] as $sub_item) {
@@ -366,7 +399,7 @@ $navItems = [
";
+if (!cc_test_current_user_can_access_module(db())) {
+ http_response_code(403);
+ require_once __DIR__ . '/layout_header.php';
+ echo "
";
require_once __DIR__ . '/layout_footer.php';
exit();
}
+require_once __DIR__ . '/layout_header.php';
require_once __DIR__ . '/vendor/autoload.php';
$credentialsPath = __DIR__ . '/google_credentials.json';
diff --git a/update_callcenter_test_tracking.php b/update_callcenter_test_tracking.php
index 7dbe1855..824173a3 100644
--- a/update_callcenter_test_tracking.php
+++ b/update_callcenter_test_tracking.php
@@ -5,7 +5,7 @@ require_once 'includes/callcenter_test_helpers.php';
header('Content-Type: application/json; charset=utf-8');
-if (!isset($_SESSION['user_id'])) {
+if (!isset($_SESSION['user_id']) || !cc_test_current_user_can_access_module(db())) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'No autorizado']);
exit;
@@ -64,6 +64,9 @@ try {
$confirmacionProducto = cc_test_normalize_nullable_text('confirmacion_producto', 255);
$confirmacionCantidad = cc_test_normalize_nullable_text('confirmacion_cantidad', 50);
$confirmacionPrecio = cc_test_normalize_nullable_text('confirmacion_precio', 80);
+ $confirmacionProductoExtra = cc_test_normalize_nullable_text('confirmacion_producto_extra', 255);
+ $confirmacionCantidadExtra = cc_test_normalize_nullable_text('confirmacion_cantidad_extra', 50);
+ $confirmacionPrecioExtra = cc_test_normalize_nullable_text('confirmacion_precio_extra', 80);
$pdo = db();
cc_test_ensure_tracking_table($pdo);
@@ -133,6 +136,9 @@ try {
confirmacion_producto,
confirmacion_cantidad,
confirmacion_precio,
+ confirmacion_producto_extra,
+ confirmacion_cantidad_extra,
+ confirmacion_precio_extra,
proxima_llamada_at,
fecha_entrega_programada,
numero_cuenta_enviado_at,
@@ -157,6 +163,9 @@ try {
:confirmacion_producto,
:confirmacion_cantidad,
:confirmacion_precio,
+ :confirmacion_producto_extra,
+ :confirmacion_cantidad_extra,
+ :confirmacion_precio_extra,
:proxima_llamada_at,
:fecha_entrega_programada,
:numero_cuenta_enviado_at,
@@ -181,6 +190,9 @@ try {
confirmacion_producto = VALUES(confirmacion_producto),
confirmacion_cantidad = VALUES(confirmacion_cantidad),
confirmacion_precio = VALUES(confirmacion_precio),
+ confirmacion_producto_extra = VALUES(confirmacion_producto_extra),
+ confirmacion_cantidad_extra = VALUES(confirmacion_cantidad_extra),
+ confirmacion_precio_extra = VALUES(confirmacion_precio_extra),
proxima_llamada_at = VALUES(proxima_llamada_at),
fecha_entrega_programada = VALUES(fecha_entrega_programada),
numero_cuenta_enviado_at = VALUES(numero_cuenta_enviado_at),
@@ -208,6 +220,9 @@ try {
':confirmacion_producto' => $confirmacionProducto,
':confirmacion_cantidad' => $confirmacionCantidad,
':confirmacion_precio' => $confirmacionPrecio,
+ ':confirmacion_producto_extra' => $confirmacionProductoExtra,
+ ':confirmacion_cantidad_extra' => $confirmacionCantidadExtra,
+ ':confirmacion_precio_extra' => $confirmacionPrecioExtra,
':proxima_llamada_at' => $proximaLlamada,
':fecha_entrega_programada' => $fechaEntrega,
':numero_cuenta_enviado_at' => $numeroCuentaEnviadoAt,