Autosave: 20260723-183722
This commit is contained in:
parent
2caec74b43
commit
2924efbd4b
@ -25,6 +25,12 @@ if (isset($_GET['ajax']) && $_GET['ajax'] === 'panel_status') {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||
|
||||
$panelStoreKey = trim(mb_strtolower((string) ($_GET['store'] ?? 'flower')));
|
||||
$availablePanelStores = drive_test_available_stores();
|
||||
if (!isset($availablePanelStores[$panelStoreKey])) {
|
||||
$panelStoreKey = 'flower';
|
||||
}
|
||||
|
||||
$currentUserId = (int) ($_SESSION['user_id'] ?? 0);
|
||||
if ($currentUserId <= 0) {
|
||||
http_response_code(401);
|
||||
@ -36,9 +42,23 @@ if (isset($_GET['ajax']) && $_GET['ajax'] === 'panel_status') {
|
||||
}
|
||||
|
||||
try {
|
||||
$stmtPanel = $pdo->prepare("\n SELECT source_key, user_id, estado, assigned_at, updated_at, eliminado_at\n FROM callcenter_test_tracking\n WHERE user_id = ? AND eliminado_at IS NULL\n ORDER BY source_key\n ");
|
||||
$stmtPanel->execute([$currentUserId]);
|
||||
$panelOrders = $stmtPanel->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($panelStoreKey === 'tuani_recuperables') {
|
||||
$panelOrders = drive_test_fetch_orders_from_db($pdo, $panelStoreKey);
|
||||
$panelTracking = drive_test_fetch_tracking($pdo, array_column($panelOrders, 'source_key'));
|
||||
$panelOrders = drive_test_merge_tracking($panelOrders, $panelTracking);
|
||||
$panelOrders = array_values(array_filter($panelOrders, static function (array $order): bool {
|
||||
return empty($order['eliminado']);
|
||||
}));
|
||||
} else {
|
||||
$stmtPanel = $pdo->prepare("
|
||||
SELECT source_key, user_id, estado, assigned_at, updated_at, eliminado_at
|
||||
FROM callcenter_test_tracking
|
||||
WHERE user_id = ? AND eliminado_at IS NULL
|
||||
ORDER BY source_key
|
||||
");
|
||||
$stmtPanel->execute([$currentUserId]);
|
||||
$panelOrders = $stmtPanel->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
@ -247,20 +267,6 @@ function cc_test_panel_orders_signature(array $orders): string
|
||||
}
|
||||
|
||||
$view = $_GET['view'] ?? 'pendientes_hoy';
|
||||
$allowedViews = [
|
||||
'pendientes_hoy' => 'Bandeja principal',
|
||||
'nuevos_hoy' => 'Pedidos asignados hoy',
|
||||
'confirmados' => 'Confirmados',
|
||||
'recuperados' => 'Recuperados',
|
||||
'seguimiento' => 'Seguimiento',
|
||||
'promo_final' => 'Promo Final',
|
||||
'observados' => 'Observados',
|
||||
'cerrados' => 'Cerrados / descartados',
|
||||
'todos' => 'Todos los pedidos cargados',
|
||||
];
|
||||
if (!isset($allowedViews[$view])) {
|
||||
$view = 'pendientes_hoy';
|
||||
}
|
||||
|
||||
$storeKey = trim((string) ($_GET['store'] ?? 'flower'));
|
||||
$storeKey = mb_strtolower($storeKey);
|
||||
@ -271,6 +277,122 @@ if (!isset($availableStores[$storeKey])) {
|
||||
$storeConfig = $availableStores[$storeKey];
|
||||
$storeLabel = (string) ($storeConfig['label'] ?? strtoupper($storeKey));
|
||||
$usesIncrementalSync = !empty($storeConfig['incremental_sync']);
|
||||
$storeNav = drive_test_store_navigation_state($availableStores, $storeKey);
|
||||
$storeNavGroups = $storeNav['groups'];
|
||||
$activeStoreGroupKey = (string) ($storeNav['active_group_key'] ?? $storeKey);
|
||||
$activeStoreGroupLabel = (string) ($storeNav['active_group_label'] ?? $storeLabel);
|
||||
$activeStoreGroupChildren = $storeNav['active_group_children'] ?? [];
|
||||
$parentStoreLabel = (string) ($storeNav['parent_label'] ?? '');
|
||||
|
||||
$isMainTuaniStore = cc_test_is_tuani_main_store_key($storeKey);
|
||||
$isTuaniRecuperablesStore = $storeKey === 'tuani_recuperables';
|
||||
$viewCardColumnClass = $isTuaniRecuperablesStore ? 'col-md-6 col-xl-2' : 'col-md-6 col-xl-2';
|
||||
|
||||
$viewCards = $isTuaniRecuperablesStore ? [
|
||||
'pendientes_hoy' => [
|
||||
'label' => 'Carritos recuperables',
|
||||
'heading' => 'Carritos recuperables',
|
||||
'stat_key' => 'pendientes_hoy',
|
||||
'active_class' => 'bg-dark text-white',
|
||||
],
|
||||
'confirmados' => [
|
||||
'label' => 'Confirmados',
|
||||
'heading' => 'Confirmados',
|
||||
'stat_key' => 'confirmados',
|
||||
'active_class' => 'bg-success-subtle border border-success',
|
||||
],
|
||||
'recuperados' => [
|
||||
'label' => 'Recuperados',
|
||||
'heading' => 'Recuperados',
|
||||
'stat_key' => 'recuperados',
|
||||
'active_class' => 'bg-warning-subtle border border-warning',
|
||||
],
|
||||
'seguimiento' => [
|
||||
'label' => 'Seguimiento',
|
||||
'heading' => 'Seguimiento',
|
||||
'stat_key' => 'seguimiento',
|
||||
'active_class' => 'bg-primary-subtle border border-primary',
|
||||
],
|
||||
'observados' => [
|
||||
'label' => 'Observados',
|
||||
'heading' => 'Observados',
|
||||
'stat_key' => 'observados',
|
||||
'active_class' => 'bg-warning-subtle border border-warning',
|
||||
],
|
||||
'todos' => [
|
||||
'label' => 'Todos',
|
||||
'heading' => 'Todos los pedidos cargados',
|
||||
'stat_key' => 'total',
|
||||
'active_class' => 'bg-light border',
|
||||
],
|
||||
] : [
|
||||
'pendientes_hoy' => [
|
||||
'label' => 'Bandeja principal',
|
||||
'heading' => 'Bandeja principal',
|
||||
'stat_key' => 'pendientes_hoy',
|
||||
'active_class' => 'bg-dark text-white',
|
||||
],
|
||||
'nuevos_hoy' => [
|
||||
'label' => 'Asignados hoy',
|
||||
'heading' => 'Pedidos asignados hoy',
|
||||
'stat_key' => 'nuevos_hoy',
|
||||
'active_class' => 'bg-primary-subtle border border-primary',
|
||||
],
|
||||
'confirmados' => [
|
||||
'label' => 'Confirmados',
|
||||
'heading' => 'Confirmados',
|
||||
'stat_key' => 'confirmados',
|
||||
'active_class' => 'bg-success-subtle border border-success',
|
||||
],
|
||||
'recuperados' => [
|
||||
'label' => 'Recuperados',
|
||||
'heading' => 'Recuperados',
|
||||
'stat_key' => 'recuperados',
|
||||
'active_class' => 'bg-warning-subtle border border-warning',
|
||||
],
|
||||
'seguimiento' => [
|
||||
'label' => 'Seguimiento',
|
||||
'heading' => 'Seguimiento',
|
||||
'stat_key' => 'seguimiento',
|
||||
'active_class' => 'bg-primary-subtle border border-primary',
|
||||
],
|
||||
'promo_final' => [
|
||||
'label' => 'Promo Final',
|
||||
'heading' => 'Promo Final',
|
||||
'stat_key' => 'promo_final',
|
||||
'active_class' => 'bg-danger-subtle border border-danger',
|
||||
],
|
||||
'observados' => [
|
||||
'label' => 'Observados',
|
||||
'heading' => 'Observados',
|
||||
'stat_key' => 'observados',
|
||||
'active_class' => 'bg-warning-subtle border border-warning',
|
||||
],
|
||||
'cerrados' => [
|
||||
'label' => 'Cerrados',
|
||||
'heading' => 'Cerrados / descartados',
|
||||
'stat_key' => 'cerrados',
|
||||
'active_class' => 'bg-secondary-subtle border border-secondary',
|
||||
],
|
||||
'todos' => [
|
||||
'label' => 'Todos',
|
||||
'heading' => 'Todos los pedidos cargados',
|
||||
'stat_key' => 'total',
|
||||
'active_class' => 'bg-light border',
|
||||
],
|
||||
];
|
||||
|
||||
$allowedViews = [];
|
||||
foreach ($viewCards as $viewKey => $cardConfig) {
|
||||
$allowedViews[$viewKey] = $cardConfig['heading'];
|
||||
}
|
||||
if (!isset($allowedViews[$view])) {
|
||||
$view = array_key_first($allowedViews) ?: 'pendientes_hoy';
|
||||
}
|
||||
|
||||
$viewStatesNote = $isTuaniRecuperablesStore
|
||||
? 'Estados disponibles: <strong>Carritos recuperables</strong>, <strong>Seguimiento</strong>, <strong>Observados</strong>, <strong>Confirmados</strong>, <strong>Recuperados</strong> y <strong>Todos</strong>. Esta bandeja compartida está disponible para cualquier asesora y, cuando una asesora gestiona un carrito recuperable, el sistema lo asigna automáticamente a quien lo trabaja.'
|
||||
: 'Estados disponibles: Por llamar, Devolver llamada, Observado, Se envió número de cuenta, Confirmado contraentrega, Confirmado envío, Cancelado y Repetido. <strong>Promo Final</strong> se habilita desde el Día 4 y exige imagen de sustento para mover el pedido.';
|
||||
|
||||
$errorMessage = null;
|
||||
$noticeMessage = null;
|
||||
@ -410,7 +532,7 @@ try {
|
||||
? (string) ($assessors[$selectedAssessorFilter]['label'] ?? $selectedAssessorFilter)
|
||||
: '';
|
||||
|
||||
if (!$isAdmin) {
|
||||
if (!$isAdmin && !$isTuaniRecuperablesStore) {
|
||||
$orders = array_values(array_filter($orders, static function (array $order) use ($currentUserId): bool {
|
||||
return (int) ($order['user_id'] ?? 0) === $currentUserId;
|
||||
}));
|
||||
@ -433,9 +555,7 @@ try {
|
||||
$order['promo_final_evidencia_cargada'] = trim((string) ($order['promo_final_evidencia_path'] ?? '')) !== '';
|
||||
$order['es_promo_final'] = $order['promo_final_habilitado'] && $order['promo_final_evidencia_cargada'];
|
||||
$order['promo_final_pendiente'] = $order['promo_final_habilitado'] && !$order['promo_final_evidencia_cargada'];
|
||||
$order['pendiente_logistica_destino'] = cc_test_is_tuani_store_key($storeKey)
|
||||
? cc_test_pending_logistica_destination($order)
|
||||
: null;
|
||||
$order['pendiente_logistica_destino'] = $isMainTuaniStore ? cc_test_pending_logistica_destination($order) : null;
|
||||
$order['pendiente_logistica'] = $order['pendiente_logistica_destino'] !== null;
|
||||
|
||||
$order['es_nuevo_hoy'] = $assignedDate ? $assignedDate->format('Y-m-d') === $todayStart->format('Y-m-d') : false;
|
||||
@ -487,20 +607,35 @@ try {
|
||||
}
|
||||
|
||||
$performanceDashboard = cc_test_build_performance_dashboard($performanceOrders, $assessors, 7);
|
||||
$performanceTitle = $isAdmin
|
||||
? ($selectedAssessorFilterLabel !== '' ? 'KPI diario · ' . $selectedAssessorFilterLabel : 'KPI diario por asesora')
|
||||
: 'Mi KPI diario';
|
||||
$performanceSubtitle = $isAdmin
|
||||
? 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados hoy.'
|
||||
: 'Tus KPIs de hoy se calculan sobre tus pedidos asignados hoy. Confirmación = asignados hoy confirmados hoy. Recuperación = asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados hoy.';
|
||||
if ($isTuaniRecuperablesStore) {
|
||||
$performanceTitle = 'KPI compartido de recuperación';
|
||||
$performanceSubtitle = 'Los indicadores de esta bandeja se calculan sobre todos los pedidos visibles para cualquier asesora.';
|
||||
$performanceChart1Title = 'Distribución de estados de la bandeja';
|
||||
$performanceChart1Note = 'La dona separa los pedidos visibles por estado en esta bandeja compartida.';
|
||||
$performanceChart2Title = 'Evolución de los últimos 7 días';
|
||||
$performanceChart2Note = 'La línea muestra cómo se mueve la bandeja compartida en los últimos 7 días.';
|
||||
$performanceFootnote = 'Los indicadores se calculan sobre todos los pedidos visibles de esta bandeja compartida.';
|
||||
} else {
|
||||
$performanceTitle = $isAdmin
|
||||
? ($selectedAssessorFilterLabel !== '' ? 'KPI diario · ' . $selectedAssessorFilterLabel : 'KPI diario por asesora')
|
||||
: 'Mi KPI diario';
|
||||
$performanceSubtitle = $isAdmin
|
||||
? 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados hoy.'
|
||||
: 'Tus KPIs de hoy se calculan sobre tus pedidos asignados hoy. Confirmación = asignados hoy confirmados hoy. Recuperación = asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados hoy.';
|
||||
$performanceChart1Title = $isAdmin ? 'Distribución de estados' : 'Mi distribución de estados';
|
||||
$performanceChart1Note = 'La dona separa los pedidos de hoy por estado: confirmados nuevos, recuperados, POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO, SE ENVIO NUMERO DE CUENTA, repetidos y cancelados.';
|
||||
$performanceChart2Title = $isAdmin ? 'Evolución de los últimos 7 días' : 'Tu evolución de los últimos 7 días';
|
||||
$performanceChart2Note = 'La línea de confirmados separa pedidos asignados hoy y pedidos asignados antes de hoy.';
|
||||
$performanceFootnote = 'Seguimiento abierto = pedidos asignados hoy en POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO y SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.';
|
||||
}
|
||||
$performanceDashboardHtml = cc_test_render_performance_dashboard($performanceDashboard, [
|
||||
'title' => $performanceTitle,
|
||||
'subtitle' => $performanceSubtitle,
|
||||
'chart1_title' => $isAdmin ? 'Distribución de estados' : 'Mi distribución de estados',
|
||||
'chart1_note' => 'La dona separa los pedidos de hoy por estado: confirmados nuevos, recuperados, POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO, SE ENVIO NUMERO DE CUENTA, repetidos y cancelados.',
|
||||
'chart2_title' => $isAdmin ? 'Evolución de los últimos 7 días' : 'Tu evolución de los últimos 7 días',
|
||||
'chart2_note' => 'La línea de confirmados separa pedidos asignados hoy y pedidos asignados antes de hoy.',
|
||||
'footnote' => 'Seguimiento abierto = pedidos asignados hoy en POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO y SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.',
|
||||
'chart1_title' => $performanceChart1Title,
|
||||
'chart1_note' => $performanceChart1Note,
|
||||
'chart2_title' => $performanceChart2Title,
|
||||
'chart2_note' => $performanceChart2Note,
|
||||
'footnote' => $performanceFootnote,
|
||||
]);
|
||||
|
||||
$panelSignature = cc_test_panel_orders_signature($orders);
|
||||
@ -631,6 +766,16 @@ if ($selectedAssessorFilter !== '') {
|
||||
$callCenterParams['assessor'] = $selectedAssessorFilter;
|
||||
}
|
||||
|
||||
if ($isTuaniRecuperablesStore) {
|
||||
$pageTitle = 'Carritos recuperables | Bandeja compartida';
|
||||
$pageDescription = 'Bandeja compartida para que cualquier asesora gestione carritos recuperables con vistas de seguimiento y observados desde Drive.';
|
||||
} else {
|
||||
$pageTitle = 'Pedidos Asignados | Bandeja de la asesora';
|
||||
$pageDescription = 'Bandeja para que cada asesora gestione sus pedidos asignados con la misma interfaz, estados, historial y acciones.';
|
||||
}
|
||||
|
||||
$callCenterSectionLabel = $isTuaniRecuperablesStore ? 'Carritos recuperables' : 'Pedidos Asignados';
|
||||
|
||||
require_once 'layout_header.php';
|
||||
?>
|
||||
|
||||
@ -797,22 +942,36 @@ require_once 'layout_header.php';
|
||||
<section class="mb-4">
|
||||
<div class="d-flex flex-column flex-xl-row justify-content-between align-items-xl-center gap-3">
|
||||
<div>
|
||||
<h1 class="h2 fw-bold mb-1"><i class="bi bi-headset text-primary"></i> Pedidos Asignados</h1>
|
||||
<h1 class="h2 fw-bold mb-1"><i class="bi bi-headset text-primary"></i> <?php echo htmlspecialchars($callCenterSectionLabel); ?></h1>
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<?php if ($isAdmin): ?>
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('gestiones_callcenter.php', $callCenterParams)); ?>" class="btn btn-sm btn-outline-primary">Base de Datos Pedidos</a>
|
||||
<?php endif; ?>
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', $callCenterParams)); ?>" class="btn btn-sm btn-primary">Pedidos Asignados</a>
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', $callCenterParams)); ?>" class="btn btn-sm btn-primary"><?php echo htmlspecialchars($callCenterSectionLabel); ?></a>
|
||||
</div>
|
||||
<ul class="nav nav-pills mt-3 flex-wrap gap-2" role="tablist" aria-label="Selector de tienda">
|
||||
<?php foreach ($availableStores as $key => $config): ?>
|
||||
<?php foreach ($storeNavGroups as $groupKey => $group): ?>
|
||||
<?php $groupConfig = $group['config'] ?? []; ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?php echo $storeKey === $key ? 'active' : ''; ?>" href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['store' => $key]))); ?>">
|
||||
<?php echo htmlspecialchars((string) ($config['label'] ?? strtoupper((string) $key))); ?>
|
||||
<a class="nav-link <?php echo $activeStoreGroupKey === $groupKey ? 'active' : ''; ?>" href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['store' => $groupKey]))); ?>">
|
||||
<?php echo htmlspecialchars((string) ($groupConfig['label'] ?? strtoupper((string) $groupKey))); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php if (!empty($activeStoreGroupChildren)): ?>
|
||||
<div class="mt-3 p-3 rounded-4 border bg-light">
|
||||
<div class="small text-uppercase text-muted fw-semibold mb-2">Dentro de <?php echo htmlspecialchars($activeStoreGroupLabel); ?></div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-sm <?php echo $storeKey === $activeStoreGroupKey ? 'btn-primary' : 'btn-outline-primary'; ?>" href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['store' => $activeStoreGroupKey]))); ?>">Pedidos <?php echo htmlspecialchars($activeStoreGroupLabel); ?></a>
|
||||
<?php foreach ($activeStoreGroupChildren as $childKey => $childConfig): ?>
|
||||
<a class="btn btn-sm <?php echo $storeKey === $childKey ? 'btn-primary' : 'btn-outline-primary'; ?>" href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['store' => $childKey]))); ?>">
|
||||
<?php echo htmlspecialchars((string) ($childConfig['label'] ?? strtoupper((string) $childKey))); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<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>
|
||||
@ -839,98 +998,22 @@ require_once 'layout_header.php';
|
||||
<?php echo $performanceDashboardHtml ?? ''; ?>
|
||||
|
||||
<section class="row g-3 mb-4">
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'pendientes_hoy']))); ?>" class="text-decoration-none">
|
||||
<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="small text-uppercase opacity-75 mb-2">Bandeja principal</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['pendientes_hoy']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'nuevos_hoy']))); ?>" 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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Asignados hoy</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['nuevos_hoy']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'confirmados']))); ?>" 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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Confirmados</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['confirmados']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'recuperados']))); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'recuperados' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Recuperados</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['recuperados']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'seguimiento']))); ?>" 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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Seguimiento</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['seguimiento']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'promo_final']))); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'promo_final' ? 'bg-danger-subtle border border-danger' : 'bg-white'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Promo Final</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['promo_final']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'observados']))); ?>" 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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Observados</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['observados']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'cerrados']))); ?>" 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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Cerrados</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['cerrados']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'todos']))); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'todos' ? 'bg-light border' : 'bg-white'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Todos</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['total']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<?php foreach ($viewCards as $viewKey => $viewCard): ?>
|
||||
<?php $isActiveCard = $view === $viewKey; ?>
|
||||
<?php $cardClass = $isActiveCard ? $viewCard['active_class'] : 'bg-white'; ?>
|
||||
<?php $labelClass = $isActiveCard && str_contains((string) ($viewCard['active_class'] ?? ''), 'text-white') ? 'opacity-75' : 'text-muted'; ?>
|
||||
<div class="<?php echo htmlspecialchars($viewCardColumnClass); ?>">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => $viewKey]))); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo htmlspecialchars($cardClass); ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase <?php echo htmlspecialchars($labelClass); ?> mb-2"><?php echo htmlspecialchars((string) ($viewCard['label'] ?? $viewKey)); ?></div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) ($stats[$viewCard['stat_key']] ?? 0); ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
|
||||
<?php if ($isAdmin): ?>
|
||||
<section class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body py-3">
|
||||
@ -967,7 +1050,7 @@ require_once 'layout_header.php';
|
||||
<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>
|
||||
<h2 class="h5 fw-bold mb-1"><?php echo htmlspecialchars($allowedViews[$view]); ?></h2>
|
||||
<p class="text-muted small mb-1">Estados disponibles: Por llamar, Devolver llamada, Observado, Se envió número de cuenta, Confirmado contraentrega, Confirmado envío, Cancelado y Repetido. <strong>Promo Final</strong> se habilita desde el Día 4 y exige imagen de sustento para mover el pedido.</p>
|
||||
<p class="text-muted small mb-1"><?php echo $viewStatesNote; ?></p>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<?php if ($selectedAssessorFilterLabel !== ''): ?>
|
||||
@ -1023,7 +1106,7 @@ require_once 'layout_header.php';
|
||||
$subirLogisticaButtonLabel = 'Subir pedido';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-muted';
|
||||
$subirLogisticaNoteHtml = 'Selecciona <strong>CONFIRMADO CONTRAENTREGA</strong> o <strong>CONFIRMADO ENVIO</strong> para enviarlo a logística.';
|
||||
if (cc_test_is_tuani_store_key($storeKey)) {
|
||||
if ($isMainTuaniStore) {
|
||||
if ($order['estado'] === 'CONFIRMADO CONTRAENTREGA') {
|
||||
if (!empty($order['ruta_contraentrega_pedido_id'])) {
|
||||
$subirLogisticaButtonLabel = 'Actualizar en ruta';
|
||||
@ -1218,7 +1301,7 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<h3 class="modal-title h5 mb-0"><?php echo htmlspecialchars(cc_test_display_value($order['nombre'], 'Cliente sin nombre')); ?></h3>
|
||||
<?php if (cc_test_is_tuani_store_key($storeKey)): ?>
|
||||
<?php if ($isMainTuaniStore): ?>
|
||||
<button type="button" class="btn btn-primary btn-sm px-3" id="subir-logistica-<?php echo htmlspecialchars($order['source_key']); ?>" data-route-pedido-id="<?php echo (int) ($order['ruta_contraentrega_pedido_id'] ?? 0); ?>" data-rotulado-pedido-id="<?php echo (int) ($order['pedido_rotulado_pedido_id'] ?? 0); ?>" onclick="guardarGestion('<?php echo htmlspecialchars($order['source_key']); ?>', this, { subirLogistica: true })"><?php echo htmlspecialchars($subirLogisticaButtonLabel); ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@ -1253,7 +1336,7 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (cc_test_is_tuani_store_key($storeKey)): ?>
|
||||
<?php if ($isMainTuaniStore): ?>
|
||||
<div class="<?php echo htmlspecialchars($subirLogisticaNoteClass); ?>" id="subir-logistica-note-<?php echo htmlspecialchars($order['source_key']); ?>"><?php echo $subirLogisticaNoteHtml; ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@ -1596,7 +1679,7 @@ require_once 'layout_header.php';
|
||||
|
||||
<div class="modal-footer d-flex justify-content-between flex-wrap gap-2">
|
||||
<div class="small text-muted">
|
||||
<?php if (cc_test_is_tuani_store_key($storeKey)): ?>
|
||||
<?php if ($isMainTuaniStore): ?>
|
||||
<strong>Guardar gestión</strong> solo guarda el historial. Para enviarlo a logística, usa el <strong>botón del encabezado</strong> junto al nombre del cliente.
|
||||
<?php else: ?>
|
||||
Los cambios se guardan en la base local del módulo de prueba.
|
||||
|
||||
@ -73,6 +73,19 @@ function cc_test_display_value(?string $value, string $fallback = 'No registrado
|
||||
return $value !== '' ? $value : $fallback;
|
||||
}
|
||||
|
||||
function cc_test_build_url(string $path, array $params = []): string
|
||||
{
|
||||
foreach ($params as $key => $value) {
|
||||
if ($value === '' || $value === null) {
|
||||
unset($params[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$query = http_build_query($params);
|
||||
|
||||
return $query !== '' ? $path . '?' . $query : $path;
|
||||
}
|
||||
|
||||
function cc_test_order_label(array $order): string
|
||||
{
|
||||
if (!empty($order['is_agregado'])) {
|
||||
@ -160,20 +173,6 @@ function cc_test_followup_semaforo(array $order): ?array
|
||||
}
|
||||
|
||||
$view = $_GET['view'] ?? 'pendientes_hoy';
|
||||
$allowedViews = [
|
||||
'pendientes_hoy' => 'Bandeja principal',
|
||||
'nuevos_hoy' => 'Nuevos de hoy',
|
||||
'confirmados' => 'Confirmados',
|
||||
'recuperados' => 'Recuperados',
|
||||
'seguimiento' => 'Seguimiento',
|
||||
'promo_final' => 'Promo Final',
|
||||
'observados' => 'Observados',
|
||||
'cerrados' => 'Cerrados / descartados',
|
||||
'todos' => 'Todos los pedidos cargados',
|
||||
];
|
||||
if (!isset($allowedViews[$view])) {
|
||||
$view = 'pendientes_hoy';
|
||||
}
|
||||
|
||||
$storeKey = trim((string) ($_GET['store'] ?? 'flower'));
|
||||
$storeKey = mb_strtolower($storeKey);
|
||||
@ -184,6 +183,122 @@ if (!isset($availableStores[$storeKey])) {
|
||||
$storeConfig = $availableStores[$storeKey];
|
||||
$storeLabel = (string) ($storeConfig['label'] ?? strtoupper($storeKey));
|
||||
$usesIncrementalSync = !empty($storeConfig['incremental_sync']);
|
||||
$storeNav = drive_test_store_navigation_state($availableStores, $storeKey);
|
||||
$storeNavGroups = $storeNav['groups'];
|
||||
$activeStoreGroupKey = (string) ($storeNav['active_group_key'] ?? $storeKey);
|
||||
$activeStoreGroupLabel = (string) ($storeNav['active_group_label'] ?? $storeLabel);
|
||||
$activeStoreGroupChildren = $storeNav['active_group_children'] ?? [];
|
||||
$parentStoreLabel = (string) ($storeNav['parent_label'] ?? '');
|
||||
|
||||
$isMainTuaniStore = cc_test_is_tuani_main_store_key($storeKey);
|
||||
$isTuaniRecuperablesStore = $storeKey === 'tuani_recuperables';
|
||||
$viewCardColumnClass = $isTuaniRecuperablesStore ? 'col-md-6 col-xl-2' : 'col-md-6 col-xl-2';
|
||||
|
||||
$viewCards = $isTuaniRecuperablesStore ? [
|
||||
'pendientes_hoy' => [
|
||||
'label' => 'Carritos recuperables',
|
||||
'heading' => 'Carritos recuperables',
|
||||
'stat_key' => 'pendientes_hoy',
|
||||
'active_class' => 'bg-dark text-white',
|
||||
],
|
||||
'confirmados' => [
|
||||
'label' => 'Confirmados',
|
||||
'heading' => 'Confirmados',
|
||||
'stat_key' => 'confirmados',
|
||||
'active_class' => 'bg-success-subtle border border-success',
|
||||
],
|
||||
'recuperados' => [
|
||||
'label' => 'Recuperados',
|
||||
'heading' => 'Recuperados',
|
||||
'stat_key' => 'recuperados',
|
||||
'active_class' => 'bg-warning-subtle border border-warning',
|
||||
],
|
||||
'seguimiento' => [
|
||||
'label' => 'Seguimiento',
|
||||
'heading' => 'Seguimiento',
|
||||
'stat_key' => 'seguimiento',
|
||||
'active_class' => 'bg-primary-subtle border border-primary',
|
||||
],
|
||||
'observados' => [
|
||||
'label' => 'Observados',
|
||||
'heading' => 'Observados',
|
||||
'stat_key' => 'observados',
|
||||
'active_class' => 'bg-warning-subtle border border-warning',
|
||||
],
|
||||
'todos' => [
|
||||
'label' => 'Todos',
|
||||
'heading' => 'Todos los pedidos cargados',
|
||||
'stat_key' => 'total',
|
||||
'active_class' => 'bg-light border',
|
||||
],
|
||||
] : [
|
||||
'pendientes_hoy' => [
|
||||
'label' => 'Bandeja principal',
|
||||
'heading' => 'Bandeja principal',
|
||||
'stat_key' => 'pendientes_hoy',
|
||||
'active_class' => 'bg-dark text-white',
|
||||
],
|
||||
'nuevos_hoy' => [
|
||||
'label' => 'Nuevos de hoy',
|
||||
'heading' => 'Nuevos de hoy',
|
||||
'stat_key' => 'nuevos_hoy',
|
||||
'active_class' => 'bg-primary-subtle border border-primary',
|
||||
],
|
||||
'confirmados' => [
|
||||
'label' => 'Confirmados',
|
||||
'heading' => 'Confirmados',
|
||||
'stat_key' => 'confirmados',
|
||||
'active_class' => 'bg-success-subtle border border-success',
|
||||
],
|
||||
'recuperados' => [
|
||||
'label' => 'Recuperados',
|
||||
'heading' => 'Recuperados',
|
||||
'stat_key' => 'recuperados',
|
||||
'active_class' => 'bg-warning-subtle border border-warning',
|
||||
],
|
||||
'seguimiento' => [
|
||||
'label' => 'Seguimiento',
|
||||
'heading' => 'Seguimiento',
|
||||
'stat_key' => 'seguimiento',
|
||||
'active_class' => 'bg-primary-subtle border border-primary',
|
||||
],
|
||||
'promo_final' => [
|
||||
'label' => 'Promo Final',
|
||||
'heading' => 'Promo Final',
|
||||
'stat_key' => 'promo_final',
|
||||
'active_class' => 'bg-danger-subtle border border-danger',
|
||||
],
|
||||
'observados' => [
|
||||
'label' => 'Observados',
|
||||
'heading' => 'Observados',
|
||||
'stat_key' => 'observados',
|
||||
'active_class' => 'bg-warning-subtle border border-warning',
|
||||
],
|
||||
'cerrados' => [
|
||||
'label' => 'Cerrados',
|
||||
'heading' => 'Cerrados / descartados',
|
||||
'stat_key' => 'cerrados',
|
||||
'active_class' => 'bg-secondary-subtle border border-secondary',
|
||||
],
|
||||
'todos' => [
|
||||
'label' => 'Todos',
|
||||
'heading' => 'Todos los pedidos cargados',
|
||||
'stat_key' => 'total',
|
||||
'active_class' => 'bg-light border',
|
||||
],
|
||||
];
|
||||
|
||||
$allowedViews = [];
|
||||
foreach ($viewCards as $viewKey => $cardConfig) {
|
||||
$allowedViews[$viewKey] = $cardConfig['heading'];
|
||||
}
|
||||
if (!isset($allowedViews[$view])) {
|
||||
$view = array_key_first($allowedViews) ?: 'pendientes_hoy';
|
||||
}
|
||||
|
||||
$viewStatesNote = $isTuaniRecuperablesStore
|
||||
? 'Estados disponibles: <strong>Carritos recuperables</strong>, <strong>Seguimiento</strong>, <strong>Observados</strong>, <strong>Confirmados</strong>, <strong>Recuperados</strong> y <strong>Todos</strong>. Esta bandeja compartida está disponible para cualquier asesora y, cuando una asesora gestiona un carrito recuperable, el sistema lo asigna automáticamente a quien lo trabaja.'
|
||||
: 'Estados disponibles: Por llamar, Devolver llamada, Observado, Se envió número de cuenta, Confirmado contraentrega, Confirmado envío, Cancelado y Repetido. <strong>Promo Final</strong> se habilita desde el Día 4 y exige imagen de sustento para mover el pedido.';
|
||||
|
||||
$errorMessage = null;
|
||||
$noticeMessage = null;
|
||||
@ -633,9 +748,7 @@ try {
|
||||
$order['promo_final_evidencia_cargada'] = trim((string) ($order['promo_final_evidencia_path'] ?? '')) !== '';
|
||||
$order['es_promo_final'] = $order['promo_final_habilitado'] && $order['promo_final_evidencia_cargada'];
|
||||
$order['promo_final_pendiente'] = $order['promo_final_habilitado'] && !$order['promo_final_evidencia_cargada'];
|
||||
$order['pendiente_logistica_destino'] = cc_test_is_tuani_store_key($storeKey)
|
||||
? cc_test_pending_logistica_destination($order)
|
||||
: null;
|
||||
$order['pendiente_logistica_destino'] = $isMainTuaniStore ? cc_test_pending_logistica_destination($order) : null;
|
||||
$order['pendiente_logistica'] = $order['pendiente_logistica_destino'] !== null;
|
||||
|
||||
$order['es_nuevo_hoy'] = $firstSeenDate ? $firstSeenDate->format('Y-m-d') === $todayStart->format('Y-m-d') : false;
|
||||
@ -859,6 +972,16 @@ try {
|
||||
$errorMessage = $exception->getMessage();
|
||||
}
|
||||
|
||||
if ($isTuaniRecuperablesStore) {
|
||||
$pageTitle = 'Carritos recuperables | Base de Datos Pedidos';
|
||||
$pageDescription = 'Bandeja compartida para que cualquier asesora revise carritos recuperables con vistas de seguimiento y observados cargados desde Drive.';
|
||||
} else {
|
||||
$pageTitle = 'Base de Datos Pedidos | Asignación';
|
||||
$pageDescription = 'Bandejas de asignación y gestión de estados para el Call Center, cargadas desde Drive.';
|
||||
}
|
||||
|
||||
$callCenterSectionLabel = $isTuaniRecuperablesStore ? 'Carritos recuperables' : 'Pedidos Asignados';
|
||||
|
||||
require_once 'layout_header.php';
|
||||
?>
|
||||
|
||||
@ -1094,17 +1217,31 @@ require_once 'layout_header.php';
|
||||
<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">
|
||||
<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?view=<?php echo urlencode($view); ?>&store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-sm btn-outline-dark">Pedidos Asignados</a>
|
||||
<a href="call_center_pro.php?view=<?php echo urlencode($view); ?>&store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-sm btn-outline-dark"><?php echo htmlspecialchars($callCenterSectionLabel); ?></a>
|
||||
</div>
|
||||
<ul class="nav nav-pills mt-3 flex-wrap gap-2" role="tablist" aria-label="Selector de tienda">
|
||||
<?php foreach ($availableStores as $key => $config): ?>
|
||||
<?php foreach ($storeNavGroups as $groupKey => $group): ?>
|
||||
<?php $groupConfig = $group['config'] ?? []; ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?php echo $storeKey === $key ? 'active' : ''; ?>" href="?view=<?php echo htmlspecialchars($view); ?>&store=<?php echo htmlspecialchars((string) $key); ?>">
|
||||
<?php echo htmlspecialchars((string) ($config['label'] ?? strtoupper((string) $key))); ?>
|
||||
<a class="nav-link <?php echo $activeStoreGroupKey === $groupKey ? 'active' : ''; ?>" href="<?php echo htmlspecialchars(cc_test_build_url('gestiones_callcenter.php', ['view' => $view, 'store' => $groupKey])); ?>">
|
||||
<?php echo htmlspecialchars((string) ($groupConfig['label'] ?? strtoupper((string) $groupKey))); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php if (!empty($activeStoreGroupChildren)): ?>
|
||||
<div class="mt-3 p-3 rounded-4 border bg-light">
|
||||
<div class="small text-uppercase text-muted fw-semibold mb-2">Dentro de <?php echo htmlspecialchars($activeStoreGroupLabel); ?></div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-sm <?php echo $storeKey === $activeStoreGroupKey ? 'btn-primary' : 'btn-outline-primary'; ?>" href="<?php echo htmlspecialchars(cc_test_build_url('gestiones_callcenter.php', ['view' => $view, 'store' => $activeStoreGroupKey])); ?>">Pedidos <?php echo htmlspecialchars($activeStoreGroupLabel); ?></a>
|
||||
<?php foreach ($activeStoreGroupChildren as $childKey => $childConfig): ?>
|
||||
<a class="btn btn-sm <?php echo $storeKey === $childKey ? 'btn-primary' : 'btn-outline-primary'; ?>" href="<?php echo htmlspecialchars(cc_test_build_url('gestiones_callcenter.php', ['view' => $view, 'store' => $childKey])); ?>">
|
||||
<?php echo htmlspecialchars((string) ($childConfig['label'] ?? strtoupper((string) $childKey))); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<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>
|
||||
@ -1173,98 +1310,22 @@ require_once 'layout_header.php';
|
||||
</section>
|
||||
|
||||
<section class="row g-3 mb-4">
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase opacity-75 mb-2">Bandeja principal</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['pendientes_hoy']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Nuevos de hoy</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['nuevos_hoy']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Confirmados</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['confirmados']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="?view=recuperados&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'recuperados' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Recuperados</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['recuperados']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Seguimiento</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['seguimiento']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="?view=promo_final&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'promo_final' ? 'bg-danger-subtle border border-danger' : 'bg-white'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Promo Final</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['promo_final']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Observados</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['observados']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Cerrados</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['cerrados']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<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'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Todos</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['total']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<?php foreach ($viewCards as $viewKey => $viewCard): ?>
|
||||
<?php $isActiveCard = $view === $viewKey; ?>
|
||||
<?php $cardClass = $isActiveCard ? $viewCard['active_class'] : 'bg-white'; ?>
|
||||
<?php $labelClass = $isActiveCard && str_contains((string) ($viewCard['active_class'] ?? ''), 'text-white') ? 'opacity-75' : 'text-muted'; ?>
|
||||
<div class="<?php echo htmlspecialchars($viewCardColumnClass); ?>">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('gestiones_callcenter.php', ['view' => $viewKey, 'store' => $storeKey])); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo htmlspecialchars($cardClass); ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase <?php echo htmlspecialchars($labelClass); ?> mb-2"><?php echo htmlspecialchars((string) ($viewCard['label'] ?? $viewKey)); ?></div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) ($stats[$viewCard['stat_key']] ?? 0); ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
|
||||
<section class="mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
@ -1310,7 +1371,7 @@ require_once 'layout_header.php';
|
||||
<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>
|
||||
<h2 class="h5 fw-bold mb-1"><?php echo htmlspecialchars($allowedViews[$view]); ?></h2>
|
||||
<p class="text-muted small mb-1">Estados disponibles: Por llamar, Devolver llamada, Observado, Se envió número de cuenta, Confirmado contraentrega, Confirmado envío, Cancelado y Repetido. <strong>Promo Final</strong> se habilita desde el Día 4 y exige imagen de sustento para mover el pedido.</p>
|
||||
<p class="text-muted small mb-1"><?php echo $viewStatesNote; ?></p>
|
||||
</div>
|
||||
<span class="badge bg-light text-dark border"><?php echo count($visibleOrders); ?> pedidos en esta bandeja</span>
|
||||
</div>
|
||||
@ -1380,7 +1441,7 @@ require_once 'layout_header.php';
|
||||
$subirLogisticaButtonLabel = 'Subir pedido';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-muted';
|
||||
$subirLogisticaNoteHtml = 'Selecciona <strong>CONFIRMADO CONTRAENTREGA</strong> o <strong>CONFIRMADO ENVIO</strong> para enviarlo a logística.';
|
||||
if (cc_test_is_tuani_store_key($storeKey)) {
|
||||
if ($isMainTuaniStore) {
|
||||
if ($order['estado'] === 'CONFIRMADO CONTRAENTREGA') {
|
||||
if (!empty($order['ruta_contraentrega_pedido_id'])) {
|
||||
$subirLogisticaButtonLabel = 'Actualizar en ruta';
|
||||
@ -1606,7 +1667,7 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<h3 class="modal-title h5 mb-0"><?php echo htmlspecialchars(cc_test_display_value($order['nombre'], 'Cliente sin nombre')); ?></h3>
|
||||
<?php if (cc_test_is_tuani_store_key($storeKey)): ?>
|
||||
<?php if ($isMainTuaniStore): ?>
|
||||
<button type="button" class="btn btn-primary btn-sm px-3" id="subir-logistica-<?php echo htmlspecialchars($order['source_key']); ?>" data-route-pedido-id="<?php echo (int) ($order['ruta_contraentrega_pedido_id'] ?? 0); ?>" data-rotulado-pedido-id="<?php echo (int) ($order['pedido_rotulado_pedido_id'] ?? 0); ?>" onclick="guardarGestion('<?php echo htmlspecialchars($order['source_key']); ?>', this, { subirLogistica: true })"><?php echo htmlspecialchars($subirLogisticaButtonLabel); ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@ -1641,7 +1702,7 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (cc_test_is_tuani_store_key($storeKey)): ?>
|
||||
<?php if ($isMainTuaniStore): ?>
|
||||
<div class="<?php echo htmlspecialchars($subirLogisticaNoteClass); ?>" id="subir-logistica-note-<?php echo htmlspecialchars($order['source_key']); ?>"><?php echo $subirLogisticaNoteHtml; ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@ -1984,7 +2045,7 @@ require_once 'layout_header.php';
|
||||
|
||||
<div class="modal-footer d-flex justify-content-between flex-wrap gap-2">
|
||||
<div class="small text-muted">
|
||||
<?php if (cc_test_is_tuani_store_key($storeKey)): ?>
|
||||
<?php if ($isMainTuaniStore): ?>
|
||||
<strong>Guardar gestión</strong> solo guarda el historial. Para enviarlo a logística, usa el <strong>botón del encabezado</strong> junto al nombre del cliente.
|
||||
<?php else: ?>
|
||||
Los cambios se guardan en la base local del módulo de prueba.
|
||||
|
||||
@ -244,6 +244,12 @@ function cc_test_is_tuani_store_key(string $storeKey): bool
|
||||
return in_array($storeKey, ['otra_tienda', 'tuani_recuperables'], true);
|
||||
}
|
||||
|
||||
function cc_test_is_tuani_main_store_key(string $storeKey): bool
|
||||
{
|
||||
$storeKey = trim(mb_strtolower($storeKey));
|
||||
return $storeKey === 'otra_tienda';
|
||||
}
|
||||
|
||||
function cc_test_fetch_assessors(PDO $pdo, array $allowedNames = []): array
|
||||
{
|
||||
if (empty($allowedNames)) {
|
||||
|
||||
@ -33,7 +33,7 @@ function drive_test_available_stores(): array
|
||||
'enforce_db_start_row' => true,
|
||||
'incremental_sync' => true,
|
||||
],
|
||||
// Prueba de carritos abandonados: misma estructura, pero desde la fila 2000.
|
||||
// Bandeja compartida de carritos recuperables: misma estructura, pero desde la fila 2000.
|
||||
'tuani_recuperables' => [
|
||||
'label' => 'Carritos recuperables',
|
||||
'sheet_title' => 'easysell_abandoneds',
|
||||
@ -46,6 +46,79 @@ function drive_test_available_stores(): array
|
||||
];
|
||||
}
|
||||
|
||||
function drive_test_store_navigation_groups(array $availableStores): array
|
||||
{
|
||||
$groups = [];
|
||||
$childBuckets = [];
|
||||
|
||||
foreach ($availableStores as $key => $config) {
|
||||
$parentKey = trim((string) ($config['parent_store_key'] ?? ''));
|
||||
if ($parentKey !== '' && isset($availableStores[$parentKey])) {
|
||||
if (!isset($childBuckets[$parentKey])) {
|
||||
$childBuckets[$parentKey] = [];
|
||||
}
|
||||
$childBuckets[$parentKey][$key] = $config;
|
||||
continue;
|
||||
}
|
||||
|
||||
$groups[$key] = [
|
||||
'key' => $key,
|
||||
'config' => $config,
|
||||
'children' => [],
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($groups as $groupKey => &$group) {
|
||||
if (isset($childBuckets[$groupKey])) {
|
||||
$group['children'] = $childBuckets[$groupKey];
|
||||
}
|
||||
}
|
||||
unset($group);
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
function drive_test_store_navigation_state(array $availableStores, string $storeKey): array
|
||||
{
|
||||
$storeKey = mb_strtolower(trim($storeKey));
|
||||
$groups = drive_test_store_navigation_groups($availableStores);
|
||||
$activeGroupKey = $storeKey;
|
||||
$activeGroup = $groups[$storeKey] ?? null;
|
||||
|
||||
if ($activeGroup === null) {
|
||||
foreach ($groups as $groupKey => $group) {
|
||||
if (isset($group['children'][$storeKey])) {
|
||||
$activeGroupKey = (string) $groupKey;
|
||||
$activeGroup = $group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($activeGroup === null) {
|
||||
$activeGroup = [
|
||||
'key' => $storeKey,
|
||||
'config' => $availableStores[$storeKey] ?? [],
|
||||
'children' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$parentKey = trim((string) ($availableStores[$storeKey]['parent_store_key'] ?? ''));
|
||||
$parentLabel = '';
|
||||
if ($parentKey !== '' && isset($availableStores[$parentKey])) {
|
||||
$parentLabel = (string) ($availableStores[$parentKey]['label'] ?? strtoupper($parentKey));
|
||||
}
|
||||
|
||||
return [
|
||||
'groups' => $groups,
|
||||
'active_group_key' => $activeGroupKey,
|
||||
'active_group' => $activeGroup,
|
||||
'active_group_label' => (string) ($activeGroup['config']['label'] ?? strtoupper((string) $activeGroupKey)),
|
||||
'active_group_children' => $activeGroup['children'] ?? [],
|
||||
'parent_label' => $parentLabel,
|
||||
];
|
||||
}
|
||||
|
||||
function drive_test_get_store_config(string $storeKey): array
|
||||
{
|
||||
$storeKey = trim(mb_strtolower($storeKey));
|
||||
|
||||
@ -38,11 +38,34 @@ try {
|
||||
$tracking = $stmtTrack->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$trackingUserId = $tracking ? (int) ($tracking['user_id'] ?? 0) : null;
|
||||
if (!$tracking || $trackingUserId !== $asesor_id) {
|
||||
|
||||
$storeKey = '';
|
||||
try {
|
||||
$stmtOrder = $pdo->prepare('SELECT store_key FROM callcenter_test_orders WHERE source_key = ? LIMIT 1');
|
||||
$stmtOrder->execute([$pedido_id]);
|
||||
$storeKey = trim((string) ($stmtOrder->fetchColumn() ?: ''));
|
||||
} catch (Throwable $lookupException) {
|
||||
$storeKey = '';
|
||||
}
|
||||
|
||||
$isRecoverablesQueue = $storeKey === 'tuani_recuperables';
|
||||
$canClaimRecoverable = $isRecoverablesQueue && $trackingUserId === null;
|
||||
|
||||
if ($tracking === null) {
|
||||
if (!$canClaimRecoverable) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'error' => 'No autorizado para gestionar este pedido.']);
|
||||
exit;
|
||||
}
|
||||
} elseif ($trackingUserId !== $asesor_id && !$canClaimRecoverable) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'error' => 'No autorizado para gestionar este pedido.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($canClaimRecoverable) {
|
||||
cc_test_upsert_assignee($pdo, $pedido_id, $asesor_id);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('INSERT INTO historial_llamadas (pedido_id, asesor_id, resultado, observacion) VALUES (?, ?, ?, ?)');
|
||||
|
||||
@ -28,6 +28,12 @@ if (!isset($availableStores[$storeKey])) {
|
||||
}
|
||||
$storeConfig = $availableStores[$storeKey];
|
||||
$storeLabel = (string) ($storeConfig['label'] ?? strtoupper($storeKey));
|
||||
$storeNav = drive_test_store_navigation_state($availableStores, $storeKey);
|
||||
$storeNavGroups = $storeNav['groups'];
|
||||
$activeStoreGroupKey = (string) ($storeNav['active_group_key'] ?? $storeKey);
|
||||
$activeStoreGroupLabel = (string) ($storeNav['active_group_label'] ?? $storeLabel);
|
||||
$activeStoreGroupChildren = $storeNav['active_group_children'] ?? [];
|
||||
$parentStoreLabel = (string) ($storeNav['parent_label'] ?? '');
|
||||
$sheetTitle = trim((string) ($storeConfig['sheet_title'] ?? ''));
|
||||
|
||||
$spreadsheetId = (string) ($storeConfig['spreadsheet_id'] ?? '');
|
||||
@ -72,12 +78,28 @@ try {
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body py-3">
|
||||
<div class="d-flex flex-column flex-lg-row align-items-lg-center gap-3">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<?php foreach ($availableStores as $key => $config): ?>
|
||||
<a class="btn btn-sm <?php echo $storeKey === $key ? 'btn-primary' : 'btn-outline-primary'; ?>" href="?store=<?php echo htmlspecialchars((string) $key); ?>">
|
||||
<?php echo htmlspecialchars((string) ($config['label'] ?? strtoupper((string) $key))); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<div class="d-flex flex-column gap-3">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<?php foreach ($storeNavGroups as $groupKey => $group): ?>
|
||||
<?php $groupConfig = $group['config'] ?? []; ?>
|
||||
<a class="btn btn-sm <?php echo $activeStoreGroupKey === $groupKey ? 'btn-primary' : 'btn-outline-primary'; ?>" href="?store=<?php echo htmlspecialchars((string) $groupKey); ?>">
|
||||
<?php echo htmlspecialchars((string) ($groupConfig['label'] ?? strtoupper((string) $groupKey))); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if (!empty($activeStoreGroupChildren)): ?>
|
||||
<div class="p-3 rounded-4 border bg-light">
|
||||
<div class="small text-uppercase text-muted fw-semibold mb-2">Dentro de <?php echo htmlspecialchars($activeStoreGroupLabel); ?></div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-sm <?php echo $storeKey === $activeStoreGroupKey ? 'btn-primary' : 'btn-outline-primary'; ?>" href="?store=<?php echo htmlspecialchars((string) $activeStoreGroupKey); ?>">Pedidos <?php echo htmlspecialchars($activeStoreGroupLabel); ?></a>
|
||||
<?php foreach ($activeStoreGroupChildren as $childKey => $childConfig): ?>
|
||||
<a class="btn btn-sm <?php echo $storeKey === $childKey ? 'btn-primary' : 'btn-outline-primary'; ?>" href="?store=<?php echo htmlspecialchars((string) $childKey); ?>">
|
||||
<?php echo htmlspecialchars((string) ($childConfig['label'] ?? strtoupper((string) $childKey))); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="ms-lg-auto d-flex flex-wrap gap-2">
|
||||
<span class="badge bg-light text-dark border px-3 py-2">Tienda: <?php echo htmlspecialchars($storeLabel); ?></span>
|
||||
|
||||
@ -841,6 +841,10 @@ try {
|
||||
$stmtCurrent->execute([$sourceKey]);
|
||||
$currentTracking = $stmtCurrent->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
|
||||
$sourceOrderForAuth = cc_test_fetch_source_order($pdo, $sourceKey);
|
||||
$sourceStoreKeyForAuth = trim((string) ($sourceOrderForAuth['store_key'] ?? ''));
|
||||
$isRecoverablesQueue = $sourceStoreKeyForAuth === 'tuani_recuperables';
|
||||
|
||||
if (!array_key_exists('numero_cuenta_sede_id', $_POST)) {
|
||||
$numeroCuentaSedeIdCurrent = trim((string) ($currentTracking['numero_cuenta_sede_id'] ?? ''));
|
||||
$numeroCuentaSedeId = $numeroCuentaSedeIdCurrent !== '' ? $numeroCuentaSedeIdCurrent : null;
|
||||
@ -869,7 +873,15 @@ try {
|
||||
if (!$isAdmin) {
|
||||
$currentUserId = (int) ($_SESSION['user_id'] ?? 0);
|
||||
$trackingUserId = $currentTracking ? (int) ($currentTracking['user_id'] ?? 0) : null;
|
||||
if (!$currentTracking || $trackingUserId !== $currentUserId) {
|
||||
$recoverablesAutoassignStates = ['SE ENVIO NUMERO DE CUENTA', 'OBSERVADO'];
|
||||
$canClaimRecoverable = $isRecoverablesQueue && $trackingUserId === null && in_array($estado, $recoverablesAutoassignStates, true);
|
||||
if ($currentTracking === null) {
|
||||
if (!$canClaimRecoverable) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'No autorizado para gestionar este pedido.']);
|
||||
exit;
|
||||
}
|
||||
} elseif ($trackingUserId !== $currentUserId && !$canClaimRecoverable) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'No autorizado para gestionar este pedido.']);
|
||||
exit;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user