Autosave: 20260525-012131

This commit is contained in:
Flatlogic Bot 2026-05-25 01:22:01 +00:00
parent 2c3b594aa0
commit 00437625c8
5 changed files with 692 additions and 260 deletions

View File

@ -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;

View File

@ -1,301 +1,475 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'includes/drive_test_orders.php';
// Simulamos que eres la asesora con ID 1 (puedes cambiarlo luego por el ID real de la sesión)
$mi_id_asesora = $_SESSION['user_id'] ?? 1;
$pageTitle = 'Call Center de Prueba | Importar Drive (Test)';
$pageDescription = 'Panel de seguimiento para confirmar pedidos usando únicamente los últimos 10 registros de Importar Drive (Test).';
$db = db();
// 1. Obtener estadísticas rápidas para la asesora
$stmt_stats = $db->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';
?>
<div class="container-fluid mt-4">
<div class="row mb-4">
<div class="col-12">
<h2 class="fw-bold"><i class="bi bi-headset text-primary"></i> Mi Panel de Gestión (Call Center)</h2>
<p class="text-muted">Haz clic en las tarjetas para filtrar los pedidos por estado.</p>
<main class="container-fluid py-4">
<section class="mb-4">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
<div>
<h1 class="h2 fw-bold mb-1"><i class="bi bi-headset text-primary"></i> Call Center de prueba</h1>
<p class="text-muted mb-0">Este panel usa solo los últimos 10 registros de <strong>Importar Drive (Test)</strong> para confirmar pedidos sin tocar pedidos reales.</p>
</div>
<div class="d-flex flex-wrap gap-2">
<span class="badge rounded-pill text-bg-light border px-3 py-2">Fuente: Importar Drive (Test)</span>
<span class="badge rounded-pill text-bg-light border px-3 py-2">Hoja detectada: <?php echo (int) $totalRows; ?> registros</span>
<a href="test_importar_drive.php" class="btn btn-outline-primary btn-sm">Ver vista previa</a>
</div>
</div>
</div>
</section>
<!-- Tarjetas de Resumen -->
<div class="row mb-4">
<div class="col-md-3">
<a href="?view=todos" class="text-decoration-none">
<div class="card border-0 shadow-sm <?php echo $view == 'todos' ? 'ring-active bg-dark text-white' : 'bg-white text-dark'; ?>">
<div class="card-body">
<h6 class="card-title opacity-75">Total Asignados</h6>
<h2 class="mb-0 fw-bold"><?php echo $stats['total'] ?? 0; ?></h2>
</div>
</div>
</a>
</div>
<div class="col-md-3">
<a href="?view=pendientes" class="text-decoration-none">
<div class="card border-0 shadow-sm <?php echo $view == 'pendientes' ? 'ring-active bg-warning text-dark' : 'bg-white text-dark border-start border-warning border-4'; ?>">
<div class="card-body">
<h6 class="card-title opacity-75">Por Llamar</h6>
<h2 class="mb-0 fw-bold"><?php echo $stats['pendientes'] ?? 0; ?></h2>
</div>
</div>
</a>
</div>
<div class="col-md-3">
<a href="?view=reintentos" class="text-decoration-none">
<div class="card border-0 shadow-sm <?php echo $view == 'reintentos' ? 'ring-active bg-info text-white' : 'bg-white text-dark border-start border-info border-4'; ?>">
<div class="card-body">
<h6 class="card-title opacity-75">Reintentos</h6>
<h2 class="mb-0 fw-bold"><?php echo $stats['reintentos'] ?? 0; ?></h2>
</div>
</div>
</a>
</div>
<div class="col-md-3">
<a href="?view=cerrados" class="text-decoration-none">
<div class="card border-0 shadow-sm <?php echo $view == 'cerrados' ? 'ring-active bg-success text-white' : 'bg-white text-dark border-start border-success border-4'; ?>">
<div class="card-body">
<h6 class="card-title opacity-75">Ventas Cerradas</h6>
<h2 class="mb-0 fw-bold"><?php echo $stats['cerrados'] ?? 0; ?></h2>
</div>
</div>
</a>
</div>
</div>
<?php if ($errorMessage !== null): ?>
<section class="alert alert-danger" role="alert">
<strong>No se pudo cargar el panel de Call Center de prueba.</strong><br>
<?php echo htmlspecialchars($errorMessage); ?>
</section>
<?php else: ?>
<section class="row g-3 mb-4">
<div class="col-md-6 col-xl-3">
<a href="?view=todos" class="text-decoration-none">
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'todos' ? 'bg-dark text-white' : 'bg-white'; ?>">
<div class="card-body">
<div class="small text-uppercase opacity-75 mb-2">Total visibles</div>
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['total']; ?></div>
</div>
</article>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="?view=pendientes" class="text-decoration-none">
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'pendientes' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
<div class="card-body">
<div class="small text-uppercase text-muted mb-2">Por llamar</div>
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['pendientes']; ?></div>
</div>
</article>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="?view=reintentos" class="text-decoration-none">
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'reintentos' ? 'bg-info-subtle border border-info' : 'bg-white'; ?>">
<div class="card-body">
<div class="small text-uppercase text-muted mb-2">Reintento</div>
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['reintentos']; ?></div>
</div>
</article>
</a>
</div>
<div class="col-md-6 col-xl-3">
<a href="?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>
</section>
<style>
.ring-active { box-shadow: 0 0 0 3px rgba(0,0,0,0.1), 0 8px 15px rgba(0,0,0,0.1) !important; transform: translateY(-2px); transition: all 0.3s; }
.card:hover { transform: translateY(-3px); transition: all 0.3s; }
</style>
<!-- Tabla de Gestión -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold"><?php echo $titulo_tabla; ?></h5>
<span class="badge bg-light text-dark border"><?php echo count($pedidos); ?> registros</span>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Cliente / Celular</th>
<th>Ubicación</th>
<th>Producto</th>
<th>Monto</th>
<th>Estado Actual</th>
<th class="text-center">Acciones de Gestión</th>
</tr>
</thead>
<tbody>
<?php if (empty($pedidos)): ?>
<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>
<h2 class="h5 fw-bold mb-1"><?php echo htmlspecialchars($allowedViews[$view]['titulo']); ?></h2>
<p class="text-muted small mb-0">Datos tomados solo desde <strong>Importar Drive (Test)</strong>: nombre, celular, dirección, referencia, distrito, ciudad, producto, cantidad, precio, método, sede / ID, DNI y observaciones.</p>
</div>
<span class="badge bg-light text-dark border"><?php echo count($visibleOrders); ?> pedidos</span>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<td colspan="6" class="text-center py-5 text-muted">
<i class="bi bi-inbox fs-1 d-block mb-2"></i>
No hay pedidos en esta categoría.
</td>
<th>Cliente y contacto</th>
<th>Ubicación</th>
<th>Pedido</th>
<th>Seguimiento</th>
<th class="text-center">Acciones</th>
</tr>
<?php else: ?>
<?php foreach ($pedidos as $p): ?>
</thead>
<tbody>
<?php if (empty($visibleOrders)): ?>
<tr>
<td>
<div class="fw-bold"><?php echo htmlspecialchars($p['nombre_completo'] ?? 'Sin nombre'); ?></div>
<div class="small text-muted">DNI: <?php echo htmlspecialchars($p['dni_cliente'] ?: ($p['dni'] ?: 'N/A')); ?></div>
<a href="tel:<?php echo $p['celular']; ?>" class="text-decoration-none">
<i class="bi bi-telephone-fill small"></i> <?php echo htmlspecialchars($p['celular']); ?>
</a>
<td colspan="5" class="text-center py-5 text-muted">
<i class="bi bi-inbox fs-2 d-block mb-2"></i>
No hay pedidos en esta vista del módulo de prueba.
</td>
<td>
<div class="small fw-bold text-primary"><?php echo htmlspecialchars($p['sede_envio'] ?? 'N/A'); ?></div>
<div class="small text-truncate" style="max-width: 150px;" title="<?php echo htmlspecialchars($p['direccion_exacta'] ?? ''); ?>">
<?php echo htmlspecialchars($p['direccion_exacta'] ?? '-'); ?>
</div>
</td>
<td>
<div class="small"><?php echo htmlspecialchars($p['producto'] ?? '-'); ?></div>
<div class="badge bg-light text-dark border">Cant: <?php echo $p['cantidad'] ?? 1; ?></div>
</td>
<td>
<span class="fw-bold text-success">S/ <?php echo number_format($p['monto_total'], 2); ?></span>
</td>
<td>
<?php
$badge_class = 'bg-secondary';
if ($p['estado'] == 'Gestion') $badge_class = 'bg-warning text-dark';
if ($p['estado'] == 'NO CONTESTO, DEVOLVER LLAMADA') $badge_class = 'bg-info';
if ($p['estado'] == 'COMPLETADO ✅') $badge_class = 'bg-success';
?>
<span class="badge <?php echo $badge_class; ?>">
<?php echo htmlspecialchars($p['estado']); ?>
</span>
</td>
<td class="text-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#modalDetalle<?php echo $p['id']; ?>" title="Ver Detalles">
<i class="bi bi-eye"></i>
</button>
<?php if ($p['estado'] != 'COMPLETADO ✅'): ?>
<button onclick="cambiarEstado(<?php echo $p['id']; ?>, 'COMPLETADO ✅')" class="btn btn-sm btn-success" title="Venta Confirmada">
<i class="bi bi-check-circle"></i> Confirmar
</button>
</tr>
<?php else: ?>
<?php foreach ($visibleOrders as $order): ?>
<?php
$modalId = 'modalDriveTest' . substr($order['source_key'], 0, 10);
$badgeClass = cc_test_badge_class($order['estado']);
$precioLabel = cc_test_format_price($order['precio'] ?? '');
$driveDateLabel = cc_test_format_drive_datetime($order['import_id'] ?? '');
?>
<tr>
<td>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['nombre'])); ?></div>
<div class="small text-muted">Pedido: <?php echo htmlspecialchars(cc_test_display_value($order['codigo'], 'Sin código')); ?></div>
<div class="small text-muted">Drive: <?php echo htmlspecialchars($driveDateLabel); ?></div>
<div class="small text-muted">DNI: <?php echo htmlspecialchars(cc_test_display_value($order['dni'])); ?></div>
<?php if (!empty($order['telefono_url'])): ?>
<a href="<?php echo htmlspecialchars($order['telefono_url']); ?>" class="text-decoration-none small">
<i class="bi bi-telephone-fill"></i> <?php echo htmlspecialchars(cc_test_display_value($order['celular'])); ?>
</a>
<?php else: ?>
<div class="small text-muted">Celular: No registrado</div>
<?php endif; ?>
<button onclick="cambiarEstado(<?php echo $p['id']; ?>, 'NO CONTESTO, DEVOLVER LLAMADA')" class="btn btn-sm btn-outline-info" title="No contestó">
<i class="bi bi-telephone-x"></i> No contestó
</button>
<button onclick="cambiarEstado(<?php echo $p['id']; ?>, 'CANCELADO')" class="btn btn-sm btn-outline-danger" title="Rechazado">
<i class="bi bi-x-circle"></i> Rechazar
</button>
</div>
</td>
<td>
<div class="fw-semibold text-primary"><?php echo htmlspecialchars(cc_test_display_value($order['distrito'])); ?></div>
<div class="small"><?php echo htmlspecialchars(cc_test_display_value($order['direccion'])); ?></div>
<div class="small text-muted">Ref.: <?php echo htmlspecialchars(cc_test_display_value($order['referencia'])); ?></div>
<div class="small text-muted"><?php echo htmlspecialchars(cc_test_display_value($order['ciudad'])); ?> · <?php echo htmlspecialchars(cc_test_display_value($order['pais'])); ?></div>
<?php if (!empty($order['coordenadas'])): ?>
<a href="<?php echo htmlspecialchars($order['coordenadas']); ?>" target="_blank" rel="noopener noreferrer" class="btn btn-sm btn-link px-0 text-decoration-none">Ver ubicación</a>
<?php endif; ?>
</td>
<td>
<div class="small fw-semibold"><?php echo nl2br(htmlspecialchars(cc_test_display_value($order['producto']))); ?></div>
<div class="small text-muted">Cantidad: <?php echo htmlspecialchars(cc_test_display_value($order['cantidad'], '1')); ?> · Precio: <span class="text-success fw-bold"><?php echo htmlspecialchars($precioLabel); ?></span></div>
<div class="small text-muted">Método: <?php echo htmlspecialchars(cc_test_display_value($order['metodo'])); ?></div>
<div class="small text-muted">Sede / ID: <?php echo htmlspecialchars(cc_test_display_value($order['sede'])); ?></div>
</td>
<td>
<span class="badge <?php echo $badgeClass; ?> mb-2"><?php echo htmlspecialchars($order['estado']); ?></span>
<?php if ($order['total_llamadas'] > 0): ?>
<div class="mb-2">
<span class="badge bg-secondary" title="Intentos de llamada">
<i class="bi bi-telephone-outbound"></i> <?php echo (int) $order['total_llamadas']; ?> llamadas
</span>
</div>
<?php endif; ?>
<div class="small text-muted">Obs. Drive: <?php echo htmlspecialchars(cc_test_display_value($order['observaciones'], 'Sin observaciones')); ?></div>
<div class="small text-muted">Nota interna: <?php echo htmlspecialchars(cc_test_display_value($order['nota_seguimiento'], 'Sin nota')); ?></div>
</td>
<td class="text-center">
<div class="d-flex flex-column flex-md-row justify-content-center gap-2">
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#<?php echo htmlspecialchars($modalId); ?>">
<i class="bi bi-eye"></i> Ver
</button>
<?php if (!empty($order['telefono_url'])): ?>
<a href="<?php echo htmlspecialchars($order['telefono_url']); ?>"
class="btn btn-sm btn-outline-secondary"
onclick="registrarLlamada('<?php echo htmlspecialchars($order['source_key']); ?>')">
<i class="bi bi-telephone"></i> Llamar
</a>
<?php endif; ?>
<?php if (!empty($order['whatsapp_url'])): ?>
<a href="<?php echo htmlspecialchars($order['whatsapp_url']); ?>" target="_blank" rel="noopener noreferrer" class="btn btn-sm btn-outline-success">
<i class="bi bi-whatsapp"></i> WhatsApp
</a>
<?php endif; ?>
</div>
</td>
</tr>
<!-- Modal de Detalles -->
<div class="modal fade" id="modalDetalle<?php echo $p['id']; ?>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold">Detalles del Pedido #<?php echo $p['id']; ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<div class="modal fade" id="<?php echo htmlspecialchars($modalId); ?>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content border-0 shadow">
<div class="modal-header">
<div>
<h3 class="h5 mb-1"><?php echo htmlspecialchars(cc_test_display_value($order['nombre'])); ?></h3>
<div class="small text-muted">Pedido de prueba cargado desde Importar Drive (Test)</div>
</div>
<div class="modal-body text-start">
<div class="row g-3">
<div class="col-md-6">
<label class="text-muted small d-block">Nombre del Cliente</label>
<div class="fw-bold"><?php echo htmlspecialchars($p['nombre_completo']); ?></div>
</div>
<div class="col-md-3">
<label class="text-muted small d-block">DNI</label>
<div class="fw-bold"><?php echo htmlspecialchars($p['dni_cliente'] ?: ($p['dni'] ?: 'N/A')); ?></div>
</div>
<div class="col-md-3">
<label class="text-muted small d-block">Celular</label>
<div class="fw-bold"><?php echo htmlspecialchars($p['celular']); ?></div>
</div>
<hr class="my-2">
<div class="col-md-4">
<label class="text-muted small d-block">Ciudad / Sede</label>
<div class="fw-bold"><?php echo htmlspecialchars($p['sede_envio'] ?? 'N/A'); ?></div>
</div>
<div class="col-md-4">
<label class="text-muted small d-block">Provincia / Distrito</label>
<div class="fw-bold"><?php echo htmlspecialchars($p['codigo_rastreo'] ?? 'N/A'); ?></div>
</div>
<div class="col-md-4">
<label class="text-muted small d-block">Método / Agencia</label>
<div class="fw-bold"><?php echo htmlspecialchars($p['agencia'] ?? 'N/A'); ?></div>
</div>
<div class="col-md-12">
<label class="text-muted small d-block">Dirección Exacta</label>
<div class="fw-bold"><?php echo htmlspecialchars($p['direccion_exacta'] ?? 'N/A'); ?></div>
</div>
<div class="col-md-12">
<label class="text-muted small d-block">Referencia</label>
<div class="fw-bold"><?php echo htmlspecialchars($p['referencia_domicilio'] ?? 'N/A'); ?></div>
</div>
<div class="col-md-12">
<label class="text-muted small d-block">Coordenadas / Link Maps</label>
<?php if (!empty($p['coordenadas'])): ?>
<a href="<?php echo htmlspecialchars($p['coordenadas']); ?>" target="_blank" class="btn btn-sm btn-link p-0">Ver en Google Maps</a>
<?php else: ?>
<div class="fw-bold">N/A</div>
<?php endif; ?>
</div>
<hr class="my-2">
<div class="col-md-6">
<label class="text-muted small d-block">Producto</label>
<div class="fw-bold"><?php echo htmlspecialchars($p['producto']); ?></div>
</div>
<div class="col-md-3">
<label class="text-muted small d-block">Cantidad</label>
<div class="fw-bold"><?php echo htmlspecialchars($p['cantidad'] ?? 1); ?></div>
</div>
<div class="col-md-3">
<label class="text-muted small d-block">Precio Total</label>
<div class="fw-bold text-success">S/ <?php echo number_format($p['monto_total'], 2); ?></div>
</div>
<div class="col-md-12">
<label class="text-muted small d-block">Observaciones / Notas</label>
<div class="p-2 bg-light rounded small">
<?php echo nl2br(htmlspecialchars($p['notas'] ?: ($p['nota_adicional'] ?: ($p['observacion'] ?: 'Sin observaciones')))); ?>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
</div>
<div class="modal-body">
<div class="row g-3 mb-3">
<div class="col-md-4">
<label class="text-muted small d-block">Código</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['codigo'], 'Sin código')); ?></div>
</div>
<div class="col-md-4">
<label class="text-muted small d-block">Fecha detectada en Drive</label>
<div class="fw-bold"><?php echo htmlspecialchars($driveDateLabel); ?></div>
</div>
<div class="col-md-4">
<label class="text-muted small d-block">Estado de seguimiento</label>
<div><span class="badge <?php echo $badgeClass; ?>"><?php echo htmlspecialchars($order['estado']); ?></span></div>
</div>
<div class="col-md-6">
<label class="text-muted small d-block">Celular</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['celular'])); ?></div>
</div>
<div class="col-md-6">
<label class="text-muted small d-block">DNI</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['dni'])); ?></div>
</div>
<div class="col-md-12">
<label class="text-muted small d-block">Dirección</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['direccion'])); ?></div>
</div>
<div class="col-md-6">
<label class="text-muted small d-block">Referencia</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['referencia'])); ?></div>
</div>
<div class="col-md-3">
<label class="text-muted small d-block">Distrito</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['distrito'])); ?></div>
</div>
<div class="col-md-3">
<label class="text-muted small d-block">Ciudad</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['ciudad'])); ?></div>
</div>
<div class="col-md-4">
<label class="text-muted small d-block">País</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['pais'])); ?></div>
</div>
<div class="col-md-4">
<label class="text-muted small d-block">Método</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['metodo'])); ?></div>
</div>
<div class="col-md-4">
<label class="text-muted small d-block">Sede / ID</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['sede'])); ?></div>
</div>
<div class="col-md-8">
<label class="text-muted small d-block">Producto</label>
<div class="fw-bold"><?php echo nl2br(htmlspecialchars(cc_test_display_value($order['producto']))); ?></div>
</div>
<div class="col-md-2">
<label class="text-muted small d-block">Cantidad</label>
<div class="fw-bold"><?php echo htmlspecialchars(cc_test_display_value($order['cantidad'], '1')); ?></div>
</div>
<div class="col-md-2">
<label class="text-muted small d-block">Precio</label>
<div class="fw-bold text-success"><?php echo htmlspecialchars($precioLabel); ?></div>
</div>
<div class="col-md-12">
<label class="text-muted small d-block">Observaciones del Drive</label>
<div class="p-3 bg-light rounded small"><?php echo nl2br(htmlspecialchars(cc_test_display_value($order['observaciones'], 'Sin observaciones'))); ?></div>
</div>
<div class="col-md-12">
<label class="text-muted small d-block">Coordenadas</label>
<?php if (!empty($order['coordenadas'])): ?>
<a href="<?php echo htmlspecialchars($order['coordenadas']); ?>" target="_blank" rel="noopener noreferrer" class="btn btn-sm btn-outline-secondary">Abrir ubicación</a>
<?php else: ?>
<div class="fw-bold">No registradas</div>
<?php endif; ?>
</div>
</div>
<div class="modal-footer">
<hr>
<div class="mb-3">
<h4 class="h6 fw-bold"><i class="bi bi-clock-history"></i> Historial de llamadas</h4>
<?php
$stmtHist = db()->prepare("SELECT h.*, u.nombre as asesor FROM historial_llamadas h LEFT JOIN usuarios u ON h.asesor_id = u.id WHERE h.pedido_id = ? ORDER BY h.fecha_llamada DESC");
$stmtHist->execute([$order['source_key']]);
$historial = $stmtHist->fetchAll(PDO::FETCH_ASSOC);
?>
<?php if (empty($historial)): ?>
<p class="text-muted small">No hay intentos de llamada registrados aún.</p>
<?php else: ?>
<div class="list-group list-group-flush border rounded">
<?php foreach ($historial as $h): ?>
<div class="list-group-item small py-2">
<div class="d-flex justify-content-between">
<span class="fw-bold"><?php echo htmlspecialchars($h['asesor'] ?? 'Desconocido'); ?></span>
<span class="text-muted"><?php echo date('d/m/Y H:i', strtotime($h['fecha_llamada'])); ?></span>
</div>
<div class="text-primary"><?php echo htmlspecialchars($h['resultado']); ?></div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<hr>
<div class="col-md-4">
<label for="estado-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Estado de seguimiento</label>
<select class="form-select" id="estado-<?php echo htmlspecialchars($order['source_key']); ?>">
<?php foreach (['POR LLAMAR', 'REINTENTO', 'CONFIRMADO'] as $estadoOption): ?>
<option value="<?php echo htmlspecialchars($estadoOption); ?>" <?php echo $order['estado'] === $estadoOption ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($estadoOption); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-8">
<label for="nota-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Nota interna de seguimiento</label>
<textarea class="form-control" id="nota-<?php echo htmlspecialchars($order['source_key']); ?>" rows="3" placeholder="Ejemplo: cliente pidió devolver la llamada en la tarde."><?php echo htmlspecialchars($order['nota_seguimiento']); ?></textarea>
</div>
</div>
</div>
<div class="modal-footer d-flex justify-content-between">
<div class="small text-muted">Los cambios se guardan solo en el módulo de prueba.</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
<a href="pedido_form.php?id=<?php echo $p['id']; ?>" class="btn btn-warning">Editar Pedido</a>
<button type="button" class="btn btn-primary" onclick="guardarSeguimiento('<?php echo htmlspecialchars($order['source_key']); ?>')">Guardar seguimiento</button>
</div>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</section>
<?php endif; ?>
</main>
<script>
function cambiarEstado(id, nuevoEstado) {
if (!confirm('¿Estás seguro de cambiar el estado a ' + nuevoEstado + '?')) return;
fetch('update_estado.php', {
function guardarSeguimiento(sourceKey) {
const estado = document.getElementById('estado-' + sourceKey)?.value || 'POR LLAMAR';
const nota = document.getElementById('nota-' + sourceKey)?.value || '';
const body = new URLSearchParams({
source_key: sourceKey,
estado: estado,
nota_seguimiento: nota
});
fetch('update_callcenter_test_tracking.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'id=' + id + '&estado=' + encodeURIComponent(nuevoEstado)
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: body.toString()
})
.then(response => response.text())
.then(response => response.json())
.then(data => {
location.reload();
if (!data.success) {
throw new Error(data.message || 'No se pudo guardar');
}
window.location.reload();
})
.catch(error => {
alert('Error al actualizar: ' + error);
alert(error.message || 'Ocurrió un error al guardar el seguimiento.');
});
}
function registrarLlamada(sourceKey) {
const body = new URLSearchParams({
pedido_id: sourceKey,
resultado: 'Llamada iniciada',
observacion: 'Clic en botón llamar desde el panel'
});
fetch('save_llamada.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: body.toString()
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('Llamada registrada correctamente');
// Opcional: recargar para actualizar el contador,
// pero como abre la app de llamadas, tal vez sea mejor no interrumpir.
// window.location.reload();
}
})
.catch(error => console.error('Error al registrar llamada:', error));
}
</script>
<?php include 'layout_footer.php'; ?>
<?php require_once 'layout_footer.php'; ?>

View File

@ -0,0 +1,145 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
function drive_test_normalize_header(string $header): string
{
$header = trim($header);
$header = preg_replace('/\s+/', ' ', $header ?? '');
$header = strtoupper((string) $header);
return $header;
}
function drive_test_get_cell(array $row, array $indexes, array $aliases, string $default = ''): string
{
foreach ($aliases as $alias) {
$normalized = drive_test_normalize_header($alias);
if (array_key_exists($normalized, $indexes)) {
return trim((string) ($row[$indexes[$normalized]] ?? $default));
}
}
return $default;
}
function drive_test_fetch_orders(int $limit = 10): array
{
$credentialsPath = __DIR__ . '/../google_credentials.json';
$spreadsheetId = '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c';
$range = 'A:Z';
if (!file_exists($credentialsPath)) {
throw new RuntimeException('No se encontró el archivo de credenciales de Google.');
}
$client = new Google\Client();
$client->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;
}

30
save_llamada.php Normal file
View File

@ -0,0 +1,30 @@
<?php
require_once 'db/config.php';
session_start();
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => 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()]);
}

View File

@ -0,0 +1,72 @@
<?php
session_start();
require_once 'db/config.php';
header('Content-Type: application/json; charset=utf-8');
if (!isset($_SESSION['user_id'])) {
http_response_code(403);
echo json_encode(['success' => 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.',
]);
}