Acceso denegado.
"; require_once 'layout_footer.php'; exit(); } function cc_test_parse_datetime(?string $value): ?DateTimeImmutable { $value = trim((string) $value); if ($value === '') { return null; } try { return new DateTimeImmutable($value); } catch (Throwable $exception) { return null; } } function cc_test_format_datetime(?string $value, string $fallback = 'Sin programar'): string { $date = cc_test_parse_datetime($value); return $date ? $date->format('d/m/Y h:i A') : $fallback; } function cc_test_format_datetime_input(?string $value): string { $date = cc_test_parse_datetime($value); return $date ? $date->format('Y-m-d\TH:i') : ''; } function cc_test_format_date(?string $value, string $fallback = 'Sin fecha'): string { $date = cc_test_parse_datetime($value); return $date ? $date->format('d/m/Y') : $fallback; } function cc_test_format_date_input(?string $value): string { $date = cc_test_parse_datetime($value); return $date ? $date->format('Y-m-d') : ''; } function cc_test_format_price(?string $value): string { $value = trim((string) $value); return $value !== '' ? 'S/ ' . $value : 'No registrado'; } function cc_test_display_value(?string $value, string $fallback = 'No registrado'): string { $value = trim((string) $value); return $value !== '' ? $value : $fallback; } function cc_test_order_label(array $order): string { $codigo = trim((string) ($order['codigo'] ?? '')); if ($codigo !== '') { return '#' . ltrim($codigo, '#'); } return 'Sin número'; } function cc_test_badge_class(string $estado): string { return match (cc_test_normalize_state($estado)) { 'CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO' => 'bg-success-subtle text-success-emphasis', 'DEVOLVER LLAMADA' => 'bg-info-subtle text-info-emphasis', 'OBSERVADO' => 'bg-warning-subtle text-warning-emphasis', 'CANCELADO' => 'bg-danger-subtle text-danger-emphasis', 'REPETIDO' => 'bg-secondary-subtle text-secondary-emphasis', 'SE ENVIO NUMERO DE CUENTA' => 'bg-primary-subtle text-primary-emphasis', default => 'bg-dark-subtle text-dark-emphasis', }; } function cc_test_order_time(array $order): int { foreach (['proxima_llamada_at', 'numero_cuenta_enviado_at', 'ultima_gestion_at', 'seguimiento_actualizado', 'import_id'] as $field) { $date = cc_test_parse_datetime($order[$field] ?? null); if ($date) { return $date->getTimestamp(); } } return 0; } function cc_test_followup_semaforo(array $order): ?array { if (cc_test_normalize_state((string) ($order['estado'] ?? '')) !== 'SE ENVIO NUMERO DE CUENTA') { return null; } return cc_test_account_followup_semaforo($order['numero_cuenta_enviado_at'] ?? null); } $view = $_GET['view'] ?? 'pendientes_hoy'; $allowedViews = [ 'pendientes_hoy' => 'Pendientes de hoy', 'nuevos_hoy' => 'Nuevos de hoy', 'confirmados' => 'Confirmados', 'seguimiento' => 'Seguimiento', 'observados' => 'Observados', 'cerrados' => 'Cerrados / descartados', 'todos' => 'Todos los pedidos cargados', ]; if (!isset($allowedViews[$view])) { $view = 'pendientes_hoy'; } $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; $noticeMessage = null; $assessors = []; $orders = []; $visibleOrders = []; $modalsHtml = []; $totalRows = 0; $loadLimit = 20; $cupoObjetivoPorAsesora = 10; $advisorOpenCounts = []; $advisorTotalCounts = []; $recommendedAssessorKey = null; $startRow = (int) ($storeConfig['startRow'] ?? 5552); $catalogoProductos = []; $stats = [ 'total' => 0, 'pendientes_hoy' => 0, 'nuevos_hoy' => 0, 'confirmados' => 0, 'seguimiento' => 0, 'observados' => 0, 'cerrados' => 0, ]; try { $pdo = db(); cc_test_ensure_tracking_table($pdo); cc_test_ensure_historial_llamadas_table($pdo); $assessors = cc_test_fetch_assessors($pdo); try { $stmtProductos = $pdo->query("SELECT id, nombre FROM products ORDER BY nombre ASC"); $catalogoProductos = $stmtProductos->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $exception) { $catalogoProductos = []; } $preview = drive_test_fetch_orders($loadLimit, $startRow, $storeKey); $totalRows = (int) ($preview['total_rows'] ?? 0); $orders = $preview['orders'] ?? []; $tracking = drive_test_fetch_tracking($pdo, array_column($orders, 'source_key')); $orders = drive_test_merge_tracking($orders, $tracking); if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'assign_assessor') { $sourceKey = trim((string) ($_POST['source_key'] ?? '')); $targetValue = trim((string) ($_POST['target_assessor'] ?? '')); if ($sourceKey === '' || !preg_match('/^[a-f0-9]{40}$/', $sourceKey)) { throw new RuntimeException('Pedido de prueba inválido para asignar.'); } $targetKey = trim(mb_strtoupper($targetValue)); $targetKey = preg_replace('/\s+/', ' ', $targetKey) ?? $targetKey; $selectedAssessor = $targetKey !== '' ? ($assessors[$targetKey] ?? null) : null; if ($targetKey !== '' && !$selectedAssessor) { throw new RuntimeException('No se encontró la asesora seleccionada.'); } cc_test_upsert_assignee($pdo, $sourceKey, $selectedAssessor ? (int) $selectedAssessor['id'] : null); $noticeMessage = $selectedAssessor ? 'Pedido asignado a ' . $selectedAssessor['label'] . '.' : 'Pedido dejado sin asignar.'; $tracking = drive_test_fetch_tracking($pdo, array_column($orders, 'source_key')); $orders = drive_test_merge_tracking($orders, $tracking); } 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'); $callCounts = []; if (!empty($sourceKeys)) { $placeholders = implode(',', array_fill(0, count($sourceKeys), '?')); $stmtCalls = $pdo->prepare("SELECT pedido_id, COUNT(*) as total FROM historial_llamadas WHERE pedido_id IN ($placeholders) GROUP BY pedido_id"); $stmtCalls->execute($sourceKeys); $callCounts = $stmtCalls->fetchAll(PDO::FETCH_KEY_PAIR); } $todayStart = new DateTimeImmutable('today'); $todayEnd = $todayStart->setTime(23, 59, 59); $openStates = cc_test_open_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) { $order['estado'] = cc_test_normalize_state((string) ($order['estado'] ?? '')); $order['total_llamadas'] = (int) ($callCounts[$order['source_key']] ?? 0); $importDate = cc_test_parse_datetime($order['import_id'] ?? null); $proximaDate = cc_test_parse_datetime($order['proxima_llamada_at'] ?? null); $order['es_nuevo_hoy'] = $importDate ? $importDate->format('Y-m-d') === $todayStart->format('Y-m-d') : false; $order['es_pendiente_hoy'] = in_array($order['estado'], $openStates, true) && ($proximaDate === null || $proximaDate <= $todayEnd); $order['es_cerrado'] = in_array($order['estado'], $closedStates, true); 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']++; if ($order['es_nuevo_hoy']) { $stats['nuevos_hoy']++; } if ($order['es_pendiente_hoy']) { $stats['pendientes_hoy']++; } if (in_array($order['estado'], cc_test_confirmed_states(), true)) { $stats['confirmados']++; } if ($order['estado'] === 'SE ENVIO NUMERO DE CUENTA') { $stats['seguimiento']++; } if ($order['estado'] === 'OBSERVADO') { $stats['observados']++; } if ($order['es_cerrado']) { $stats['cerrados']++; } } unset($order); $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 { return match ($view) { 'pendientes_hoy' => (bool) ($order['es_pendiente_hoy'] ?? false), 'nuevos_hoy' => (bool) ($order['es_nuevo_hoy'] ?? false), 'confirmados' => in_array(($order['estado'] ?? ''), cc_test_confirmed_states(), true), 'seguimiento' => ($order['estado'] ?? '') === 'SE ENVIO NUMERO DE CUENTA', 'observados' => ($order['estado'] ?? '') === 'OBSERVADO', 'cerrados' => (bool) ($order['es_cerrado'] ?? false), default => true, }; })); usort($visibleOrders, static function (array $a, array $b) use ($view): int { if ($view === 'pendientes_hoy') { $aDue = cc_test_parse_datetime($a['proxima_llamada_at'] ?? null); $bDue = cc_test_parse_datetime($b['proxima_llamada_at'] ?? null); if ($aDue && $bDue && $aDue != $bDue) { return $aDue <=> $bDue; } if ($aDue && !$bDue) { return 1; } if (!$aDue && $bDue) { return -1; } } return cc_test_order_time($b) <=> cc_test_order_time($a); }); } catch (Throwable $exception) { $errorMessage = $exception->getMessage(); } require_once 'layout_header.php'; ?>

Base de Datos Pedidos

Drive detectado: filas Extracción: desde fila Auto actualización: cada 10 min Ver vista previa Drive
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); ?>

Rendimiento por asesora

Pendientes = POR LLAMAR / DEVOLVER LLAMADA / OBSERVADO
KARINA
Pendientes:
Cupo restante ()
ESTEFANYA
Pendientes:
Cupo restante ()
Asigna pedidos sin asignar hasta completar el cupo.

Estados disponibles: Por llamar, Devolver llamada, Observado, Se envió número de cuenta, Confirmado contraentrega, Confirmado envío, Cancelado y Repetido.

pedidos en esta bandeja
N° Pedido Cliente Ubicación editable Pedido Gestión Acciones
No hay pedidos en esta bandeja por ahora.
ID Drive:
Celular:
DNI:
Dirección:
Referencia:
Departamento:
Provincia:
Distrito:
DISTRITO 1:
Cantidad: ·
Ingreso Drive:
Observación Drive:
llamadas Seguimiento
Número de cuenta enviado hace día ·
Próxima llamada:
Entrega programada:
Última gestión:
Nota:
Asignar a asesora
WhatsApp
Llamada preparada para AirDroid

Número listo para copiar

Pedido
-
Cliente
-
Número
-