diff --git a/db/migrations/073_create_callcenter_test_tracking_table.sql b/db/migrations/073_create_callcenter_test_tracking_table.sql new file mode 100644 index 00000000..2df8af09 --- /dev/null +++ b/db/migrations/073_create_callcenter_test_tracking_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS `callcenter_test_tracking` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `source_key` CHAR(40) NOT NULL, + `estado` VARCHAR(40) NOT NULL DEFAULT 'POR LLAMAR', + `nota_seguimiento` TEXT NULL, + `user_id` INT NULL, + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_callcenter_test_tracking_source_key` (`source_key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/gestiones_callcenter.php b/gestiones_callcenter.php index d3f1f0b6..934c3c13 100644 --- a/gestiones_callcenter.php +++ b/gestiones_callcenter.php @@ -1,301 +1,475 @@ prepare("SELECT - COUNT(*) as total, - SUM(CASE WHEN estado = 'Gestion' THEN 1 ELSE 0 END) as pendientes, - SUM(CASE WHEN estado = 'NO CONTESTO, DEVOLVER LLAMADA' THEN 1 ELSE 0 END) as reintentos, - SUM(CASE WHEN estado = 'COMPLETADO ✅' THEN 1 ELSE 0 END) as cerrados -FROM pedidos WHERE asesor_id = ?"); -$stmt_stats->execute([$mi_id_asesora]); -$stats = $stmt_stats->fetch(PDO::FETCH_ASSOC); - -// 2. Determinar qué vista mostrar $view = $_GET['view'] ?? 'pendientes'; -$where_clause = "AND estado = 'Gestion'"; -$titulo_tabla = "Pedidos Pendientes de Atención"; +$allowedViews = [ + 'pendientes' => ['estado' => 'POR LLAMAR', 'titulo' => 'Pedidos por llamar'], + 'reintentos' => ['estado' => 'REINTENTO', 'titulo' => 'Pedidos en reintento'], + 'confirmados' => ['estado' => 'CONFIRMADO', 'titulo' => 'Pedidos confirmados'], + 'todos' => ['estado' => null, 'titulo' => 'Todos los pedidos de prueba'], +]; -if ($view === 'reintentos') { - $where_clause = "AND estado = 'NO CONTESTO, DEVOLVER LLAMADA'"; - $titulo_tabla = "Reintentos (No contestaron)"; -} elseif ($view === 'cerrados') { - $where_clause = "AND estado = 'COMPLETADO ✅'"; - $titulo_tabla = "Ventas Cerradas (Confirmadas)"; -} elseif ($view === 'todos') { - $where_clause = ""; - $titulo_tabla = "Todos mis Pedidos Asignados"; +if (!isset($allowedViews[$view])) { + $view = 'pendientes'; } -// 3. Obtener la lista de pedidos según el filtro -$stmt_pedidos = $db->prepare("SELECT * FROM pedidos - WHERE asesor_id = ? - $where_clause - ORDER BY created_at DESC"); -$stmt_pedidos->execute([$mi_id_asesora]); -$pedidos = $stmt_pedidos->fetchAll(PDO::FETCH_ASSOC); +$errorMessage = null; +$orders = []; +$stats = [ + 'total' => 0, + 'pendientes' => 0, + 'reintentos' => 0, + 'confirmados' => 0, +]; +$visibleOrders = []; +$totalRows = 0; -include 'layout_header.php'; +try { + $preview = drive_test_fetch_orders(10); + $totalRows = (int) ($preview['total_rows'] ?? 0); + $orders = $preview['orders'] ?? []; + + $tracking = drive_test_fetch_tracking(db(), array_column($orders, 'source_key')); + $orders = drive_test_merge_tracking($orders, $tracking); + + // Obtener conteo de llamadas + $sourceKeys = array_column($orders, 'source_key'); + $callCounts = []; + if (!empty($sourceKeys)) { + $placeholders = implode(',', array_fill(0, count($sourceKeys), '?')); + $stmtCalls = db()->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); + } + + foreach ($orders as &$order) { + $order['total_llamadas'] = $callCounts[$order['source_key']] ?? 0; + $stats['total']++; + if ($order['estado'] === 'POR LLAMAR') { + $stats['pendientes']++; + } elseif ($order['estado'] === 'REINTENTO') { + $stats['reintentos']++; + } elseif ($order['estado'] === 'CONFIRMADO') { + $stats['confirmados']++; + } + } + + $expectedState = $allowedViews[$view]['estado']; + if ($expectedState === null) { + $visibleOrders = $orders; + } else { + $visibleOrders = array_values(array_filter($orders, static function (array $order) use ($expectedState): bool { + return ($order['estado'] ?? '') === $expectedState; + })); + } +} catch (Throwable $exception) { + $errorMessage = $exception->getMessage(); +} + +function cc_test_badge_class(string $estado): string +{ + return match ($estado) { + 'CONFIRMADO' => 'bg-success-subtle text-success-emphasis', + 'REINTENTO' => 'bg-info-subtle text-info-emphasis', + default => 'bg-warning-subtle text-warning-emphasis', + }; +} + +function cc_test_display_value(?string $value, string $fallback = 'No registrado'): string +{ + $value = trim((string) $value); + return $value !== '' ? $value : $fallback; +} + +function cc_test_format_price(?string $value): string +{ + $value = trim((string) $value); + return $value !== '' ? 'S/ ' . $value : 'No registrado'; +} + +function cc_test_format_drive_datetime(?string $value): string +{ + $value = trim((string) $value); + if ($value === '') { + return 'No registrada'; + } + + try { + return (new DateTimeImmutable($value))->format('d/m/Y h:i A'); + } catch (Throwable $exception) { + return $value; + } +} + +require_once 'layout_header.php'; ?> -
-
-
-

Mi Panel de Gestión (Call Center)

-

Haz clic en las tarjetas para filtrar los pedidos por estado.

+
+
+
+
+

Call Center de prueba

+

Este panel usa solo los últimos 10 registros de Importar Drive (Test) para confirmar pedidos sin tocar pedidos reales.

+
+
+ Fuente: Importar Drive (Test) + Hoja detectada: registros + Ver vista previa +
-
+ - - + + + +
+ + + + +
- - - -
-
-
- registros -
-
-
- - - - - - - - - - - - - +
+
+
+

+

Datos tomados solo desde Importar Drive (Test): nombre, celular, dirección, referencia, distrito, ciudad, producto, cantidad, precio, método, sede / ID, DNI y observaciones.

+
+ pedidos +
+
+
+
Cliente / CelularUbicaciónProductoMontoEstado ActualAcciones de Gestión
+ - + + + + + - - + + + - - - - - - + + + + + + + + + + - - -
- - No hay pedidos en esta categoría. - Cliente y contactoUbicaciónPedidoSeguimientoAcciones
-
-
DNI:
- - - +
+ + No hay pedidos en esta vista del módulo de prueba. -
-
- -
-
-
-
Cant:
-
- S/ - - - - - - -
- - - +
+
+
Pedido:
+
Drive:
+
DNI:
+ + + + + +
Celular: No registrado
- - - - - +
+
+
+
Ref.:
+
·
+ + Ver ubicación + +
+
+
Cantidad: · Precio:
+
Método:
+
Sede / ID:
+
+ + 0): ?> +
+ + llamadas + +
+ +
Obs. Drive:
+
Nota interna:
+
+
+ + + + Llamar + + + + + WhatsApp + + +
+
+
+ + + + +
-
-
- + + + - + diff --git a/includes/drive_test_orders.php b/includes/drive_test_orders.php new file mode 100644 index 00000000..47f5f48b --- /dev/null +++ b/includes/drive_test_orders.php @@ -0,0 +1,145 @@ +setAuthConfig($credentialsPath); + $client->addScope(Google\Service\Sheets::SPREADSHEETS_READONLY); + + $service = new Google\Service\Sheets($client); + $response = $service->spreadsheets_values->get($spreadsheetId, $range); + $values = $response->getValues(); + + if (empty($values)) { + return [ + 'headers' => [], + 'orders' => [], + 'total_rows' => 0, + ]; + } + + $headers = $values[0] ?? []; + $headerIndexes = []; + foreach ($headers as $index => $header) { + $normalized = drive_test_normalize_header((string) $header); + if ($normalized === '') { + $normalized = $index === 0 ? 'CODIGO' : 'COL_' . $index; + } + $headerIndexes[$normalized] = $index; + } + + $dataRows = array_slice($values, 1); + $previewRows = array_reverse(array_slice($dataRows, -$limit)); + $orders = []; + + foreach ($previewRows as $row) { + $codigo = trim((string) ($row[0] ?? '')); + $importId = drive_test_get_cell($row, $headerIndexes, ['ID']); + $nombre = drive_test_get_cell($row, $headerIndexes, ['NOMBRE']); + $celular = preg_replace('/\D+/', '', drive_test_get_cell($row, $headerIndexes, ['CELULAR'])); + $producto = drive_test_get_cell($row, $headerIndexes, ['PRODUCTO']); + $cantidad = drive_test_get_cell($row, $headerIndexes, ['CANTIDAD']); + $precio = drive_test_get_cell($row, $headerIndexes, ['PRECIO']); + $pais = drive_test_get_cell($row, $headerIndexes, ['PAIS']); + $coordenadas = drive_test_get_cell($row, $headerIndexes, ['COORDENADAS']); + $ciudad = drive_test_get_cell($row, $headerIndexes, ['CIUDAD']); + $metodo = drive_test_get_cell($row, $headerIndexes, ['METODO']); + $sede = drive_test_get_cell($row, $headerIndexes, ['SEDE / ID', 'SEDE/ID']); + $dni = drive_test_get_cell($row, $headerIndexes, ['N° DNI', 'N° DNI ', 'NRO DNI', 'DNI']); + $observaciones = drive_test_get_cell($row, $headerIndexes, ['OBSERVACIONES', 'OBSERVACIONES ']); + $direccion = drive_test_get_cell($row, $headerIndexes, ['DIRECION', 'DIRECCION']); + $referencia = drive_test_get_cell($row, $headerIndexes, ['REFERENCIA']); + $distrito = drive_test_get_cell($row, $headerIndexes, ['DISTRITO']); + $sourceKey = sha1(implode('|', [$codigo, $importId, $nombre, $celular, $producto])); + + $orders[] = [ + 'source_key' => $sourceKey, + 'codigo' => $codigo, + 'import_id' => $importId, + 'nombre' => $nombre, + 'direccion' => $direccion, + 'referencia' => $referencia, + 'distrito' => $distrito, + 'celular' => $celular, + 'producto' => $producto, + 'cantidad' => $cantidad, + 'precio' => $precio, + 'pais' => $pais, + 'coordenadas' => $coordenadas, + 'ciudad' => $ciudad, + 'metodo' => $metodo, + 'sede' => $sede, + 'dni' => $dni, + 'observaciones' => $observaciones, + ]; + } + + return [ + 'headers' => $headers, + 'orders' => $orders, + 'total_rows' => count($dataRows), + ]; +} + +function drive_test_fetch_tracking(PDO $pdo, array $sourceKeys): array +{ + if (empty($sourceKeys)) { + return []; + } + + $placeholders = implode(',', array_fill(0, count($sourceKeys), '?')); + $stmt = $pdo->prepare("SELECT source_key, estado, nota_seguimiento, updated_at FROM callcenter_test_tracking WHERE source_key IN ($placeholders)"); + $stmt->execute($sourceKeys); + + $tracking = []; + foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { + $tracking[$row['source_key']] = $row; + } + + return $tracking; +} + +function drive_test_merge_tracking(array $orders, array $tracking): array +{ + foreach ($orders as &$order) { + $current = $tracking[$order['source_key']] ?? null; + $order['estado'] = $current['estado'] ?? 'POR LLAMAR'; + $order['nota_seguimiento'] = $current['nota_seguimiento'] ?? ''; + $order['seguimiento_actualizado'] = $current['updated_at'] ?? null; + $digits = preg_replace('/\D+/', '', $order['celular'] ?? ''); + $order['whatsapp_url'] = $digits !== '' ? 'https://wa.me/' . $digits : ''; + $order['telefono_url'] = $digits !== '' ? 'tel:' . $digits : ''; + } + unset($order); + + return $orders; +} diff --git a/save_llamada.php b/save_llamada.php new file mode 100644 index 00000000..164c0da6 --- /dev/null +++ b/save_llamada.php @@ -0,0 +1,30 @@ + false, 'error' => 'Sesión no iniciada']); + exit; +} + +$pedido_id = $_POST['pedido_id'] ?? ''; +$resultado = $_POST['resultado'] ?? 'Llamada iniciada'; +$observacion = $_POST['observacion'] ?? ''; +$asesor_id = $_SESSION['user_id']; + +if (empty($pedido_id)) { + echo json_encode(['success' => false, 'error' => 'ID de pedido faltante']); + exit; +} + +try { + $pdo = db(); + $stmt = $pdo->prepare("INSERT INTO historial_llamadas (pedido_id, asesor_id, resultado, observacion) VALUES (?, ?, ?, ?)"); + $stmt->execute([$pedido_id, $asesor_id, $resultado, $observacion]); + + echo json_encode(['success' => true]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/update_callcenter_test_tracking.php b/update_callcenter_test_tracking.php new file mode 100644 index 00000000..155fd1bd --- /dev/null +++ b/update_callcenter_test_tracking.php @@ -0,0 +1,72 @@ + false, 'message' => 'No autorizado']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + http_response_code(405); + echo json_encode(['success' => false, 'message' => 'Método no permitido']); + exit; +} + +$sourceKey = trim((string) ($_POST['source_key'] ?? '')); +$estado = trim((string) ($_POST['estado'] ?? 'POR LLAMAR')); +$nota = trim((string) ($_POST['nota_seguimiento'] ?? '')); +$validStates = ['POR LLAMAR', 'REINTENTO', 'CONFIRMADO']; + +if ($sourceKey === '' || !preg_match('/^[a-f0-9]{40}$/', $sourceKey)) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Pedido de prueba inválido']); + exit; +} + +if (!in_array($estado, $validStates, true)) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Estado inválido']); + exit; +} + +if (mb_strlen($nota) > 3000) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'La nota es demasiado larga']); + exit; +} + +try { + $pdo = db(); + $stmt = $pdo->prepare( + 'INSERT INTO callcenter_test_tracking (source_key, estado, nota_seguimiento, user_id) + VALUES (:source_key, :estado, :nota, :user_id) + ON DUPLICATE KEY UPDATE + estado = VALUES(estado), + nota_seguimiento = VALUES(nota_seguimiento), + user_id = VALUES(user_id), + updated_at = CURRENT_TIMESTAMP' + ); + + $stmt->execute([ + ':source_key' => $sourceKey, + ':estado' => $estado, + ':nota' => $nota, + ':user_id' => (int) $_SESSION['user_id'], + ]); + + echo json_encode([ + 'success' => true, + 'message' => 'Seguimiento actualizado correctamente.', + 'estado' => $estado, + ]); +} catch (Throwable $exception) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'message' => 'No se pudo guardar el seguimiento.', + ]); +}