Autosave: 20260707-083540
This commit is contained in:
parent
17c381597c
commit
c4432f5693
@ -9,8 +9,8 @@ $provinciasPorDepartamentoContraentrega = contraentregaProvinciasPorDepartamento
|
|||||||
$distritosPorProvinciaContraentrega = contraentregaDistritosPorProvincia();
|
$distritosPorProvinciaContraentrega = contraentregaDistritosPorProvincia();
|
||||||
$departamentosContraentrega = array_keys($provinciasPorDepartamentoContraentrega);
|
$departamentosContraentrega = array_keys($provinciasPorDepartamentoContraentrega);
|
||||||
|
|
||||||
$pageTitle = 'Call Center Pro | Bandeja de la asesora';
|
$pageTitle = 'Pedidos Asignados | Bandeja de la asesora';
|
||||||
$pageDescription = 'Replica de la bandeja de Call Center Pro para que cada asesora trabaje desde Call Center Pro con la misma interfaz, estados, historial y acciones sobre sus pedidos asignados.';
|
$pageDescription = 'Bandeja para que cada asesora gestione sus pedidos asignados con la misma interfaz, estados, historial y acciones.';
|
||||||
|
|
||||||
if (!cc_test_current_user_can_access_module(db())) {
|
if (!cc_test_current_user_can_access_module(db())) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
@ -129,6 +129,14 @@ if (!isset($allowedViews[$view])) {
|
|||||||
$view = 'pendientes_hoy';
|
$view = 'pendientes_hoy';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$storeKey = trim((string) ($_GET['store'] ?? 'flower'));
|
||||||
|
$storeKey = mb_strtolower($storeKey);
|
||||||
|
$availableStores = drive_test_available_stores();
|
||||||
|
if (!isset($availableStores[$storeKey])) {
|
||||||
|
$storeKey = 'flower';
|
||||||
|
}
|
||||||
|
$storeConfig = $availableStores[$storeKey];
|
||||||
|
|
||||||
$errorMessage = null;
|
$errorMessage = null;
|
||||||
$noticeMessage = null;
|
$noticeMessage = null;
|
||||||
$assessors = [];
|
$assessors = [];
|
||||||
@ -136,8 +144,8 @@ $orders = [];
|
|||||||
$visibleOrders = [];
|
$visibleOrders = [];
|
||||||
$modalsHtml = [];
|
$modalsHtml = [];
|
||||||
$totalRows = 0;
|
$totalRows = 0;
|
||||||
$loadLimit = 10;
|
$loadLimit = 20;
|
||||||
$startRow = 5552;
|
$startRow = (int) ($storeConfig['startRow'] ?? 5552);
|
||||||
$catalogoProductos = [];
|
$catalogoProductos = [];
|
||||||
$stats = [
|
$stats = [
|
||||||
'total' => 0,
|
'total' => 0,
|
||||||
@ -162,7 +170,7 @@ try {
|
|||||||
$catalogoProductos = [];
|
$catalogoProductos = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$preview = drive_test_fetch_orders($loadLimit, $startRow);
|
$preview = drive_test_fetch_orders($loadLimit, $startRow, $storeKey);
|
||||||
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
||||||
$orders = $preview['orders'] ?? [];
|
$orders = $preview['orders'] ?? [];
|
||||||
|
|
||||||
@ -287,17 +295,27 @@ require_once 'layout_header.php';
|
|||||||
<section class="mb-4">
|
<section class="mb-4">
|
||||||
<div class="d-flex flex-column flex-xl-row justify-content-between align-items-xl-center gap-3">
|
<div class="d-flex flex-column flex-xl-row justify-content-between align-items-xl-center gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h2 fw-bold mb-1"><i class="bi bi-headset text-primary"></i> Call Center Pro</h1>
|
<h1 class="h2 fw-bold mb-1"><i class="bi bi-headset text-primary"></i> Pedidos Asignados</h1>
|
||||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||||
<a href="gestiones_callcenter.php" class="btn btn-sm <?php echo $view !== 'todos' ? 'btn-primary' : 'btn-outline-primary'; ?>">Bandeja principal</a>
|
<?php if (in_array((string) ($_SESSION['user_role'] ?? ''), ['Administrador', 'admin'], true)): ?>
|
||||||
<a href="call_center_pro.php" class="btn btn-sm btn-outline-dark">Call Center Pro</a>
|
<a href="gestiones_callcenter.php?view=<?php echo urlencode($view); ?>&store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-sm btn-outline-primary">Base de Datos Pedidos</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<a href="call_center_pro.php?view=<?php echo urlencode($view); ?>&store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-sm btn-primary">Pedidos Asignados</a>
|
||||||
</div>
|
</div>
|
||||||
|
<ul class="nav nav-pills mt-3" role="tablist" aria-label="Selector de tienda">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?php echo $storeKey === 'flower' ? 'active' : ''; ?>" href="?view=<?php echo htmlspecialchars($view); ?>&store=flower">Flower</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?php echo $storeKey === 'otra_tienda' ? 'active' : ''; ?>" href="?view=<?php echo htmlspecialchars($view); ?>&store=otra_tienda">TUANI</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Drive detectado: <?php echo (int) $totalRows; ?> filas</span>
|
<span class="badge rounded-pill text-bg-light border px-3 py-2">Drive detectado: <?php echo (int) $totalRows; ?> filas</span>
|
||||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Extracción: desde fila <?php echo (int) $startRow; ?></span>
|
<span class="badge rounded-pill text-bg-light border px-3 py-2">Extracción: desde fila <?php echo (int) $startRow; ?></span>
|
||||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Auto actualización: cada 10 min</span>
|
<span class="badge rounded-pill text-bg-light border px-3 py-2">Auto actualización: cada 10 min</span>
|
||||||
<a href="test_importar_drive.php" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
|
<a href="test_importar_drive.php?store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -315,7 +333,7 @@ require_once 'layout_header.php';
|
|||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<section class="row g-3 mb-4">
|
<section class="row g-3 mb-4">
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=pendientes_hoy" class="text-decoration-none">
|
<a href="?view=pendientes_hoy&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'pendientes_hoy' ? 'bg-dark text-white' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'pendientes_hoy' ? 'bg-dark text-white' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase opacity-75 mb-2">Pendientes de hoy</div>
|
<div class="small text-uppercase opacity-75 mb-2">Pendientes de hoy</div>
|
||||||
@ -325,7 +343,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=nuevos_hoy" class="text-decoration-none">
|
<a href="?view=nuevos_hoy&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'nuevos_hoy' ? 'bg-primary-subtle border border-primary' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'nuevos_hoy' ? 'bg-primary-subtle border border-primary' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Nuevos de hoy</div>
|
<div class="small text-uppercase text-muted mb-2">Nuevos de hoy</div>
|
||||||
@ -335,7 +353,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=confirmados" class="text-decoration-none">
|
<a href="?view=confirmados&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'confirmados' ? 'bg-success-subtle border border-success' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'confirmados' ? 'bg-success-subtle border border-success' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Confirmados</div>
|
<div class="small text-uppercase text-muted mb-2">Confirmados</div>
|
||||||
@ -345,7 +363,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=seguimiento" class="text-decoration-none">
|
<a href="?view=seguimiento&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'seguimiento' ? 'bg-primary-subtle border border-primary' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'seguimiento' ? 'bg-primary-subtle border border-primary' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Seguimiento</div>
|
<div class="small text-uppercase text-muted mb-2">Seguimiento</div>
|
||||||
@ -355,7 +373,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=observados" class="text-decoration-none">
|
<a href="?view=observados&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'observados' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'observados' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Observados</div>
|
<div class="small text-uppercase text-muted mb-2">Observados</div>
|
||||||
@ -365,7 +383,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=cerrados" class="text-decoration-none">
|
<a href="?view=cerrados&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'cerrados' ? 'bg-secondary-subtle border border-secondary' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'cerrados' ? 'bg-secondary-subtle border border-secondary' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Cerrados</div>
|
<div class="small text-uppercase text-muted mb-2">Cerrados</div>
|
||||||
@ -375,7 +393,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=todos" class="text-decoration-none">
|
<a href="?view=todos&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'todos' ? 'bg-light border' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'todos' ? 'bg-light border' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Todos</div>
|
<div class="small text-uppercase text-muted mb-2">Todos</div>
|
||||||
|
|||||||
@ -9,10 +9,11 @@ $provinciasPorDepartamentoContraentrega = contraentregaProvinciasPorDepartamento
|
|||||||
$distritosPorProvinciaContraentrega = contraentregaDistritosPorProvincia();
|
$distritosPorProvinciaContraentrega = contraentregaDistritosPorProvincia();
|
||||||
$departamentosContraentrega = array_keys($provinciasPorDepartamentoContraentrega);
|
$departamentosContraentrega = array_keys($provinciasPorDepartamentoContraentrega);
|
||||||
|
|
||||||
$pageTitle = 'Call Center de Prueba | Bandejas de gestión';
|
$pageTitle = 'Base de Datos Pedidos | Asignación';
|
||||||
$pageDescription = 'Bandejas diarias de Call Center con estados reales, próxima llamada, fecha de entrega, historial y edición de datos del pedido importado desde Drive.';
|
$pageDescription = 'Bandejas de asignación y gestión de estados para el Call Center, cargadas desde Drive.';
|
||||||
|
|
||||||
if (!cc_test_current_user_can_access_module(db())) {
|
$role = (string) ($_SESSION['user_role'] ?? '');
|
||||||
|
if (!in_array($role, ['Administrador', 'admin'], true)) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
require_once 'layout_header.php';
|
require_once 'layout_header.php';
|
||||||
echo "<div class='container py-5'><div class='alert alert-danger mb-0'>Acceso denegado.</div></div>";
|
echo "<div class='container py-5'><div class='alert alert-danger mb-0'>Acceso denegado.</div></div>";
|
||||||
@ -128,6 +129,14 @@ if (!isset($allowedViews[$view])) {
|
|||||||
$view = 'pendientes_hoy';
|
$view = 'pendientes_hoy';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$storeKey = trim((string) ($_GET['store'] ?? 'flower'));
|
||||||
|
$storeKey = mb_strtolower($storeKey);
|
||||||
|
$availableStores = drive_test_available_stores();
|
||||||
|
if (!isset($availableStores[$storeKey])) {
|
||||||
|
$storeKey = 'flower';
|
||||||
|
}
|
||||||
|
$storeConfig = $availableStores[$storeKey];
|
||||||
|
|
||||||
$errorMessage = null;
|
$errorMessage = null;
|
||||||
$noticeMessage = null;
|
$noticeMessage = null;
|
||||||
$assessors = [];
|
$assessors = [];
|
||||||
@ -135,8 +144,13 @@ $orders = [];
|
|||||||
$visibleOrders = [];
|
$visibleOrders = [];
|
||||||
$modalsHtml = [];
|
$modalsHtml = [];
|
||||||
$totalRows = 0;
|
$totalRows = 0;
|
||||||
$loadLimit = 10;
|
$loadLimit = 20;
|
||||||
$startRow = 5552;
|
|
||||||
|
$cupoObjetivoPorAsesora = 10;
|
||||||
|
$advisorOpenCounts = [];
|
||||||
|
$advisorTotalCounts = [];
|
||||||
|
$recommendedAssessorKey = null;
|
||||||
|
$startRow = (int) ($storeConfig['startRow'] ?? 5552);
|
||||||
$catalogoProductos = [];
|
$catalogoProductos = [];
|
||||||
$stats = [
|
$stats = [
|
||||||
'total' => 0,
|
'total' => 0,
|
||||||
@ -161,7 +175,7 @@ try {
|
|||||||
$catalogoProductos = [];
|
$catalogoProductos = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$preview = drive_test_fetch_orders($loadLimit, $startRow);
|
$preview = drive_test_fetch_orders($loadLimit, $startRow, $storeKey);
|
||||||
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
||||||
$orders = $preview['orders'] ?? [];
|
$orders = $preview['orders'] ?? [];
|
||||||
|
|
||||||
@ -191,6 +205,145 @@ try {
|
|||||||
$orders = drive_test_merge_tracking($orders, $tracking);
|
$orders = drive_test_merge_tracking($orders, $tracking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'auto_assign_cupo') {
|
||||||
|
$openStates = cc_test_open_states();
|
||||||
|
|
||||||
|
if (empty($assessors)) {
|
||||||
|
throw new RuntimeException('No hay asesoras configuradas en el sistema.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pendingByAssessorId = [];
|
||||||
|
foreach ($assessors as $assessor) {
|
||||||
|
$id = (int) ($assessor['id'] ?? 0);
|
||||||
|
if ($id > 0) {
|
||||||
|
$pendingByAssessorId[$id] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contamos cuántos pedidos siguen activos (POR LLAMAR / DEVOLVER LLAMADA / OBSERVADO).
|
||||||
|
foreach ($orders as $o) {
|
||||||
|
if ($o['user_id'] === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uid = (int) ($o['user_id'] ?? 0);
|
||||||
|
if (!isset($pendingByAssessorId[$uid])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$estadoNorm = cc_test_normalize_state((string) ($o['estado'] ?? ''));
|
||||||
|
if (in_array($estadoNorm, $openStates, true)) {
|
||||||
|
$pendingByAssessorId[$uid]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingByAssessorId = [];
|
||||||
|
foreach ($pendingByAssessorId as $uid => $pending) {
|
||||||
|
$missingByAssessorId[$uid] = max(0, (int) $cupoObjetivoPorAsesora - (int) $pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingTotal = array_sum($missingByAssessorId);
|
||||||
|
|
||||||
|
// Pedidos sin asignar y que todavía requieren llamada.
|
||||||
|
$eligibleUnassigned = [];
|
||||||
|
foreach ($orders as $o) {
|
||||||
|
if ($o['user_id'] !== null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$estadoNorm = cc_test_normalize_state((string) ($o['estado'] ?? ''));
|
||||||
|
if (!in_array($estadoNorm, $openStates, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$eligibleUnassigned[] = $o;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($missingTotal <= 0) {
|
||||||
|
$noticeMessage = 'Cupos completos: ninguna asesora necesita pedidos por el momento.';
|
||||||
|
} elseif (empty($eligibleUnassigned)) {
|
||||||
|
$noticeMessage = 'No hay pedidos sin asignar (pendientes) para rellenar los cupos.';
|
||||||
|
} else {
|
||||||
|
usort($eligibleUnassigned, static function (array $a, array $b): int {
|
||||||
|
$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($a) <=> cc_test_order_time($b);
|
||||||
|
});
|
||||||
|
|
||||||
|
$orderedAssessorIds = [];
|
||||||
|
foreach (['KARINA', 'ESTEFANYA'] as $assessorKey) {
|
||||||
|
if (isset($assessors[$assessorKey])) {
|
||||||
|
$orderedAssessorIds[] = (int) ($assessors[$assessorKey]['id'] ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($orderedAssessorIds)) {
|
||||||
|
$orderedAssessorIds = array_map('intval', array_keys($pendingByAssessorId));
|
||||||
|
}
|
||||||
|
|
||||||
|
$assignedCount = 0;
|
||||||
|
|
||||||
|
foreach ($eligibleUnassigned as $order) {
|
||||||
|
if ($missingTotal <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chosenId = null;
|
||||||
|
$minPending = PHP_INT_MAX;
|
||||||
|
|
||||||
|
foreach ($orderedAssessorIds as $uid) {
|
||||||
|
$uid = (int) $uid;
|
||||||
|
if ($uid <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$missing = $missingByAssessorId[$uid] ?? 0;
|
||||||
|
if ($missing <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pending = $pendingByAssessorId[$uid] ?? 0;
|
||||||
|
if ($pending < $minPending) {
|
||||||
|
$minPending = $pending;
|
||||||
|
$chosenId = $uid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($chosenId === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_test_upsert_assignee($pdo, (string) ($order['source_key'] ?? ''), $chosenId);
|
||||||
|
|
||||||
|
$pendingByAssessorId[$chosenId] = ((int) ($pendingByAssessorId[$chosenId] ?? 0)) + 1;
|
||||||
|
$missingByAssessorId[$chosenId] = max(0, (int) ($missingByAssessorId[$chosenId] ?? 0) - 1);
|
||||||
|
$missingTotal--;
|
||||||
|
|
||||||
|
$assignedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($assignedCount > 0) {
|
||||||
|
$noticeMessage = 'Auto-asignación lista: se asignaron ' . $assignedCount . ' pedidos para completar cupos.';
|
||||||
|
} else {
|
||||||
|
$noticeMessage = 'No se asignaron pedidos (puede que ya no existan cupos o pedidos elegibles).';
|
||||||
|
}
|
||||||
|
|
||||||
|
$tracking = drive_test_fetch_tracking($pdo, array_column($orders, 'source_key'));
|
||||||
|
$orders = drive_test_merge_tracking($orders, $tracking);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
$sourceKeys = array_column($orders, 'source_key');
|
$sourceKeys = array_column($orders, 'source_key');
|
||||||
$callCounts = [];
|
$callCounts = [];
|
||||||
if (!empty($sourceKeys)) {
|
if (!empty($sourceKeys)) {
|
||||||
@ -205,6 +358,16 @@ try {
|
|||||||
$openStates = cc_test_open_states();
|
$openStates = cc_test_open_states();
|
||||||
$closedStates = cc_test_closed_states();
|
$closedStates = cc_test_closed_states();
|
||||||
|
|
||||||
|
$advisorOpenCounts = [];
|
||||||
|
$advisorTotalCounts = [];
|
||||||
|
foreach ($assessors as $assessor) {
|
||||||
|
$id = (int) ($assessor['id'] ?? 0);
|
||||||
|
if ($id > 0) {
|
||||||
|
$advisorOpenCounts[$id] = 0;
|
||||||
|
$advisorTotalCounts[$id] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($orders as &$order) {
|
foreach ($orders as &$order) {
|
||||||
$order['estado'] = cc_test_normalize_state((string) ($order['estado'] ?? ''));
|
$order['estado'] = cc_test_normalize_state((string) ($order['estado'] ?? ''));
|
||||||
$order['total_llamadas'] = (int) ($callCounts[$order['source_key']] ?? 0);
|
$order['total_llamadas'] = (int) ($callCounts[$order['source_key']] ?? 0);
|
||||||
@ -216,6 +379,16 @@ try {
|
|||||||
&& ($proximaDate === null || $proximaDate <= $todayEnd);
|
&& ($proximaDate === null || $proximaDate <= $todayEnd);
|
||||||
$order['es_cerrado'] = in_array($order['estado'], $closedStates, true);
|
$order['es_cerrado'] = in_array($order['estado'], $closedStates, true);
|
||||||
|
|
||||||
|
if ($order['user_id'] !== null) {
|
||||||
|
$userId = (int) $order['user_id'];
|
||||||
|
if (isset($advisorTotalCounts[$userId])) {
|
||||||
|
$advisorTotalCounts[$userId]++;
|
||||||
|
if (in_array($order['estado'], $openStates, true)) {
|
||||||
|
$advisorOpenCounts[$userId]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$stats['total']++;
|
$stats['total']++;
|
||||||
if ($order['es_nuevo_hoy']) {
|
if ($order['es_nuevo_hoy']) {
|
||||||
$stats['nuevos_hoy']++;
|
$stats['nuevos_hoy']++;
|
||||||
@ -238,6 +411,26 @@ try {
|
|||||||
}
|
}
|
||||||
unset($order);
|
unset($order);
|
||||||
|
|
||||||
|
$orderedAssessorKeys = ['KARINA', 'ESTEFANYA'];
|
||||||
|
$orderedAssessorKeys = array_values(array_filter($orderedAssessorKeys, static function (string $key) use ($assessors): bool {
|
||||||
|
return isset($assessors[$key]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (empty($orderedAssessorKeys)) {
|
||||||
|
$orderedAssessorKeys = array_keys($assessors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$recommendedAssessorKey = null;
|
||||||
|
$minPending = PHP_INT_MAX;
|
||||||
|
foreach ($orderedAssessorKeys as $assessorKey) {
|
||||||
|
$id = (int) ($assessors[$assessorKey]['id'] ?? 0);
|
||||||
|
$pending = (int) ($advisorOpenCounts[$id] ?? 0);
|
||||||
|
if ($pending < $minPending) {
|
||||||
|
$minPending = $pending;
|
||||||
|
$recommendedAssessorKey = $assessorKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$visibleOrders = array_values(array_filter($orders, static function (array $order) use ($view): bool {
|
$visibleOrders = array_values(array_filter($orders, static function (array $order) use ($view): bool {
|
||||||
return match ($view) {
|
return match ($view) {
|
||||||
'pendientes_hoy' => (bool) ($order['es_pendiente_hoy'] ?? false),
|
'pendientes_hoy' => (bool) ($order['es_pendiente_hoy'] ?? false),
|
||||||
@ -278,17 +471,25 @@ require_once 'layout_header.php';
|
|||||||
<section class="mb-4">
|
<section class="mb-4">
|
||||||
<div class="d-flex flex-column flex-xl-row justify-content-between align-items-xl-center gap-3">
|
<div class="d-flex flex-column flex-xl-row justify-content-between align-items-xl-center gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h2 fw-bold mb-1"><i class="bi bi-headset text-primary"></i> Call Center de prueba</h1>
|
<h1 class="h2 fw-bold mb-1"><i class="bi bi-headset text-primary"></i> Base de Datos Pedidos</h1>
|
||||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||||
<a href="gestiones_callcenter.php" class="btn btn-sm <?php echo $view !== 'todos' ? 'btn-primary' : 'btn-outline-primary'; ?>">Bandeja principal</a>
|
<a href="?view=pendientes_hoy&store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-sm <?php echo $view !== 'todos' ? 'btn-primary' : 'btn-outline-primary'; ?>">Bandeja principal</a>
|
||||||
<a href="call_center_pro.php" class="btn btn-sm btn-outline-dark">Call Center Pro</a>
|
<a href="call_center_pro.php?view=<?php echo urlencode($view); ?>&store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-sm btn-outline-dark">Pedidos Asignados</a>
|
||||||
</div>
|
</div>
|
||||||
|
<ul class="nav nav-pills mt-3" role="tablist" aria-label="Selector de tienda">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?php echo $storeKey === 'flower' ? 'active' : ''; ?>" href="?view=<?php echo htmlspecialchars($view); ?>&store=flower">Flower</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?php echo $storeKey === 'otra_tienda' ? 'active' : ''; ?>" href="?view=<?php echo htmlspecialchars($view); ?>&store=otra_tienda">TUANI</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Drive detectado: <?php echo (int) $totalRows; ?> filas</span>
|
<span class="badge rounded-pill text-bg-light border px-3 py-2">Drive detectado: <?php echo (int) $totalRows; ?> filas</span>
|
||||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Extracción: desde fila <?php echo (int) $startRow; ?></span>
|
<span class="badge rounded-pill text-bg-light border px-3 py-2">Extracción: desde fila <?php echo (int) $startRow; ?></span>
|
||||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Auto actualización: cada 10 min</span>
|
<span class="badge rounded-pill text-bg-light border px-3 py-2">Auto actualización: cada 10 min</span>
|
||||||
<a href="test_importar_drive.php" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
|
<a href="test_importar_drive.php?store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -306,7 +507,7 @@ require_once 'layout_header.php';
|
|||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<section class="row g-3 mb-4">
|
<section class="row g-3 mb-4">
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=pendientes_hoy" class="text-decoration-none">
|
<a href="?view=pendientes_hoy&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'pendientes_hoy' ? 'bg-dark text-white' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'pendientes_hoy' ? 'bg-dark text-white' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase opacity-75 mb-2">Pendientes de hoy</div>
|
<div class="small text-uppercase opacity-75 mb-2">Pendientes de hoy</div>
|
||||||
@ -316,7 +517,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=nuevos_hoy" class="text-decoration-none">
|
<a href="?view=nuevos_hoy&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'nuevos_hoy' ? 'bg-primary-subtle border border-primary' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'nuevos_hoy' ? 'bg-primary-subtle border border-primary' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Nuevos de hoy</div>
|
<div class="small text-uppercase text-muted mb-2">Nuevos de hoy</div>
|
||||||
@ -326,7 +527,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=confirmados" class="text-decoration-none">
|
<a href="?view=confirmados&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'confirmados' ? 'bg-success-subtle border border-success' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'confirmados' ? 'bg-success-subtle border border-success' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Confirmados</div>
|
<div class="small text-uppercase text-muted mb-2">Confirmados</div>
|
||||||
@ -336,7 +537,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=seguimiento" class="text-decoration-none">
|
<a href="?view=seguimiento&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'seguimiento' ? 'bg-primary-subtle border border-primary' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'seguimiento' ? 'bg-primary-subtle border border-primary' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Seguimiento</div>
|
<div class="small text-uppercase text-muted mb-2">Seguimiento</div>
|
||||||
@ -346,7 +547,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=observados" class="text-decoration-none">
|
<a href="?view=observados&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'observados' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'observados' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Observados</div>
|
<div class="small text-uppercase text-muted mb-2">Observados</div>
|
||||||
@ -356,7 +557,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=cerrados" class="text-decoration-none">
|
<a href="?view=cerrados&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'cerrados' ? 'bg-secondary-subtle border border-secondary' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'cerrados' ? 'bg-secondary-subtle border border-secondary' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Cerrados</div>
|
<div class="small text-uppercase text-muted mb-2">Cerrados</div>
|
||||||
@ -366,7 +567,7 @@ require_once 'layout_header.php';
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-xl-2">
|
<div class="col-md-6 col-xl-2">
|
||||||
<a href="?view=todos" class="text-decoration-none">
|
<a href="?view=todos&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'todos' ? 'bg-light border' : 'bg-white'; ?>">
|
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'todos' ? 'bg-light border' : 'bg-white'; ?>">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="small text-uppercase text-muted mb-2">Todos</div>
|
<div class="small text-uppercase text-muted mb-2">Todos</div>
|
||||||
@ -377,6 +578,63 @@ require_once 'layout_header.php';
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-4">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<?php
|
||||||
|
$karinaId = (int) ($assessors['KARINA']['id'] ?? 0);
|
||||||
|
$estefId = (int) ($assessors['ESTEFANYA']['id'] ?? 0);
|
||||||
|
$karinaPendientes = $karinaId > 0 ? (int) ($advisorOpenCounts[$karinaId] ?? 0) : 0;
|
||||||
|
$estefPendientes = $estefId > 0 ? (int) ($advisorOpenCounts[$estefId] ?? 0) : 0;
|
||||||
|
$karinaCupoRestante = max(0, (int) $cupoObjetivoPorAsesora - $karinaPendientes);
|
||||||
|
$estefCupoRestante = max(0, (int) $cupoObjetivoPorAsesora - $estefPendientes);
|
||||||
|
$tieneCupos = ($karinaCupoRestante > 0 || $estefCupoRestante > 0);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
|
||||||
|
<div>
|
||||||
|
<h2 class="h5 fw-bold mb-1">
|
||||||
|
<i class="bi bi-activity text-primary"></i> Rendimiento por asesora
|
||||||
|
</h2>
|
||||||
|
<div class="text-muted small">Pendientes = <strong>POR LLAMAR</strong> / <strong>DEVOLVER LLAMADA</strong> / <strong>OBSERVADO</strong></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap gap-2 align-items-stretch justify-content-lg-end">
|
||||||
|
<div class="border rounded-3 bg-light px-3 py-2" style="min-width: 240px;">
|
||||||
|
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||||
|
<div class="fw-bold text-primary">KARINA</div>
|
||||||
|
<span class="badge bg-primary-subtle text-primary-emphasis border">Pendientes: <?php echo (int) $karinaPendientes; ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
|
||||||
|
<div class="h3 fw-bold mb-0"><?php echo (int) $karinaCupoRestante; ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border rounded-3 bg-light px-3 py-2" style="min-width: 240px;">
|
||||||
|
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||||
|
<div class="fw-bold text-success">ESTEFANYA</div>
|
||||||
|
<span class="badge bg-success-subtle text-success-emphasis border">Pendientes: <?php echo (int) $estefPendientes; ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
|
||||||
|
<div class="h3 fw-bold mb-0"><?php echo (int) $estefCupoRestante; ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="d-flex flex-column gap-2" style="min-width: 220px;">
|
||||||
|
<input type="hidden" name="action" value="auto_assign_cupo">
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm w-100" <?php echo $tieneCupos ? '' : 'disabled'; ?>>
|
||||||
|
Rellenar cupos
|
||||||
|
</button>
|
||||||
|
<div class="small text-muted">Asigna pedidos sin asignar hasta completar el cupo.</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="card border-0 shadow-sm">
|
<section class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white py-3 d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-2">
|
<div class="card-header bg-white py-3 d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-2">
|
||||||
<div>
|
<div>
|
||||||
@ -471,10 +729,16 @@ require_once 'layout_header.php';
|
|||||||
<input type="hidden" name="action" value="assign_assessor">
|
<input type="hidden" name="action" value="assign_assessor">
|
||||||
<input type="hidden" name="source_key" value="<?php echo htmlspecialchars($order['source_key']); ?>">
|
<input type="hidden" name="source_key" value="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||||
<div class="small text-uppercase text-muted fw-semibold mb-1">Asignar a asesora</div>
|
<div class="small text-uppercase text-muted fw-semibold mb-1">Asignar a asesora</div>
|
||||||
|
<?php
|
||||||
|
$unassigned = ($order['user_id'] === null);
|
||||||
|
?>
|
||||||
<select name="target_assessor" class="form-select form-select-sm mb-2">
|
<select name="target_assessor" class="form-select form-select-sm mb-2">
|
||||||
<option value=""<?php echo empty($order['user_id']) ? ' selected' : ''; ?>>Sin asignar</option>
|
<option value=""<?php echo ($unassigned && empty($recommendedAssessorKey)) ? ' selected' : ''; ?>>Sin asignar</option>
|
||||||
<?php foreach ($assessors as $assessorKey => $assessor): ?>
|
<?php foreach ($assessors as $assessorKey => $assessor):
|
||||||
<option value="<?php echo htmlspecialchars($assessorKey); ?>"<?php echo ((int) ($order['user_id'] ?? 0) === (int) ($assessor['id'] ?? 0)) ? ' selected' : ''; ?>><?php echo htmlspecialchars($assessor['label']); ?></option>
|
$isSelected = ($order['user_id'] !== null && (int) ($order['user_id'] ?? 0) === (int) ($assessor['id'] ?? 0))
|
||||||
|
|| ($unassigned && $recommendedAssessorKey === $assessorKey);
|
||||||
|
?>
|
||||||
|
<option value="<?php echo htmlspecialchars($assessorKey); ?>"<?php echo $isSelected ? ' selected' : ''; ?>><?php echo htmlspecialchars($assessor['label']); ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
<button type="submit" class="btn btn-sm btn-primary w-100">Asignar pedido</button>
|
<button type="submit" class="btn btn-sm btn-primary w-100">Asignar pedido</button>
|
||||||
|
|||||||
@ -10,6 +10,76 @@ function drive_test_normalize_header(string $header): string
|
|||||||
return $header;
|
return $header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drive_test_available_stores(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// Hoja original que ya venía funcionando.
|
||||||
|
'flower' => [
|
||||||
|
'label' => 'Flower',
|
||||||
|
'spreadsheet_id' => '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c',
|
||||||
|
'sheet_gid' => null,
|
||||||
|
'startRow' => 5552,
|
||||||
|
],
|
||||||
|
// Segunda tienda (nuevo link que pasaste).
|
||||||
|
'otra_tienda' => [
|
||||||
|
'label' => 'TUANI',
|
||||||
|
'spreadsheet_id' => '1QYKeBJIIqYm6yW6Ka-5gKdxhUHwXOAc0No7RjnimxTw',
|
||||||
|
'sheet_gid' => 1523126328,
|
||||||
|
'startRow' => 5552,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function drive_test_get_store_config(string $storeKey): array
|
||||||
|
{
|
||||||
|
$storeKey = trim(mb_strtolower($storeKey));
|
||||||
|
$stores = drive_test_available_stores();
|
||||||
|
|
||||||
|
if (!isset($stores[$storeKey])) {
|
||||||
|
return $stores['flower'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stores[$storeKey] + ['key' => $storeKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
function drive_test_resolve_sheet_a1_range($service, string $spreadsheetId, ?int $sheetGid, string $rangeA1): string
|
||||||
|
{
|
||||||
|
if ($sheetGid === null) {
|
||||||
|
return $rangeA1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static $cache = [];
|
||||||
|
$cacheKey = $spreadsheetId . '|' . (string) $sheetGid . '|' . $rangeA1;
|
||||||
|
if (isset($cache[$cacheKey])) {
|
||||||
|
return $cache[$cacheKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$meta = $service->spreadsheets->get($spreadsheetId, ['fields' => 'sheets(properties(sheetId,title))']);
|
||||||
|
foreach (($meta->getSheets() ?? []) as $sheet) {
|
||||||
|
$props = $sheet->getProperties();
|
||||||
|
if (!$props) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int) $props->getSheetId() === (int) $sheetGid) {
|
||||||
|
$title = trim((string) ($props->getTitle() ?? ''));
|
||||||
|
if ($title !== '') {
|
||||||
|
// Sheet names with spaces/special chars must be quoted.
|
||||||
|
$escaped = str_replace("'", "''", $title);
|
||||||
|
$cache[$cacheKey] = "'{$escaped}'!{$rangeA1}";
|
||||||
|
return $cache[$cacheKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
// Fallback to default range.
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache[$cacheKey] = $rangeA1;
|
||||||
|
return $rangeA1;
|
||||||
|
}
|
||||||
|
|
||||||
function drive_test_get_cell(array $row, array $indexes, array $aliases, string $default = ''): string
|
function drive_test_get_cell(array $row, array $indexes, array $aliases, string $default = ''): string
|
||||||
{
|
{
|
||||||
foreach ($aliases as $alias) {
|
foreach ($aliases as $alias) {
|
||||||
@ -22,11 +92,18 @@ function drive_test_get_cell(array $row, array $indexes, array $aliases, string
|
|||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552): array
|
function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $storeKey = 'flower'): array
|
||||||
{
|
{
|
||||||
|
$storeConfig = drive_test_get_store_config($storeKey);
|
||||||
|
|
||||||
$credentialsPath = __DIR__ . '/../google_credentials.json';
|
$credentialsPath = __DIR__ . '/../google_credentials.json';
|
||||||
$spreadsheetId = '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c';
|
$spreadsheetId = (string) ($storeConfig['spreadsheet_id'] ?? '');
|
||||||
$range = 'A:Z';
|
$sheetGid = array_key_exists('sheet_gid', $storeConfig) ? ($storeConfig['sheet_gid'] === null ? null : (int) $storeConfig['sheet_gid']) : null;
|
||||||
|
$rangeA1 = 'A:Z';
|
||||||
|
|
||||||
|
if ($spreadsheetId === '') {
|
||||||
|
throw new RuntimeException('Configuración de Drive para la tienda no válida.');
|
||||||
|
}
|
||||||
|
|
||||||
if (!file_exists($credentialsPath)) {
|
if (!file_exists($credentialsPath)) {
|
||||||
throw new RuntimeException('No se encontró el archivo de credenciales de Google.');
|
throw new RuntimeException('No se encontró el archivo de credenciales de Google.');
|
||||||
@ -37,6 +114,9 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552): array
|
|||||||
$client->addScope(Google\Service\Sheets::SPREADSHEETS_READONLY);
|
$client->addScope(Google\Service\Sheets::SPREADSHEETS_READONLY);
|
||||||
|
|
||||||
$service = new Google\Service\Sheets($client);
|
$service = new Google\Service\Sheets($client);
|
||||||
|
|
||||||
|
$range = drive_test_resolve_sheet_a1_range($service, $spreadsheetId, $sheetGid, $rangeA1);
|
||||||
|
|
||||||
$response = $service->spreadsheets_values->get($spreadsheetId, $range);
|
$response = $service->spreadsheets_values->get($spreadsheetId, $range);
|
||||||
$values = $response->getValues();
|
$values = $response->getValues();
|
||||||
|
|
||||||
@ -59,16 +139,16 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552): array
|
|||||||
}
|
}
|
||||||
|
|
||||||
$dataRows = array_slice($values, 1);
|
$dataRows = array_slice($values, 1);
|
||||||
|
|
||||||
$startIndex = max(0, $startRow - 2);
|
$startIndex = max(0, $startRow - 2);
|
||||||
if ($startIndex > 0) {
|
if ($startIndex > 0 && $startIndex < count($dataRows)) {
|
||||||
$dataRows = array_slice($dataRows, $startIndex);
|
$dataRows = array_slice($dataRows, $startIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($limit > 0) {
|
$previewRows = $limit > 0
|
||||||
$previewRows = array_reverse(array_slice($dataRows, -$limit));
|
? array_reverse(array_slice($dataRows, -$limit))
|
||||||
} else {
|
: array_reverse($dataRows);
|
||||||
$previewRows = array_reverse($dataRows);
|
|
||||||
}
|
|
||||||
$orders = [];
|
$orders = [];
|
||||||
|
|
||||||
foreach ($previewRows as $row) {
|
foreach ($previewRows as $row) {
|
||||||
@ -89,7 +169,15 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552): array
|
|||||||
$direccion = drive_test_get_cell($row, $headerIndexes, ['DIRECION', 'DIRECCION']);
|
$direccion = drive_test_get_cell($row, $headerIndexes, ['DIRECION', 'DIRECCION']);
|
||||||
$referencia = drive_test_get_cell($row, $headerIndexes, ['REFERENCIA']);
|
$referencia = drive_test_get_cell($row, $headerIndexes, ['REFERENCIA']);
|
||||||
$distrito = drive_test_get_cell($row, $headerIndexes, ['DISTRITO']);
|
$distrito = drive_test_get_cell($row, $headerIndexes, ['DISTRITO']);
|
||||||
$sourceKey = sha1(implode('|', [$codigo, $importId, $nombre, $celular, $producto]));
|
|
||||||
|
// Importante: para 'flower' mantenemos EXACTAMENTE el algoritmo anterior
|
||||||
|
// para no romper el historial/tracking ya existente.
|
||||||
|
$sourceKeyBase = implode('|', [$codigo, $importId, $nombre, $celular, $producto]);
|
||||||
|
if (($storeConfig['key'] ?? 'flower') === 'flower') {
|
||||||
|
$sourceKey = sha1($sourceKeyBase);
|
||||||
|
} else {
|
||||||
|
$sourceKey = sha1(($storeConfig['key'] ?? $storeKey) . '|' . $sourceKeyBase);
|
||||||
|
}
|
||||||
|
|
||||||
$orders[] = [
|
$orders[] = [
|
||||||
'source_key' => $sourceKey,
|
'source_key' => $sourceKey,
|
||||||
|
|||||||
@ -104,7 +104,7 @@ $pageDescription = 'Entrada rápida al sistema CRM y panel de administración.';
|
|||||||
<a class="btn btn-primary" href="<?php echo $isLoggedIn ? 'dashboard.php' : 'login.php'; ?>">
|
<a class="btn btn-primary" href="<?php echo $isLoggedIn ? 'dashboard.php' : 'login.php'; ?>">
|
||||||
<?php echo $isLoggedIn ? 'Ir al dashboard' : 'Iniciar sesión'; ?>
|
<?php echo $isLoggedIn ? 'Ir al dashboard' : 'Iniciar sesión'; ?>
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-secondary" href="gestiones_callcenter.php">Abrir Call Center de prueba</a>
|
<a class="btn btn-secondary" href="gestiones_callcenter.php">Abrir Base de Datos Pedidos</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
|||||||
@ -348,13 +348,13 @@ $navItems = [
|
|||||||
'call_center' => [
|
'call_center' => [
|
||||||
'url' => 'gestiones_callcenter.php',
|
'url' => 'gestiones_callcenter.php',
|
||||||
'icon' => 'fa-headset',
|
'icon' => 'fa-headset',
|
||||||
'text' => 'Call Center (Mis Gestiones)',
|
'text' => 'Base de Datos Pedidos',
|
||||||
'roles' => ['Administrador', 'admin', 'Asesor']
|
'roles' => ['Administrador', 'admin']
|
||||||
],
|
],
|
||||||
'call_center_pro' => [
|
'call_center_pro' => [
|
||||||
'url' => 'call_center_pro.php',
|
'url' => 'call_center_pro.php',
|
||||||
'icon' => 'fa-headset',
|
'icon' => 'fa-headset',
|
||||||
'text' => 'Call Center Pro',
|
'text' => 'Pedidos Asignados',
|
||||||
'roles' => ['Administrador', 'admin', 'Asesor']
|
'roles' => ['Administrador', 'admin', 'Asesor']
|
||||||
],
|
],
|
||||||
'simular_asignacion' => [
|
'simular_asignacion' => [
|
||||||
@ -447,7 +447,7 @@ $navItems = [
|
|||||||
<a href="#" class="nav-link"><i class="fas <?php echo $item['icon']; ?>"></i> <?php echo $item['text']; ?></a>
|
<a href="#" class="nav-link"><i class="fas <?php echo $item['icon']; ?>"></i> <?php echo $item['text']; ?></a>
|
||||||
<ul class="submenu <?php echo $isSubmenuActive ? 'show' : ''; ?>">
|
<ul class="submenu <?php echo $isSubmenuActive ? 'show' : ''; ?>">
|
||||||
<?php foreach ($item['submenu'] as $sub_item):
|
<?php foreach ($item['submenu'] as $sub_item):
|
||||||
if ($isTestModule || in_array($userRole, $sub_item['roles'])):
|
if (in_array($userRole, $sub_item['roles'])):
|
||||||
$isActive = $currentPage == $sub_item['url'] ? 'active' : '';
|
$isActive = $currentPage == $sub_item['url'] ? 'active' : '';
|
||||||
?>
|
?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
|||||||
@ -28,6 +28,23 @@ try {
|
|||||||
$pdo = db();
|
$pdo = db();
|
||||||
cc_test_ensure_historial_llamadas_table($pdo);
|
cc_test_ensure_historial_llamadas_table($pdo);
|
||||||
|
|
||||||
|
$role = (string) ($_SESSION['user_role'] ?? '');
|
||||||
|
$isAdmin = in_array($role, ['Administrador', 'admin'], true);
|
||||||
|
if (!$isAdmin) {
|
||||||
|
cc_test_ensure_tracking_table($pdo);
|
||||||
|
|
||||||
|
$stmtTrack = $pdo->prepare('SELECT user_id FROM callcenter_test_tracking WHERE source_key = ? LIMIT 1');
|
||||||
|
$stmtTrack->execute([$pedido_id]);
|
||||||
|
$tracking = $stmtTrack->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$trackingUserId = $tracking ? (int) ($tracking['user_id'] ?? 0) : null;
|
||||||
|
if (!$tracking || $trackingUserId !== $asesor_id) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No autorizado para gestionar este pedido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare('INSERT INTO historial_llamadas (pedido_id, asesor_id, resultado, observacion) VALUES (?, ?, ?, ?)');
|
$stmt = $pdo->prepare('INSERT INTO historial_llamadas (pedido_id, asesor_id, resultado, observacion) VALUES (?, ?, ?, ?)');
|
||||||
$stmt->execute([$pedido_id, $asesor_id, $resultado, $observacion !== '' ? $observacion : null]);
|
$stmt->execute([$pedido_id, $asesor_id, $resultado, $observacion !== '' ? $observacion : null]);
|
||||||
|
|
||||||
|
|||||||
@ -39,4 +39,4 @@ foreach ($pedidos as $id) {
|
|||||||
|
|
||||||
echo "<h3>Simulación Exitosa</h3>";
|
echo "<h3>Simulación Exitosa</h3>";
|
||||||
echo "Se han asignado " . count($pedidos) . " pedidos al usuario <strong>$username</strong> (ID: $userId).<br>";
|
echo "Se han asignado " . count($pedidos) . " pedidos al usuario <strong>$username</strong> (ID: $userId).<br>";
|
||||||
echo "Ahora puedes verlos en el panel de gestión: <a href='gestiones_callcenter.php'>Ir al Panel de Call Center</a>";
|
echo "Ahora puedes verlos en el panel de gestión: <a href='gestiones_callcenter.php'>Ir a Base de Datos Pedidos</a>";
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
session_start();
|
session_start();
|
||||||
require_once __DIR__ . '/db/config.php';
|
require_once __DIR__ . '/db/config.php';
|
||||||
require_once __DIR__ . '/includes/callcenter_test_helpers.php';
|
require_once __DIR__ . '/includes/callcenter_test_helpers.php';
|
||||||
|
require_once __DIR__ . '/includes/drive_test_orders.php';
|
||||||
|
|
||||||
$pageTitle = 'Importar Drive (Test)';
|
$pageTitle = 'Importar Drive (Test)';
|
||||||
$pageDescription = 'Vista previa de los últimos 10 pedidos detectados en Google Sheets antes de importarlos al CRM.';
|
$pageDescription = 'Vista previa de los últimos 10 pedidos detectados en Google Sheets antes de importarlos al CRM.';
|
||||||
@ -18,8 +19,20 @@ require_once __DIR__ . '/layout_header.php';
|
|||||||
require_once __DIR__ . '/vendor/autoload.php';
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
$credentialsPath = __DIR__ . '/google_credentials.json';
|
$credentialsPath = __DIR__ . '/google_credentials.json';
|
||||||
$spreadsheetId = '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c';
|
|
||||||
$range = 'A:Z';
|
$storeKey = trim((string) ($_GET['store'] ?? 'flower'));
|
||||||
|
$storeKey = mb_strtolower($storeKey);
|
||||||
|
$availableStores = drive_test_available_stores();
|
||||||
|
if (!isset($availableStores[$storeKey])) {
|
||||||
|
$storeKey = 'flower';
|
||||||
|
}
|
||||||
|
$storeConfig = $availableStores[$storeKey];
|
||||||
|
|
||||||
|
$spreadsheetId = (string) ($storeConfig['spreadsheet_id'] ?? '');
|
||||||
|
$rangeA1 = 'A:Z';
|
||||||
|
$sheetGid = array_key_exists('sheet_gid', $storeConfig) ? ($storeConfig['sheet_gid'] === null ? null : (int) $storeConfig['sheet_gid']) : null;
|
||||||
|
|
||||||
|
$range = $rangeA1;
|
||||||
$headers = [];
|
$headers = [];
|
||||||
$previewRows = [];
|
$previewRows = [];
|
||||||
$errorMessage = null;
|
$errorMessage = null;
|
||||||
@ -35,6 +48,8 @@ try {
|
|||||||
$client->addScope(Google\Service\Sheets::SPREADSHEETS_READONLY);
|
$client->addScope(Google\Service\Sheets::SPREADSHEETS_READONLY);
|
||||||
|
|
||||||
$service = new Google\Service\Sheets($client);
|
$service = new Google\Service\Sheets($client);
|
||||||
|
|
||||||
|
$range = drive_test_resolve_sheet_a1_range($service, $spreadsheetId, $sheetGid, $rangeA1);
|
||||||
$response = $service->spreadsheets_values->get($spreadsheetId, $range);
|
$response = $service->spreadsheets_values->get($spreadsheetId, $range);
|
||||||
$values = $response->getValues();
|
$values = $response->getValues();
|
||||||
|
|
||||||
@ -61,9 +76,12 @@ try {
|
|||||||
<h2 class="h4 mb-1">Vista previa de pedidos desde Drive</h2>
|
<h2 class="h4 mb-1">Vista previa de pedidos desde Drive</h2>
|
||||||
<p class="text-muted mb-0">Aquí verás solo los últimos 10 registros encontrados en la hoja, sin guardar nada todavía en la base de datos.</p>
|
<p class="text-muted mb-0">Aquí verás solo los últimos 10 registros encontrados en la hoja, sin guardar nada todavía en la base de datos.</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="badge bg-primary-subtle text-primary-emphasis px-3 py-2">
|
<div class="d-flex flex-wrap gap-2 justify-content-end">
|
||||||
<?php echo (int) count($previewRows); ?> visibles
|
<span class="badge bg-light text-dark border px-3 py-2">Tienda: <?php echo htmlspecialchars((string) ($storeConfig['label'] ?? '')); ?></span>
|
||||||
</span>
|
<span class="badge bg-primary-subtle text-primary-emphasis px-3 py-2">
|
||||||
|
<?php echo (int) count($previewRows); ?> visibles
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($errorMessage !== null): ?>
|
<?php if ($errorMessage !== null): ?>
|
||||||
|
|||||||
@ -71,10 +71,26 @@ try {
|
|||||||
$pdo = db();
|
$pdo = db();
|
||||||
cc_test_ensure_tracking_table($pdo);
|
cc_test_ensure_tracking_table($pdo);
|
||||||
|
|
||||||
$stmtCurrent = $pdo->prepare('SELECT estado, numero_cuenta_enviado_at FROM callcenter_test_tracking WHERE source_key = ? LIMIT 1');
|
$stmtCurrent = $pdo->prepare('SELECT estado, numero_cuenta_enviado_at, user_id FROM callcenter_test_tracking WHERE source_key = ? LIMIT 1');
|
||||||
$stmtCurrent->execute([$sourceKey]);
|
$stmtCurrent->execute([$sourceKey]);
|
||||||
$currentTracking = $stmtCurrent->fetch(PDO::FETCH_ASSOC) ?: null;
|
$currentTracking = $stmtCurrent->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||||
|
|
||||||
|
$role = (string) ($_SESSION['user_role'] ?? '');
|
||||||
|
$isAdmin = in_array($role, ['Administrador', 'admin'], true);
|
||||||
|
if (!$isAdmin) {
|
||||||
|
$currentUserId = (int) ($_SESSION['user_id'] ?? 0);
|
||||||
|
$trackingUserId = $currentTracking ? (int) ($currentTracking['user_id'] ?? 0) : null;
|
||||||
|
if (!$currentTracking || $trackingUserId !== $currentUserId) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No autorizado para gestionar este pedido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$userIdToSet = $isAdmin
|
||||||
|
? ($currentTracking ? ($currentTracking['user_id'] ?? null) : null)
|
||||||
|
: (int) $_SESSION['user_id'];
|
||||||
|
|
||||||
$proximaRaw = trim((string) ($_POST['proxima_llamada_at'] ?? ''));
|
$proximaRaw = trim((string) ($_POST['proxima_llamada_at'] ?? ''));
|
||||||
$proximaLlamada = null;
|
$proximaLlamada = null;
|
||||||
if ($proximaRaw !== '') {
|
if ($proximaRaw !== '') {
|
||||||
@ -204,7 +220,7 @@ try {
|
|||||||
':source_key' => $sourceKey,
|
':source_key' => $sourceKey,
|
||||||
':estado' => $estado,
|
':estado' => $estado,
|
||||||
':nota' => $nota,
|
':nota' => $nota,
|
||||||
':user_id' => (int) $_SESSION['user_id'],
|
':user_id' => $userIdToSet,
|
||||||
':direccion' => $direccion,
|
':direccion' => $direccion,
|
||||||
':referencia' => $referencia,
|
':referencia' => $referencia,
|
||||||
':agencia' => $agencia,
|
':agencia' => $agencia,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user