@@ -692,7 +859,7 @@ require_once 'layout_header.php';
Drive detectado: filas
Agregados:
-
TUANI: distribución desde fila · último procesado Extracción: desde fila
+
: distribución desde fila · último procesado Extracción: desde fila
Auto actualización: cada 10 min
Ver vista previa Drive
@@ -882,22 +1049,45 @@ require_once 'layout_header.php';
pedidos en esta bandeja
+
+
+
Si un pedido ya está tomado por otra asesora, el sistema lo deja igual y te avisa.
+
-
+
+ Sel.
+ Asignación / acciones
N° Pedido
Cliente
Ubicación editable
Pedido
Gestión
- Acciones
-
+
No hay pedidos en esta bandeja por ahora.
@@ -950,6 +1140,123 @@ require_once 'layout_header.php';
ob_start();
?>
+
+
+
+
+
+
+
+
+
+
+ 0 ? (int) $orderUserId : null) : null;
+ $unassigned = $orderUserIdNorm === null;
+ $assignedAssessorKey = null;
+ if (!$unassigned) {
+ foreach ($assessors as $assessorKey => $assessor) {
+ if ((int) ($assessor['id'] ?? 0) === (int) $orderUserIdNorm) {
+ $assignedAssessorKey = (string) $assessorKey;
+ break;
+ }
+ }
+ }
+
+ $assignFormClass = 'border rounded-3 p-1 text-start shadow-sm cc-callcenter-assign-form';
+ if ($unassigned) {
+ $assignFormClass .= ' bg-light';
+ } elseif ($assignedAssessorKey === 'KARINA') {
+ $assignFormClass .= ' bg-primary-subtle border-primary';
+ } elseif ($assignedAssessorKey === 'ESTEFANYA') {
+ $assignFormClass .= ' bg-success-subtle border-success';
+ } elseif ($assignedAssessorKey === 'CARMEN') {
+ $assignFormClass .= ' bg-warning-subtle border-warning';
+ } else {
+ $assignFormClass .= ' bg-secondary-subtle border-secondary';
+ }
+
+ if ($unassigned) {
+ $selectedAssessorKeyForSelect = !empty($recommendedAssessorKey) ? (string) $recommendedAssessorKey : '';
+ } else {
+ $selectedAssessorKeyForSelect = $assignedAssessorKey !== null ? (string) $assignedAssessorKey : '';
+ }
+
+ $buttonLabel = $unassigned
+ ? 'Asignar pedido'
+ : ($assignedAssessorKey !== null ? 'Guardar asignación' : 'Desasignar');
+ ?>
+
+
+
+
+
+
+ Llamar / Gestionar
+
+
+
Sin teléfono
+
+
+
+ WhatsApp
+
+
+
+ Gestionar
+
+
+
@@ -1015,106 +1322,6 @@ require_once 'layout_header.php';
Última gestión:
Nota:
-
-
-
- 0 ? (int) $orderUserId : null) : null;
- $unassigned = $orderUserIdNorm === null;
- $assignedAssessorKey = null;
- if (!$unassigned) {
- foreach ($assessors as $assessorKey => $assessor) {
- if ((int) ($assessor['id'] ?? 0) === (int) $orderUserIdNorm) {
- $assignedAssessorKey = (string) $assessorKey;
- break;
- }
- }
- }
-
- $assignFormClass = 'border rounded-3 p-2 text-start shadow-sm';
- if ($unassigned) {
- $assignFormClass .= ' bg-light';
- } elseif ($assignedAssessorKey === 'KARINA') {
- $assignFormClass .= ' bg-primary-subtle border-primary';
- } elseif ($assignedAssessorKey === 'ESTEFANYA') {
- $assignFormClass .= ' bg-success-subtle border-success';
- } elseif ($assignedAssessorKey === 'CARMEN') {
- $assignFormClass .= ' bg-warning-subtle border-warning';
- } else {
- $assignFormClass .= ' bg-secondary-subtle border-secondary';
- }
-
- if ($unassigned) {
- $selectedAssessorKeyForSelect = !empty($recommendedAssessorKey) ? (string) $recommendedAssessorKey : '';
- } else {
- $selectedAssessorKeyForSelect = $assignedAssessorKey !== null ? (string) $assignedAssessorKey : '';
- }
-
- $buttonLabel = $unassigned
- ? 'Asignar pedido'
- : ($assignedAssessorKey !== null ? 'Guardar asignación' : 'Desasignar');
- ?>
-
-
-
-
-
- Llamar / Gestionar
-
-
-
Sin teléfono
-
-
-
- WhatsApp
-
-
-
- Gestionar
-
-
-
@@ -2238,6 +2445,142 @@ if (airDroidCopyButton) {
});
}
+const bulkAssignForm = document.getElementById('bulkAssignForm');
+const bulkAssignSourceKeys = document.getElementById('bulkAssignSourceKeys');
+const bulkSelectedCount = document.getElementById('bulkSelectedCount');
+const bulkAssignSubmit = document.getElementById('bulkAssignSubmit');
+const toggleSelectAllOrders = document.getElementById('toggleSelectAllOrders');
+const clearSelectedOrders = document.getElementById('clearSelectedOrders');
+const bulkOrderCheckboxes = Array.from(document.querySelectorAll('.js-bulk-order-checkbox'));
+const quickSelectButtons = Array.from(document.querySelectorAll('.js-quick-select-btn'));
+const bulkAssessorSelect = bulkAssignForm ? bulkAssignForm.querySelector('select[name="target_assessor"]') : null;
+
+function getSelectedBulkOrderCheckboxes() {
+ return bulkOrderCheckboxes.filter(function (checkbox) {
+ return checkbox.checked;
+ });
+}
+
+function setQuickSelectButtonState(button, selected) {
+ if (!button) {
+ return;
+ }
+
+ button.classList.toggle('btn-primary', selected);
+ button.classList.toggle('btn-outline-primary', !selected);
+ button.setAttribute('aria-pressed', selected ? 'true' : 'false');
+ button.setAttribute('title', selected ? 'Quitar de la selección rápida' : 'Seleccionar este pedido para asignación rápida');
+ button.innerHTML = selected ? ' ' : ' ';
+}
+
+function syncBulkAssignSelectionUi() {
+ const selectedCheckboxes = getSelectedBulkOrderCheckboxes();
+
+ if (bulkSelectedCount) {
+ bulkSelectedCount.textContent = String(selectedCheckboxes.length);
+ }
+
+ if (bulkAssignSourceKeys) {
+ bulkAssignSourceKeys.innerHTML = '';
+ selectedCheckboxes.forEach(function (checkbox) {
+ const input = document.createElement('input');
+ input.type = 'hidden';
+ input.name = 'source_keys[]';
+ input.value = checkbox.value;
+ bulkAssignSourceKeys.appendChild(input);
+ });
+ }
+
+ bulkOrderCheckboxes.forEach(function (checkbox) {
+ const row = checkbox.closest('tr');
+ const button = checkbox.parentElement ? checkbox.parentElement.querySelector('.js-quick-select-btn') : null;
+ if (row) {
+ row.classList.toggle('is-selected', checkbox.checked);
+ }
+ setQuickSelectButtonState(button, checkbox.checked);
+ });
+
+ const anySelected = selectedCheckboxes.length > 0;
+ const canSubmit = anySelected && !!bulkAssessorSelect && !bulkAssessorSelect.disabled && bulkAssessorSelect.value !== '';
+ if (bulkAssignSubmit) {
+ bulkAssignSubmit.disabled = !canSubmit;
+ }
+ if (clearSelectedOrders) {
+ clearSelectedOrders.disabled = !anySelected;
+ }
+ if (toggleSelectAllOrders) {
+ const allSelected = bulkOrderCheckboxes.length > 0 && selectedCheckboxes.length === bulkOrderCheckboxes.length;
+ toggleSelectAllOrders.textContent = allSelected ? 'Desmarcar visibles' : 'Marcar visibles';
+ toggleSelectAllOrders.disabled = bulkOrderCheckboxes.length === 0;
+ }
+}
+
+quickSelectButtons.forEach(function (button) {
+ button.addEventListener('click', function () {
+ const sourceKey = this.dataset.sourceKey || '';
+ const checkbox = document.querySelector('.js-bulk-order-checkbox[data-source-key="' + sourceKey + '"]');
+ if (!checkbox) {
+ return;
+ }
+
+ checkbox.checked = !checkbox.checked;
+ syncBulkAssignSelectionUi();
+ });
+});
+
+bulkOrderCheckboxes.forEach(function (checkbox) {
+ checkbox.addEventListener('change', syncBulkAssignSelectionUi);
+});
+
+if (bulkAssessorSelect) {
+ bulkAssessorSelect.addEventListener('change', syncBulkAssignSelectionUi);
+}
+
+if (toggleSelectAllOrders) {
+ toggleSelectAllOrders.addEventListener('click', function () {
+ const shouldSelectAll = bulkOrderCheckboxes.some(function (checkbox) {
+ return !checkbox.checked;
+ });
+
+ bulkOrderCheckboxes.forEach(function (checkbox) {
+ checkbox.checked = shouldSelectAll;
+ });
+
+ syncBulkAssignSelectionUi();
+ });
+}
+
+if (clearSelectedOrders) {
+ clearSelectedOrders.addEventListener('click', function () {
+ bulkOrderCheckboxes.forEach(function (checkbox) {
+ checkbox.checked = false;
+ });
+
+ syncBulkAssignSelectionUi();
+ });
+}
+
+if (bulkAssignForm) {
+ bulkAssignForm.addEventListener('submit', function (event) {
+ const selectedCheckboxes = getSelectedBulkOrderCheckboxes();
+ const assessorSelect = this.querySelector('select[name="target_assessor"]');
+
+ if (selectedCheckboxes.length === 0) {
+ event.preventDefault();
+ alert('Primero selecciona al menos un pedido.');
+ return;
+ }
+
+ if (!assessorSelect || !assessorSelect.value) {
+ event.preventDefault();
+ alert('Elige la asesora para hacer la asignación rápida.');
+ return;
+ }
+ });
+
+ syncBulkAssignSelectionUi();
+}
+
window.setInterval(function () {
window.location.reload();
}, 600000);
diff --git a/includes/callcenter_test_helpers.php b/includes/callcenter_test_helpers.php
index 920f8ff0..76b98629 100644
--- a/includes/callcenter_test_helpers.php
+++ b/includes/callcenter_test_helpers.php
@@ -30,6 +30,7 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
`estado` VARCHAR(40) NOT NULL DEFAULT 'POR LLAMAR',
`nota_seguimiento` TEXT NULL,
`user_id` INT NULL,
+ `assigned_at` DATETIME NULL,
`direccion` TEXT NULL,
`referencia` TEXT NULL,
`agencia` VARCHAR(80) NULL,
@@ -78,6 +79,7 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
KEY `idx_callcenter_test_tracking_proxima` (`proxima_llamada_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
+ cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'assigned_at', 'DATETIME NULL AFTER `user_id`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'direccion', 'TEXT NULL AFTER `user_id`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'referencia', 'TEXT NULL AFTER `direccion`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'agencia', 'VARCHAR(80) NULL AFTER `referencia`');
@@ -119,6 +121,11 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'pedido_rotulado_subido_por', 'INT NULL AFTER `pedido_rotulado_subido_at`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ultima_gestion_at', 'DATETIME NULL AFTER `ruta_contraentrega_subido_por`');
+ $stmtAssignedBackfill = $pdo->query("SELECT id FROM callcenter_test_tracking WHERE assigned_at IS NULL AND user_id IS NOT NULL LIMIT 1");
+ if ($stmtAssignedBackfill && $stmtAssignedBackfill->fetchColumn()) {
+ $pdo->exec("UPDATE callcenter_test_tracking SET assigned_at = created_at, updated_at = updated_at WHERE assigned_at IS NULL AND user_id IS NOT NULL");
+ }
+
$checked = true;
}
@@ -215,18 +222,27 @@ function cc_test_upsert_assignee(PDO $pdo, string $sourceKey, ?int $userId): voi
{
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)
+ $assignedAt = $userId === null ? null : (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
+
+ $stmt = $pdo->prepare("INSERT INTO callcenter_test_tracking (source_key, user_id, assigned_at, ultima_gestion_at)
+ VALUES (:source_key, :user_id, :assigned_at, CURRENT_TIMESTAMP)
ON DUPLICATE KEY UPDATE
user_id = VALUES(user_id),
+ assigned_at = CASE
+ WHEN VALUES(user_id) IS NULL THEN NULL
+ WHEN assigned_at IS NULL OR user_id IS NULL OR user_id <> VALUES(user_id) THEN VALUES(assigned_at)
+ ELSE assigned_at
+ END,
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);
+ $stmt->bindValue(':assigned_at', null, PDO::PARAM_NULL);
} else {
$stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
+ $stmt->bindValue(':assigned_at', $assignedAt, PDO::PARAM_STR);
}
$stmt->execute();
diff --git a/includes/drive_test_orders.php b/includes/drive_test_orders.php
index c6982bfc..348d9a8b 100644
--- a/includes/drive_test_orders.php
+++ b/includes/drive_test_orders.php
@@ -18,8 +18,10 @@ function drive_test_available_stores(): array
'label' => 'Flower',
'spreadsheet_id' => '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c',
'sheet_gid' => null,
- 'startRow' => 5552,
- 'enforce_db_start_row' => false,
+ 'startRow' => 7796, // Pedido #37431
+ 'min_codigo_number' => 37431,
+ 'enforce_db_start_row' => true,
+ 'incremental_sync' => true,
],
// Segunda tienda (nuevo link que pasaste).
'otra_tienda' => [
@@ -28,6 +30,7 @@ function drive_test_available_stores(): array
'sheet_gid' => 1523126328,
'startRow' => 6804,
'enforce_db_start_row' => true,
+ 'incremental_sync' => true,
],
];
}
@@ -94,6 +97,16 @@ function drive_test_get_cell(array $row, array $indexes, array $aliases, string
return $default;
}
+function drive_test_extract_codigo_number(?string $codigo): ?int
+{
+ $digits = preg_replace('/\D+/', '', trim((string) $codigo));
+ if ($digits === '') {
+ return null;
+ }
+
+ return (int) $digits;
+}
+
function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $storeKey = 'flower'): array
{
$storeConfig = drive_test_get_store_config($storeKey);
@@ -157,6 +170,7 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $
$previewRows = array_reverse($previewRows, true);
$orders = [];
+ $minCodigoNumber = isset($storeConfig['min_codigo_number']) ? (int) ($storeConfig['min_codigo_number'] ?? 0) : 0;
foreach ($previewRows as $rowIndex => $row) {
$codigo = trim((string) ($row[0] ?? ''));
@@ -177,6 +191,11 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $
$referencia = drive_test_get_cell($row, $headerIndexes, ['REFERENCIA']);
$distrito = drive_test_get_cell($row, $headerIndexes, ['DISTRITO']);
$sourceRow = (int) $rowIndex + 2;
+ $codigoNumber = drive_test_extract_codigo_number($codigo);
+
+ if ($minCodigoNumber > 0 && ($codigoNumber === null || $codigoNumber < $minCodigoNumber)) {
+ continue;
+ }
// Importante: para 'flower' mantenemos EXACTAMENTE el algoritmo anterior
// para no romper el historial/tracking ya existente.
@@ -229,7 +248,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, user_id, direccion, referencia, agencia, sede_agencia, sede, ciudad, distrito, dni, numero_cuenta_sede_id, numero_cuenta_dni, observaciones, coordenadas, producto, cantidad, precio, monto_adelantado, 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, promo_final_evidencia_path, promo_final_evidencia_subido_at, promo_final_evidencia_subido_por, cancelado_evidencia_path, cancelado_evidencia_subido_at, cancelado_evidencia_subido_por, eliminado_at, eliminado_por, ruta_contraentrega_pedido_id, ruta_contraentrega_subido_at, ruta_contraentrega_subido_por, pedido_rotulado_pedido_id, pedido_rotulado_subido_at, pedido_rotulado_subido_por, 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, assigned_at, direccion, referencia, agencia, sede_agencia, sede, ciudad, distrito, dni, numero_cuenta_sede_id, numero_cuenta_dni, observaciones, coordenadas, producto, cantidad, precio, monto_adelantado, 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, promo_final_evidencia_path, promo_final_evidencia_subido_at, promo_final_evidencia_subido_por, cancelado_evidencia_path, cancelado_evidencia_subido_at, cancelado_evidencia_subido_por, eliminado_at, eliminado_por, ruta_contraentrega_pedido_id, ruta_contraentrega_subido_at, ruta_contraentrega_subido_por, pedido_rotulado_pedido_id, pedido_rotulado_subido_at, pedido_rotulado_subido_por, ultima_gestion_at, updated_at FROM callcenter_test_tracking WHERE source_key IN ($placeholders)");
$stmt->execute($sourceKeys);
$tracking = [];
@@ -251,6 +270,7 @@ function drive_test_merge_tracking(array $orders, array $tracking): array
$userId = array_key_exists('user_id', (array) $current) ? $current['user_id'] : null;
$order['user_id'] = $userId !== null ? ((int) $userId > 0 ? (int) $userId : null) : null;
$order['seguimiento_actualizado'] = $current['updated_at'] ?? null;
+ $order['assigned_at'] = $current['assigned_at'] ?? null;
$order['proxima_llamada_at'] = $current['proxima_llamada_at'] ?? null;
$order['fecha_entrega_programada'] = $current['fecha_entrega_programada'] ?? null;
$order['numero_cuenta_enviado_at'] = $current['numero_cuenta_enviado_at'] ?? null;
@@ -628,12 +648,21 @@ function drive_test_fetch_orders_from_db(PDO $pdo, string $storeKey, ?bool $only
$stmt->execute($params);
$orders = [];
+ $minCodigoNumber = isset($storeConfig['min_codigo_number']) ? (int) ($storeConfig['min_codigo_number'] ?? 0) : 0;
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
+ $isAgregado = (int) ($row['is_agregado'] ?? 0);
+ $codigo = (string) ($row['codigo'] ?? '');
+ $codigoNumber = drive_test_extract_codigo_number($codigo);
+
+ if ($minCodigoNumber > 0 && $isAgregado !== 1 && ($codigoNumber === null || $codigoNumber < $minCodigoNumber)) {
+ continue;
+ }
+
$orders[] = [
'id' => (int) ($row['id'] ?? 0),
- 'is_agregado' => (int) ($row['is_agregado'] ?? 0),
+ 'is_agregado' => $isAgregado,
'source_key' => (string) ($row['source_key'] ?? ''),
- 'codigo' => (string) ($row['codigo'] ?? ''),
+ 'codigo' => $codigo,
'import_id' => (string) ($row['import_id'] ?? ''),
'source_row' => isset($row['source_row']) && (int) $row['source_row'] > 0 ? (int) $row['source_row'] : null,
'drive_imported_at' => (string) ($row['drive_imported_at'] ?? ''),
diff --git a/ruta_contraentrega.php b/ruta_contraentrega.php
index 68e56896..65522d09 100644
--- a/ruta_contraentrega.php
+++ b/ruta_contraentrega.php
@@ -88,6 +88,7 @@ ensureTipoPaqueteEnumDefinition($pdo);
$user_id = $_SESSION['user_id'];
$user_role = $_SESSION['user_role'] ?? 'Asesor';
+$is_admin = in_array($user_role, ['Administrador', 'admin'], true);
// Fetch years for the filter
$years_query = "SELECT DISTINCT YEAR(created_at) as year FROM pedidos";
@@ -337,17 +338,19 @@ include 'layout_header.php';
AGREGAR PEDIDOS CONTRAENTREGA
- $search_query,
- ], static function ($value) {
- return $value !== '' && $value !== null;
- });
- $excelUrl = 'download_ruta_contraentrega.php' . (!empty($excelParams) ? '?' . http_build_query($excelParams) : '');
- ?>
-
- Descargar Excel (Mañana)
-
+
+ $search_query,
+ ], static function ($value) {
+ return $value !== '' && $value !== null;
+ });
+ $excelUrl = 'download_ruta_contraentrega.php' . (!empty($excelParams) ? '?' . http_build_query($excelParams) : '');
+ ?>
+
+ Descargar Excel (Mañana)
+
+