Manual code backup after billing restore 2026-05-26
This commit is contained in:
parent
10a0221760
commit
a630d16063
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
node_modules/
|
||||
*/node_modules/
|
||||
*/build/
|
||||
|
||||
assets/uploads/
|
||||
|
||||
1349
call_center_pro.php
Normal file
1349
call_center_pro.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,14 @@ $departamentosContraentrega = array_keys($provinciasPorDepartamentoContraentrega
|
||||
$pageTitle = 'Call Center de Prueba | Bandejas de gestión';
|
||||
$pageDescription = 'Bandejas diarias de Call Center con estados reales, próxima llamada, fecha de entrega, historial y edición de datos del pedido importado desde Drive.';
|
||||
|
||||
if (!cc_test_current_user_can_access_module(db())) {
|
||||
http_response_code(403);
|
||||
require_once 'layout_header.php';
|
||||
echo "<div class='container py-5'><div class='alert alert-danger mb-0'>Acceso denegado.</div></div>";
|
||||
require_once 'layout_footer.php';
|
||||
exit();
|
||||
}
|
||||
|
||||
function cc_test_parse_datetime(?string $value): ?DateTimeImmutable
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
@ -121,11 +129,14 @@ if (!isset($allowedViews[$view])) {
|
||||
}
|
||||
|
||||
$errorMessage = null;
|
||||
$noticeMessage = null;
|
||||
$assessors = [];
|
||||
$orders = [];
|
||||
$visibleOrders = [];
|
||||
$modalsHtml = [];
|
||||
$totalRows = 0;
|
||||
$loadLimit = 10;
|
||||
$startRow = 5552;
|
||||
$catalogoProductos = [];
|
||||
$stats = [
|
||||
'total' => 0,
|
||||
@ -141,6 +152,7 @@ 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");
|
||||
@ -149,13 +161,36 @@ try {
|
||||
$catalogoProductos = [];
|
||||
}
|
||||
|
||||
$preview = drive_test_fetch_orders($loadLimit);
|
||||
$preview = drive_test_fetch_orders($loadLimit, $startRow);
|
||||
$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);
|
||||
}
|
||||
|
||||
$sourceKeys = array_column($orders, 'source_key');
|
||||
$callCounts = [];
|
||||
if (!empty($sourceKeys)) {
|
||||
@ -244,35 +279,31 @@ require_once 'layout_header.php';
|
||||
<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> Call Center de prueba</h1>
|
||||
<p class="text-muted mb-0">Ahora el panel trabaja por <strong>bandejas</strong>: <strong>Nuevos de hoy</strong>, <strong>Pendientes de hoy</strong>, <strong>Seguimiento</strong>, <strong>Confirmados</strong>, <strong>Observados</strong> y <strong>Cerrados</strong>. Así no se mezcla todo cuando entran pedidos diarios.</p>
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<a href="gestiones_callcenter.php" class="btn btn-sm <?php echo $view !== 'todos' ? 'btn-primary' : 'btn-outline-primary'; ?>">Bandeja principal</a>
|
||||
<a href="call_center_pro.php" class="btn btn-sm btn-outline-dark">Call Center Pro</a>
|
||||
</div>
|
||||
</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>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Carga operativa: últimos <?php echo (int) $loadLimit; ?> registros</span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Extracción: desde fila <?php echo (int) $startRow; ?></span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Auto actualización: cada 10 min</span>
|
||||
<a href="test_importar_drive.php" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if ($noticeMessage !== null): ?>
|
||||
<section class="alert alert-success" role="alert">
|
||||
<?php echo htmlspecialchars($noticeMessage); ?>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
<?php if ($errorMessage !== null): ?>
|
||||
<section class="alert alert-danger" role="alert">
|
||||
<strong>No se pudo cargar el panel.</strong><br>
|
||||
<?php echo htmlspecialchars($errorMessage); ?>
|
||||
</section>
|
||||
<?php else: ?>
|
||||
<section class="alert alert-primary border-0 shadow-sm mb-4">
|
||||
<div class="row g-3 align-items-center">
|
||||
<div class="col-lg-8">
|
||||
<h2 class="h5 fw-bold mb-2">Flujo recomendado ya listo para prueba</h2>
|
||||
<p class="mb-0">Los pedidos nuevos entran desde Drive. Luego tú gestionas cada cliente con estado, próxima llamada y observaciones internas. Los casos abiertos siguen apareciendo en <strong>Pendientes de hoy</strong>, <strong>Seguimiento</strong> o <strong>Observados</strong> hasta que los cierres o confirmes.</p>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="small text-muted">Campos editables del módulo</div>
|
||||
<div class="fw-semibold">Dirección, referencia, departamento, provincia, distrito, DNI y observaciones</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="row g-3 mb-4">
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="?view=pendientes_hoy" class="text-decoration-none">
|
||||
@ -351,7 +382,6 @@ require_once 'layout_header.php';
|
||||
<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.</p>
|
||||
<p class="small text-primary mb-0"><i class="bi bi-phone"></i> El botón <strong>Llamar / AirDroid</strong> registra el intento, copia el número y muestra la ayuda visual para pegar en tu app de AirDroid.</p>
|
||||
</div>
|
||||
<span class="badge bg-light text-dark border"><?php echo count($visibleOrders); ?> pedidos en esta bandeja</span>
|
||||
</div>
|
||||
@ -436,6 +466,20 @@ require_once 'layout_header.php';
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="d-flex flex-column gap-2 align-items-stretch">
|
||||
<?php if (in_array($_SESSION['user_role'] ?? '', ['Administrador', 'admin'], true)): ?>
|
||||
<form method="post" class="border rounded-3 bg-light p-2 text-start shadow-sm">
|
||||
<input type="hidden" name="action" value="assign_assessor">
|
||||
<input type="hidden" name="source_key" value="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<div class="small text-uppercase text-muted fw-semibold mb-1">Asignar a asesora</div>
|
||||
<select name="target_assessor" class="form-select form-select-sm mb-2">
|
||||
<option value=""<?php echo empty($order['user_id']) ? ' selected' : ''; ?>>Sin asignar</option>
|
||||
<?php foreach ($assessors as $assessorKey => $assessor): ?>
|
||||
<option value="<?php echo htmlspecialchars($assessorKey); ?>"<?php echo ((int) ($order['user_id'] ?? 0) === (int) ($assessor['id'] ?? 0)) ? ' selected' : ''; ?>><?php echo htmlspecialchars($assessor['label']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-sm btn-primary w-100">Asignar pedido</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['telefono_url'])): ?>
|
||||
<button
|
||||
type="button"
|
||||
@ -523,8 +567,8 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
<span class="badge bg-info-subtle text-info-emphasis">Producto del sistema, cantidad y precio</span>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-12 col-lg-6">
|
||||
<label for="confirmacion_producto-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Producto</label>
|
||||
<select class="form-select w-100" id="confirmacion_producto-<?php echo htmlspecialchars($order['source_key']); ?>" title="Seleccione un producto del sistema">
|
||||
<option value="">Seleccione un producto del sistema</option>
|
||||
@ -532,7 +576,6 @@ require_once 'layout_header.php';
|
||||
<option value="<?php echo htmlspecialchars((string) ($catalogoProducto['nombre'] ?? '')); ?>" <?php echo ((string) ($order['confirmacion_producto'] ?? '') === (string) ($catalogoProducto['nombre'] ?? '')) ? 'selected' : ''; ?>><?php echo htmlspecialchars((string) ($catalogoProducto['nombre'] ?? '')); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="form-text text-break" id="confirmacion_producto_resumen-<?php echo htmlspecialchars($order['source_key']); ?>">Producto seleccionado: <?php echo htmlspecialchars((string) ($order['confirmacion_producto'] ?? 'Sin producto seleccionado')); ?></div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<label for="confirmacion_cantidad-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Cantidad</label>
|
||||
@ -543,6 +586,34 @@ require_once 'layout_header.php';
|
||||
<input type="text" class="form-control" id="confirmacion_precio-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars((string) ($order['confirmacion_precio'] ?? '')); ?>" placeholder="S/ 0.00" inputmode="decimal">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm js-toggle-confirmacion-extra" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">Agregar producto adicional</button>
|
||||
</div>
|
||||
<div class="border rounded-3 bg-light p-3 mt-3 d-none js-confirmacion-extra-block" id="confirmacion_extra_block-<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||||
<div class="fw-semibold">Producto adicional distinto</div>
|
||||
<button type="button" class="btn btn-link text-danger text-decoration-none p-0 js-remove-confirmacion-extra" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">Quitar</button>
|
||||
</div>
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-12 col-lg-6">
|
||||
<label for="confirmacion_producto_extra-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Producto adicional</label>
|
||||
<select class="form-select w-100" id="confirmacion_producto_extra-<?php echo htmlspecialchars($order['source_key']); ?>" title="Seleccione un producto adicional">
|
||||
<option value="">Seleccione un producto adicional</option>
|
||||
<?php foreach ($catalogoProductos as $catalogoProducto): ?>
|
||||
<option value="<?php echo htmlspecialchars((string) ($catalogoProducto['nombre'] ?? '')); ?>" <?php echo ((string) ($order['confirmacion_producto_extra'] ?? '') === (string) ($catalogoProducto['nombre'] ?? '')) ? 'selected' : ''; ?>><?php echo htmlspecialchars((string) ($catalogoProducto['nombre'] ?? '')); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<label for="confirmacion_cantidad_extra-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Cantidad</label>
|
||||
<input type="text" class="form-control" id="confirmacion_cantidad_extra-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars((string) ($order['confirmacion_cantidad_extra'] ?? '')); ?>" placeholder="Cantidad">
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<label for="confirmacion_precio_extra-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Precio</label>
|
||||
<input type="text" class="form-control" id="confirmacion_precio_extra-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars((string) ($order['confirmacion_precio_extra'] ?? '')); ?>" placeholder="S/ 0.00" inputmode="decimal">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
@ -970,17 +1041,56 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
document.querySelectorAll('select[id^="confirmacion_producto-"]').forEach(select => {
|
||||
const sourceKey = select.id.replace('confirmacion_producto-', '');
|
||||
const resumen = document.getElementById('confirmacion_producto_resumen-' + sourceKey);
|
||||
const syncResumen = () => {
|
||||
const syncTitle = () => {
|
||||
const selectedOption = select.selectedOptions && select.selectedOptions[0] ? select.selectedOptions[0] : null;
|
||||
const label = selectedOption && selectedOption.textContent ? selectedOption.textContent.trim() : (select.value || 'Sin producto seleccionado');
|
||||
if (resumen) {
|
||||
resumen.textContent = 'Producto seleccionado: ' + label;
|
||||
}
|
||||
select.title = label;
|
||||
};
|
||||
syncResumen();
|
||||
select.addEventListener('change', syncResumen);
|
||||
syncTitle();
|
||||
select.addEventListener('change', syncTitle);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.js-toggle-confirmacion-extra').forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const sourceKey = button.dataset.sourceKey || '';
|
||||
const block = document.getElementById('confirmacion_extra_block-' + sourceKey);
|
||||
if (!block) return;
|
||||
block.classList.remove('d-none');
|
||||
button.classList.add('d-none');
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.js-remove-confirmacion-extra').forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const sourceKey = button.dataset.sourceKey || '';
|
||||
const block = document.getElementById('confirmacion_extra_block-' + sourceKey);
|
||||
const toggle = document.querySelector('.js-toggle-confirmacion-extra[data-source-key="' + sourceKey + '"]');
|
||||
if (block) {
|
||||
block.classList.add('d-none');
|
||||
const productoExtra = document.getElementById('confirmacion_producto_extra-' + sourceKey);
|
||||
const cantidadExtra = document.getElementById('confirmacion_cantidad_extra-' + sourceKey);
|
||||
const precioExtra = document.getElementById('confirmacion_precio_extra-' + sourceKey);
|
||||
if (productoExtra) productoExtra.value = '';
|
||||
if (cantidadExtra) cantidadExtra.value = '';
|
||||
if (precioExtra) precioExtra.value = '';
|
||||
}
|
||||
if (toggle) {
|
||||
toggle.classList.remove('d-none');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.js-confirmacion-extra-block').forEach(block => {
|
||||
const sourceKey = block.id.replace('confirmacion_extra_block-', '');
|
||||
const productoExtra = document.getElementById('confirmacion_producto_extra-' + sourceKey);
|
||||
const cantidadExtra = document.getElementById('confirmacion_cantidad_extra-' + sourceKey);
|
||||
const precioExtra = document.getElementById('confirmacion_precio_extra-' + sourceKey);
|
||||
const toggle = document.querySelector('.js-toggle-confirmacion-extra[data-source-key="' + sourceKey + '"]');
|
||||
const hasValue = [productoExtra, cantidadExtra, precioExtra].some(input => (input?.value || '').trim() !== '');
|
||||
if (hasValue) {
|
||||
block.classList.remove('d-none');
|
||||
if (toggle) toggle.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -1005,7 +1115,10 @@ function guardarGestion(sourceKey, trigger) {
|
||||
precio: document.getElementById('precio-' + sourceKey)?.value || '',
|
||||
confirmacion_producto: document.getElementById('confirmacion_producto-' + sourceKey)?.value || '',
|
||||
confirmacion_cantidad: document.getElementById('confirmacion_cantidad-' + sourceKey)?.value || '',
|
||||
confirmacion_precio: document.getElementById('confirmacion_precio-' + sourceKey)?.value || ''
|
||||
confirmacion_precio: document.getElementById('confirmacion_precio-' + sourceKey)?.value || '',
|
||||
confirmacion_producto_extra: document.getElementById('confirmacion_producto_extra-' + sourceKey)?.value || '',
|
||||
confirmacion_cantidad_extra: document.getElementById('confirmacion_cantidad_extra-' + sourceKey)?.value || '',
|
||||
confirmacion_precio_extra: document.getElementById('confirmacion_precio_extra-' + sourceKey)?.value || ''
|
||||
});
|
||||
|
||||
if (trigger) {
|
||||
@ -1218,6 +1331,10 @@ if (airDroidCopyButton) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.setInterval(function () {
|
||||
window.location.reload();
|
||||
}, 600000);
|
||||
</script>
|
||||
|
||||
<?php require_once 'layout_footer.php'; ?>
|
||||
|
||||
@ -45,6 +45,9 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
|
||||
`confirmacion_producto` VARCHAR(255) NULL,
|
||||
`confirmacion_cantidad` VARCHAR(50) NULL,
|
||||
`confirmacion_precio` VARCHAR(80) NULL,
|
||||
`confirmacion_producto_extra` VARCHAR(255) NULL,
|
||||
`confirmacion_cantidad_extra` VARCHAR(50) NULL,
|
||||
`confirmacion_precio_extra` VARCHAR(80) NULL,
|
||||
`proxima_llamada_at` DATETIME NULL,
|
||||
`fecha_entrega_programada` DATE NULL,
|
||||
`numero_cuenta_enviado_at` DATETIME NULL,
|
||||
@ -72,6 +75,9 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_producto', 'VARCHAR(255) NULL AFTER `precio`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_cantidad', 'VARCHAR(50) NULL AFTER `confirmacion_producto`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_precio', 'VARCHAR(80) NULL AFTER `confirmacion_cantidad`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_producto_extra', 'VARCHAR(255) NULL AFTER `confirmacion_precio`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_cantidad_extra', 'VARCHAR(50) NULL AFTER `confirmacion_producto_extra`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_precio_extra', 'VARCHAR(80) NULL AFTER `confirmacion_cantidad_extra`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'proxima_llamada_at', 'DATETIME NULL AFTER `confirmacion_precio`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'fecha_entrega_programada', 'DATE NULL AFTER `proxima_llamada_at`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'numero_cuenta_enviado_at', 'DATETIME NULL AFTER `fecha_entrega_programada`');
|
||||
@ -80,6 +86,107 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
|
||||
$checked = true;
|
||||
}
|
||||
|
||||
function cc_test_fetch_assessors(PDO $pdo, array $allowedNames = ['KARINA', 'ESTEFANYA']): array
|
||||
{
|
||||
$stmt = $pdo->prepare("SELECT id, username, nombre_asesor FROM users WHERE role = 'Asesor'");
|
||||
$stmt->execute();
|
||||
|
||||
$allowedLookup = array_fill_keys(array_map('strtoupper', $allowedNames), true);
|
||||
$byKey = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$key = trim(mb_strtoupper((string) ($row['nombre_asesor'] ?: $row['username'] ?: '')));
|
||||
$key = preg_replace('/\s+/', ' ', $key) ?? $key;
|
||||
if ($key === '' || (!empty($allowedLookup) && !isset($allowedLookup[$key]))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$byKey[$key] = [
|
||||
'id' => (int) $row['id'],
|
||||
'label' => trim((string) ($row['nombre_asesor'] ?: $row['username'] ?: ('Asesor #' . (int) $row['id']))),
|
||||
];
|
||||
}
|
||||
|
||||
return $byKey;
|
||||
}
|
||||
|
||||
function cc_test_normalize_user_key(?string $value): string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$value = preg_replace('/\s+/', ' ', $value) ?? $value;
|
||||
|
||||
return mb_strtoupper($value);
|
||||
}
|
||||
|
||||
function cc_test_allowed_module_user_keys(): array
|
||||
{
|
||||
return ['KARINA', 'ESTEFANYA'];
|
||||
}
|
||||
|
||||
function cc_test_is_allowed_module_user(?string $role = null, ?string $username = null, ?string $nombreAsesor = null): bool
|
||||
{
|
||||
$role = trim((string) $role);
|
||||
if (in_array($role, ['Administrador', 'admin'], true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$allowedLookup = array_fill_keys(cc_test_allowed_module_user_keys(), true);
|
||||
foreach ([$username, $nombreAsesor] as $candidate) {
|
||||
$key = cc_test_normalize_user_key($candidate);
|
||||
if ($key !== '' && isset($allowedLookup[$key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function cc_test_current_user_can_access_module(?PDO $pdo = null): bool
|
||||
{
|
||||
$role = $_SESSION['user_role'] ?? '';
|
||||
$username = $_SESSION['username'] ?? '';
|
||||
$nombreAsesor = null;
|
||||
|
||||
if ($pdo instanceof PDO && !empty($_SESSION['user_id']) && !in_array($role, ['Administrador', 'admin'], true)) {
|
||||
try {
|
||||
$stmt = $pdo->prepare('SELECT username, nombre_asesor FROM users WHERE id = ? LIMIT 1');
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$username = $row['username'] ?? $username;
|
||||
$nombreAsesor = $row['nombre_asesor'] ?? null;
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
// Fall back to the session values if the lookup fails.
|
||||
}
|
||||
}
|
||||
|
||||
return cc_test_is_allowed_module_user($role, $username, $nombreAsesor);
|
||||
}
|
||||
|
||||
function cc_test_upsert_assignee(PDO $pdo, string $sourceKey, ?int $userId): void
|
||||
{
|
||||
cc_test_ensure_tracking_table($pdo);
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO callcenter_test_tracking (source_key, user_id, ultima_gestion_at)
|
||||
VALUES (:source_key, :user_id, CURRENT_TIMESTAMP)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
user_id = VALUES(user_id),
|
||||
ultima_gestion_at = VALUES(ultima_gestion_at),
|
||||
updated_at = CURRENT_TIMESTAMP");
|
||||
|
||||
$stmt->bindValue(':source_key', $sourceKey, PDO::PARAM_STR);
|
||||
if ($userId === null) {
|
||||
$stmt->bindValue(':user_id', null, PDO::PARAM_NULL);
|
||||
} else {
|
||||
$stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
function cc_test_ensure_historial_llamadas_table(PDO $pdo): void
|
||||
{
|
||||
static $checked = false;
|
||||
@ -127,9 +234,11 @@ function cc_test_valid_states(): array
|
||||
];
|
||||
}
|
||||
|
||||
function cc_test_open_states(): array
|
||||
{
|
||||
return ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'];
|
||||
if (!function_exists('cc_test_open_states')) {
|
||||
function cc_test_open_states(): array
|
||||
{
|
||||
return ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'];
|
||||
}
|
||||
}
|
||||
|
||||
function cc_test_confirmed_states(): array
|
||||
@ -137,9 +246,11 @@ function cc_test_confirmed_states(): array
|
||||
return ['CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO'];
|
||||
}
|
||||
|
||||
function cc_test_closed_states(): array
|
||||
{
|
||||
return ['CANCELADO', 'REPETIDO'];
|
||||
if (!function_exists('cc_test_closed_states')) {
|
||||
function cc_test_closed_states(): array
|
||||
{
|
||||
return ['CANCELADO', 'REPETIDO'];
|
||||
}
|
||||
}
|
||||
|
||||
function cc_test_requires_delivery_date(string $estado): bool
|
||||
@ -200,17 +311,19 @@ function cc_test_state_label(string $estado): string
|
||||
};
|
||||
}
|
||||
|
||||
function cc_test_table_exists(PDO $pdo, string $tableName): bool
|
||||
{
|
||||
static $cache = [];
|
||||
if (array_key_exists($tableName, $cache)) {
|
||||
if (!function_exists('cc_test_table_exists')) {
|
||||
function cc_test_table_exists(PDO $pdo, string $tableName): bool
|
||||
{
|
||||
static $cache = [];
|
||||
if (array_key_exists($tableName, $cache)) {
|
||||
return $cache[$tableName];
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SHOW TABLES LIKE ?');
|
||||
$stmt->execute([$tableName]);
|
||||
$cache[$tableName] = (bool) $stmt->fetchColumn();
|
||||
return $cache[$tableName];
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SHOW TABLES LIKE ?');
|
||||
$stmt->execute([$tableName]);
|
||||
$cache[$tableName] = (bool) $stmt->fetchColumn();
|
||||
return $cache[$tableName];
|
||||
}
|
||||
|
||||
function cc_test_fetch_historial(PDO $pdo, string $pedidoId): array
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/callcenter_test_helpers.php';
|
||||
require_once __DIR__ . '/drive_test_orders.php';
|
||||
|
||||
function cc_test_allowed_states(): array
|
||||
@ -15,14 +16,18 @@ function cc_test_allowed_states(): array
|
||||
];
|
||||
}
|
||||
|
||||
function cc_test_open_states(): array
|
||||
{
|
||||
return ['POR LLAMAR', 'NO CONTESTA', 'DEVOLVER LLAMADA', 'REPROGRAMADO'];
|
||||
if (!function_exists('cc_test_open_states')) {
|
||||
function cc_test_open_states(): array
|
||||
{
|
||||
return ['POR LLAMAR', 'NO CONTESTA', 'DEVOLVER LLAMADA', 'REPROGRAMADO'];
|
||||
}
|
||||
}
|
||||
|
||||
function cc_test_closed_states(): array
|
||||
{
|
||||
return ['NO INTERESADO', 'NUMERO EQUIVOCADO', 'DUPLICADO'];
|
||||
if (!function_exists('cc_test_closed_states')) {
|
||||
function cc_test_closed_states(): array
|
||||
{
|
||||
return ['NO INTERESADO', 'NUMERO EQUIVOCADO', 'DUPLICADO'];
|
||||
}
|
||||
}
|
||||
|
||||
function cc_test_state_badge_class(string $estado): string
|
||||
@ -105,19 +110,21 @@ function cc_test_format_datetime_local(?string $value): string
|
||||
}
|
||||
}
|
||||
|
||||
function cc_test_table_exists(PDO $pdo, string $tableName): bool
|
||||
{
|
||||
static $cache = [];
|
||||
if (!function_exists('cc_test_table_exists')) {
|
||||
function cc_test_table_exists(PDO $pdo, string $tableName): bool
|
||||
{
|
||||
static $cache = [];
|
||||
|
||||
if (array_key_exists($tableName, $cache)) {
|
||||
return $cache[$tableName];
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SHOW TABLES LIKE ?');
|
||||
$stmt->execute([$tableName]);
|
||||
$cache[$tableName] = (bool) $stmt->fetchColumn();
|
||||
|
||||
if (array_key_exists($tableName, $cache)) {
|
||||
return $cache[$tableName];
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SHOW TABLES LIKE ?');
|
||||
$stmt->execute([$tableName]);
|
||||
$cache[$tableName] = (bool) $stmt->fetchColumn();
|
||||
|
||||
return $cache[$tableName];
|
||||
}
|
||||
|
||||
function cc_test_column_exists(PDO $pdo, string $tableName, string $columnName): bool
|
||||
|
||||
@ -22,7 +22,7 @@ function drive_test_get_cell(array $row, array $indexes, array $aliases, string
|
||||
return $default;
|
||||
}
|
||||
|
||||
function drive_test_fetch_orders(int $limit = 10): array
|
||||
function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552): array
|
||||
{
|
||||
$credentialsPath = __DIR__ . '/../google_credentials.json';
|
||||
$spreadsheetId = '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c';
|
||||
@ -59,7 +59,16 @@ function drive_test_fetch_orders(int $limit = 10): array
|
||||
}
|
||||
|
||||
$dataRows = array_slice($values, 1);
|
||||
$previewRows = array_reverse(array_slice($dataRows, -$limit));
|
||||
$startIndex = max(0, $startRow - 2);
|
||||
if ($startIndex > 0) {
|
||||
$dataRows = array_slice($dataRows, $startIndex);
|
||||
}
|
||||
|
||||
if ($limit > 0) {
|
||||
$previewRows = array_reverse(array_slice($dataRows, -$limit));
|
||||
} else {
|
||||
$previewRows = array_reverse($dataRows);
|
||||
}
|
||||
$orders = [];
|
||||
|
||||
foreach ($previewRows as $row) {
|
||||
@ -122,7 +131,7 @@ function drive_test_fetch_tracking(PDO $pdo, array $sourceKeys): array
|
||||
cc_test_ensure_tracking_table($pdo);
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($sourceKeys), '?'));
|
||||
$stmt = $pdo->prepare("SELECT source_key, estado, nota_seguimiento, direccion, referencia, agencia, sede_agencia, sede, ciudad, distrito, dni, observaciones, producto, cantidad, precio, confirmacion_producto, confirmacion_cantidad, confirmacion_precio, proxima_llamada_at, fecha_entrega_programada, numero_cuenta_enviado_at, ultima_gestion_at, updated_at FROM callcenter_test_tracking WHERE source_key IN ($placeholders)");
|
||||
$stmt = $pdo->prepare("SELECT source_key, estado, nota_seguimiento, user_id, direccion, referencia, agencia, sede_agencia, sede, ciudad, distrito, dni, observaciones, producto, cantidad, precio, confirmacion_producto, confirmacion_cantidad, confirmacion_precio, proxima_llamada_at, fecha_entrega_programada, numero_cuenta_enviado_at, ultima_gestion_at, updated_at FROM callcenter_test_tracking WHERE source_key IN ($placeholders)");
|
||||
$stmt->execute($sourceKeys);
|
||||
|
||||
$tracking = [];
|
||||
@ -141,6 +150,7 @@ function drive_test_merge_tracking(array $orders, array $tracking): array
|
||||
$current = $tracking[$order['source_key']] ?? null;
|
||||
$order['estado'] = $current['estado'] ?? 'POR LLAMAR';
|
||||
$order['nota_seguimiento'] = $current['nota_seguimiento'] ?? '';
|
||||
$order['user_id'] = array_key_exists('user_id', (array) $current) ? ($current['user_id'] !== null ? (int) $current['user_id'] : null) : null;
|
||||
$order['seguimiento_actualizado'] = $current['updated_at'] ?? null;
|
||||
$order['proxima_llamada_at'] = $current['proxima_llamada_at'] ?? null;
|
||||
$order['fecha_entrega_programada'] = $current['fecha_entrega_programada'] ?? null;
|
||||
|
||||
@ -10,7 +10,29 @@ if (!isset($_SESSION['user_id'])) {
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/callcenter_test_helpers.php';
|
||||
|
||||
$userRole = $_SESSION['user_role'] ?? '';
|
||||
$currentUserId = (int) ($_SESSION['user_id'] ?? 0);
|
||||
$currentUserUsername = $_SESSION['username'] ?? '';
|
||||
$currentUserNombreAsesor = null;
|
||||
|
||||
if ($currentUserId > 0 && !in_array($userRole, ['Administrador', 'admin'], true)) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare('SELECT username, nombre_asesor FROM users WHERE id = ? LIMIT 1');
|
||||
$stmt->execute([$currentUserId]);
|
||||
if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$currentUserUsername = $row['username'] ?? $currentUserUsername;
|
||||
$currentUserNombreAsesor = $row['nombre_asesor'] ?? null;
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
// Use the session values as a fallback.
|
||||
}
|
||||
}
|
||||
|
||||
$canAccessTestModule = cc_test_is_allowed_module_user($userRole, $currentUserUsername, $currentUserNombreAsesor);
|
||||
|
||||
// Define navigation items for each role
|
||||
$navItems = [
|
||||
@ -281,6 +303,12 @@ $navItems = [
|
||||
'text' => 'Call Center (Mis Gestiones)',
|
||||
'roles' => ['Administrador', 'admin', 'Asesor']
|
||||
],
|
||||
'call_center_pro' => [
|
||||
'url' => 'call_center_pro.php',
|
||||
'icon' => 'fa-headset',
|
||||
'text' => 'Call Center Pro',
|
||||
'roles' => ['Administrador', 'admin', 'Asesor']
|
||||
],
|
||||
'simular_asignacion' => [
|
||||
'url' => 'simular_asignacion.php',
|
||||
'icon' => 'fa-magic',
|
||||
@ -352,7 +380,12 @@ $navItems = [
|
||||
$currentPage = basename($_SERVER['PHP_SELF']);
|
||||
|
||||
foreach ($navItems as $key => $item):
|
||||
if (in_array($userRole, $item['roles'])):
|
||||
$isTestModule = ($key === 'modulo_pruebas_group');
|
||||
if ($isTestModule && !$canAccessTestModule) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isTestModule || in_array($userRole, $item['roles'])):
|
||||
if (isset($item['submenu'])):
|
||||
$isSubmenuActive = false;
|
||||
foreach ($item['submenu'] as $sub_item) {
|
||||
@ -366,7 +399,7 @@ $navItems = [
|
||||
<a href="#" class="nav-link"><i class="fas <?php echo $item['icon']; ?>"></i> <?php echo $item['text']; ?></a>
|
||||
<ul class="submenu <?php echo $isSubmenuActive ? 'show' : ''; ?>">
|
||||
<?php foreach ($item['submenu'] as $sub_item):
|
||||
if (in_array($userRole, $sub_item['roles'])):
|
||||
if ($isTestModule || in_array($userRole, $sub_item['roles'])):
|
||||
$isActive = $currentPage == $sub_item['url'] ? 'active' : '';
|
||||
?>
|
||||
<li class="nav-item">
|
||||
|
||||
@ -5,8 +5,8 @@ session_start();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Sesión no iniciada']);
|
||||
if (!isset($_SESSION['user_id']) || !cc_test_current_user_can_access_module(db())) {
|
||||
echo json_encode(['success' => false, 'error' => 'No autorizado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,19 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/callcenter_test_helpers.php';
|
||||
$db = db();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!cc_test_current_user_can_access_module($db)) {
|
||||
http_response_code(403);
|
||||
die('Acceso denegado.');
|
||||
}
|
||||
|
||||
// 1. Buscamos al primer usuario para la prueba
|
||||
$user = $db->query("SELECT id, username FROM users LIMIT 1")->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$user) {
|
||||
|
||||
@ -1,14 +1,20 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/callcenter_test_helpers.php';
|
||||
|
||||
$pageTitle = 'Importar Drive (Test)';
|
||||
$pageDescription = 'Vista previa de los últimos 10 pedidos detectados en Google Sheets antes de importarlos al CRM.';
|
||||
require_once __DIR__ . '/layout_header.php';
|
||||
|
||||
if (($_SESSION['user_role'] ?? '') !== 'Administrador' && ($_SESSION['user_role'] ?? '') !== 'admin') {
|
||||
echo "<div class='alert alert-danger'>Acceso denegado.</div>";
|
||||
if (!cc_test_current_user_can_access_module(db())) {
|
||||
http_response_code(403);
|
||||
require_once __DIR__ . '/layout_header.php';
|
||||
echo "<div class='container py-5'><div class='alert alert-danger mb-0'>Acceso denegado.</div></div>";
|
||||
require_once __DIR__ . '/layout_footer.php';
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/layout_header.php';
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$credentialsPath = __DIR__ . '/google_credentials.json';
|
||||
|
||||
@ -5,7 +5,7 @@ require_once 'includes/callcenter_test_helpers.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
if (!isset($_SESSION['user_id']) || !cc_test_current_user_can_access_module(db())) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'No autorizado']);
|
||||
exit;
|
||||
@ -64,6 +64,9 @@ try {
|
||||
$confirmacionProducto = cc_test_normalize_nullable_text('confirmacion_producto', 255);
|
||||
$confirmacionCantidad = cc_test_normalize_nullable_text('confirmacion_cantidad', 50);
|
||||
$confirmacionPrecio = cc_test_normalize_nullable_text('confirmacion_precio', 80);
|
||||
$confirmacionProductoExtra = cc_test_normalize_nullable_text('confirmacion_producto_extra', 255);
|
||||
$confirmacionCantidadExtra = cc_test_normalize_nullable_text('confirmacion_cantidad_extra', 50);
|
||||
$confirmacionPrecioExtra = cc_test_normalize_nullable_text('confirmacion_precio_extra', 80);
|
||||
|
||||
$pdo = db();
|
||||
cc_test_ensure_tracking_table($pdo);
|
||||
@ -133,6 +136,9 @@ try {
|
||||
confirmacion_producto,
|
||||
confirmacion_cantidad,
|
||||
confirmacion_precio,
|
||||
confirmacion_producto_extra,
|
||||
confirmacion_cantidad_extra,
|
||||
confirmacion_precio_extra,
|
||||
proxima_llamada_at,
|
||||
fecha_entrega_programada,
|
||||
numero_cuenta_enviado_at,
|
||||
@ -157,6 +163,9 @@ try {
|
||||
:confirmacion_producto,
|
||||
:confirmacion_cantidad,
|
||||
:confirmacion_precio,
|
||||
:confirmacion_producto_extra,
|
||||
:confirmacion_cantidad_extra,
|
||||
:confirmacion_precio_extra,
|
||||
:proxima_llamada_at,
|
||||
:fecha_entrega_programada,
|
||||
:numero_cuenta_enviado_at,
|
||||
@ -181,6 +190,9 @@ try {
|
||||
confirmacion_producto = VALUES(confirmacion_producto),
|
||||
confirmacion_cantidad = VALUES(confirmacion_cantidad),
|
||||
confirmacion_precio = VALUES(confirmacion_precio),
|
||||
confirmacion_producto_extra = VALUES(confirmacion_producto_extra),
|
||||
confirmacion_cantidad_extra = VALUES(confirmacion_cantidad_extra),
|
||||
confirmacion_precio_extra = VALUES(confirmacion_precio_extra),
|
||||
proxima_llamada_at = VALUES(proxima_llamada_at),
|
||||
fecha_entrega_programada = VALUES(fecha_entrega_programada),
|
||||
numero_cuenta_enviado_at = VALUES(numero_cuenta_enviado_at),
|
||||
@ -208,6 +220,9 @@ try {
|
||||
':confirmacion_producto' => $confirmacionProducto,
|
||||
':confirmacion_cantidad' => $confirmacionCantidad,
|
||||
':confirmacion_precio' => $confirmacionPrecio,
|
||||
':confirmacion_producto_extra' => $confirmacionProductoExtra,
|
||||
':confirmacion_cantidad_extra' => $confirmacionCantidadExtra,
|
||||
':confirmacion_precio_extra' => $confirmacionPrecioExtra,
|
||||
':proxima_llamada_at' => $proximaLlamada,
|
||||
':fecha_entrega_programada' => $fechaEntrega,
|
||||
':numero_cuenta_enviado_at' => $numeroCuentaEnviadoAt,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user