Autosave: 20260803-185453

This commit is contained in:
Flatlogic Bot 2026-08-03 18:53:12 +00:00
parent c2dc55080c
commit d2738a2add
18 changed files with 367 additions and 88 deletions

0
7796, Normal file
View File

View File

@ -267,6 +267,24 @@ function cc_test_import_time(array $order): int
return (int) ($order['id'] ?? 0);
}
function cc_test_order_priority_value(array $order): int
{
$codigo = trim((string) ($order['codigo'] ?? ''));
if ($codigo !== '') {
$digits = preg_replace('/\D+/', '', $codigo);
if ($digits !== '') {
return (int) $digits;
}
}
$sourceRow = (int) ($order['source_row'] ?? 0);
if ($sourceRow > 0) {
return $sourceRow;
}
return (int) ($order['id'] ?? 0);
}
function cc_test_followup_semaforo(array $order): ?array
{
if (cc_test_normalize_state((string) ($order['estado'] ?? '')) !== 'SE ENVIO NUMERO DE CUENTA') {
@ -845,6 +863,12 @@ try {
$bImport = cc_test_import_time($b);
if ($view === 'nuevos_hoy') {
$aOrderNumberValue = cc_test_order_priority_value($a);
$bOrderNumberValue = cc_test_order_priority_value($b);
if ($aOrderNumberValue !== $bOrderNumberValue) {
return $bOrderNumberValue <=> $aOrderNumberValue;
}
$aAssigned = cc_test_parse_datetime($a['assigned_at'] ?? null);
$bAssigned = cc_test_parse_datetime($b['assigned_at'] ?? null);
if ($aAssigned && $bAssigned && $aAssigned != $bAssigned) {
@ -859,24 +883,8 @@ try {
}
if ($view === 'ultimos_3_dias') {
$aOrderNumber = static function (array $order): int {
$codigo = trim((string) ($order['codigo'] ?? ''));
if ($codigo !== '') {
$digits = preg_replace("/[^0-9]+/", '', $codigo);
if ($digits !== '') {
return (int) $digits;
}
}
$sourceRow = (int) ($order['source_row'] ?? 0);
if ($sourceRow > 0) {
return $sourceRow;
}
return (int) ($order['id'] ?? 0);
};
$aOrderNumberValue = $aOrderNumber($a);
$bOrderNumberValue = $aOrderNumber($b);
$aOrderNumberValue = cc_test_order_priority_value($a);
$bOrderNumberValue = cc_test_order_priority_value($b);
if ($aOrderNumberValue !== $bOrderNumberValue) {
return $bOrderNumberValue <=> $aOrderNumberValue;
}
@ -1077,6 +1085,26 @@ if ($selectedAssessorFilter !== '') {
$callCenterParams['assessor'] = $selectedAssessorFilter;
}
$panelOrdersSignature = isset($visibleOrders) && is_array($visibleOrders) ? cc_test_panel_orders_signature($visibleOrders) : '';
if (!empty($_GET['ajax_refresh'])) {
header('Content-Type: application/json; charset=utf-8');
if ($errorMessage !== null) {
echo json_encode([
'success' => false,
'error' => $errorMessage,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} else {
echo json_encode([
'success' => true,
'signature' => $panelOrdersSignature,
'count' => $visibleOrdersTotalCount,
'view' => $view,
'store' => $storeKey,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
exit;
}
if ($isTuaniRecuperablesStore) {
$pageTitle = 'Carritos recuperables | Bandeja compartida';
$pageDescription = 'Bandeja compartida para que cualquier asesora gestione carritos recuperables con vistas de seguimiento y observados desde Drive.';
@ -1307,9 +1335,9 @@ require_once 'layout_header.php';
<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">Agregados: <?php echo (int) $agregadosCount; ?></span>
<span class="badge rounded-pill text-bg-light border px-3 py-2"><?php if ($usesIncrementalSync && $lastProcessedRow !== null): ?><?php echo htmlspecialchars(mb_strtoupper($storeLabel)); ?>: distribución desde fila <?php echo (int) ($storeConfig['startRow'] ?? $startRow); ?> · último procesado <?php echo (int) $lastProcessedRow; ?><?php else: ?>Extracción: desde fila <?php echo (int) $startRow; ?><?php endif; ?></span>
<span class="badge rounded-pill text-bg-light border px-3 py-2">Actualización manual</span>
<span class="badge rounded-pill text-bg-light border px-3 py-2">Actualización en vivo cada 10 min</span>
<a href="test_importar_drive.php?store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
<button type="button" class="btn btn-primary btn-sm" onclick="window.location.reload();"><i class="bi bi-arrow-clockwise me-1"></i>Actualizar pedidos</button>
<button type="button" class="btn btn-primary btn-sm" onclick="return ccCallCenterPanelRefreshNow(event);"><i class="bi bi-arrow-clockwise me-1"></i>Actualizar pedidos</button>
</div>
</div>
</section>
@ -2263,6 +2291,15 @@ require_once 'layout_header.php';
<script>
const provinciasPorDepartamento = <?php echo json_encode($provinciasPorDepartamentoContraentrega, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
const distritosPorProvincia = <?php echo json_encode($distritosPorProvinciaContraentrega, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
const ccCallCenterPanelBaseUrl = new URL(window.location.href);
ccCallCenterPanelBaseUrl.searchParams.delete('ajax_refresh');
ccCallCenterPanelBaseUrl.hash = '';
const ccCallCenterPanelRefreshUrl = new URL(ccCallCenterPanelBaseUrl.toString());
ccCallCenterPanelRefreshUrl.searchParams.set('ajax_refresh', '1');
const ccCallCenterPanelInitialSignature = <?php echo json_encode((string) ($panelOrdersSignature ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
const ccCallCenterPanelRefreshMs = 600000;
let ccCallCenterPanelRefreshInFlight = false;
let ccCallCenterPanelLastSignature = ccCallCenterPanelInitialSignature;
function getLocationControls(sourceKey) {
return {
@ -2633,7 +2670,7 @@ function eliminarPedido(sourceKey, trigger) {
throw new Error(data.message || 'No se pudo eliminar el pedido del panel.');
}
alert(data.message || 'Pedido eliminado del panel correctamente.');
window.location.reload();
ccCallCenterPanelNavigate();
})
.catch(error => {
alert(error.message || 'Ocurrió un error al eliminar el pedido del panel.');
@ -2727,7 +2764,7 @@ function guardarGestion(sourceKey, trigger, options = {}) {
if (subirLogistica || moverPromoFinal) {
alert(data.message || 'Gestión actualizada correctamente.');
}
window.location.reload();
ccCallCenterPanelNavigate();
})
.catch(error => {
alert(error.message || 'Ocurrió un error al guardar la gestión.');
@ -3023,7 +3060,163 @@ function syncServerAssessorSelects() {
}
syncServerAssessorSelects();
window.addEventListener('pageshow', syncServerAssessorSelects);
function ccCallCenterPanelStorageKey() {
return 'cc-callcenter-scroll:' + ccCallCenterPanelBaseUrl.pathname + '?' + ccCallCenterPanelBaseUrl.searchParams.toString();
}
function ccCallCenterPanelSaveScroll() {
try {
sessionStorage.setItem(ccCallCenterPanelStorageKey(), String(window.scrollY || window.pageYOffset || 0));
} catch (error) {
// Si el navegador bloquea sessionStorage, seguimos sin restaurar el scroll.
}
}
function ccCallCenterPanelRestoreScroll() {
try {
const raw = sessionStorage.getItem(ccCallCenterPanelStorageKey());
if (raw !== null && raw !== '') {
const scrollTop = parseInt(raw, 10);
if (!Number.isNaN(scrollTop)) {
window.scrollTo(0, scrollTop);
}
}
} catch (error) {
// No-op.
}
}
function ccCallCenterPanelHasDirtyForms() {
return document.querySelector('form[data-cc-dirty="1"]') !== null;
}
function ccCallCenterPanelHasOpenModal() {
return document.querySelector('.modal.show') !== null;
}
function ccCallCenterPanelShouldPauseRefresh() {
if (ccCallCenterPanelHasOpenModal() || ccCallCenterPanelHasDirtyForms()) {
return true;
}
const activeElement = document.activeElement;
if (!activeElement) {
return false;
}
const tagName = String(activeElement.tagName || '').toUpperCase();
return ['INPUT', 'TEXTAREA', 'SELECT'].includes(tagName) && !activeElement.disabled && !activeElement.readOnly;
}
function ccCallCenterPanelNavigate() {
ccCallCenterPanelSaveScroll();
window.location.replace(ccCallCenterPanelBaseUrl.toString());
}
function ccCallCenterPanelRefreshNow(event) {
if (event) {
event.preventDefault();
}
ccCallCenterPanelNavigate();
return false;
}
function ccCallCenterPanelMarkDirty(event) {
const target = event.target;
if (!target || !target.form) {
return;
}
const tagName = String(target.tagName || '').toUpperCase();
const fieldType = String(target.type || '').toLowerCase();
if (tagName === 'BUTTON' || fieldType === 'hidden' || fieldType === 'submit' || fieldType === 'button' || fieldType === 'file') {
return;
}
target.form.dataset.ccDirty = '1';
}
function ccCallCenterPanelBindDirtyTracking() {
document.addEventListener('input', ccCallCenterPanelMarkDirty, true);
document.addEventListener('change', ccCallCenterPanelMarkDirty, true);
document.querySelectorAll('form').forEach(function (form) {
if (form.dataset.ccDirtyBound === '1') {
return;
}
form.addEventListener('submit', function () {
delete form.dataset.ccDirty;
});
form.addEventListener('reset', function () {
delete form.dataset.ccDirty;
});
form.dataset.ccDirtyBound = '1';
});
}
async function ccCallCenterPanelCheckForUpdates() {
if (ccCallCenterPanelRefreshInFlight || document.visibilityState !== 'visible' || ccCallCenterPanelShouldPauseRefresh() || !ccCallCenterPanelInitialSignature) {
return;
}
ccCallCenterPanelRefreshInFlight = true;
try {
const response = await fetch(ccCallCenterPanelRefreshUrl.toString(), {
method: 'GET',
cache: 'no-store',
credentials: 'same-origin',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
return;
}
const data = await response.json();
if (!data || data.success !== true) {
return;
}
const signature = String(data.signature || '');
if (signature && signature !== ccCallCenterPanelLastSignature) {
ccCallCenterPanelNavigate();
return;
}
if (signature) {
ccCallCenterPanelLastSignature = signature;
}
} catch (error) {
// No avisamos para no interrumpir el trabajo si falla la red momentáneamente.
} finally {
ccCallCenterPanelRefreshInFlight = false;
}
}
syncServerAssessorSelects();
ccCallCenterPanelBindDirtyTracking();
ccCallCenterPanelRestoreScroll();
window.addEventListener('beforeunload', ccCallCenterPanelSaveScroll);
window.addEventListener('pageshow', function () {
syncServerAssessorSelects();
ccCallCenterPanelRestoreScroll();
ccCallCenterPanelCheckForUpdates();
});
window.addEventListener('focus', function () {
syncServerAssessorSelects();
ccCallCenterPanelCheckForUpdates();
});
document.addEventListener('visibilitychange', function () {
if (document.visibilityState === 'visible') {
ccCallCenterPanelRestoreScroll();
ccCallCenterPanelCheckForUpdates();
}
});
window.setInterval(ccCallCenterPanelCheckForUpdates, ccCallCenterPanelRefreshMs);
window.addEventListener('load', syncServerAssessorSelects);
window.requestAnimationFrame(syncServerAssessorSelects);
window.setTimeout(syncServerAssessorSelects, 0);

View File

@ -59,7 +59,8 @@ if ($user_role === 'Asesor') {
}
// Define los estados de los pedidos
$estados = ['RUTA_CONTRAENTREGA', 'RETORNADO', 'ENTREGA EXITOSA', 'ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅'];
$estados = ['RUTA_CONTRAENTREGA', 'RETORNADO', 'ENTREGA EXITOSA', 'ROTULADO 📦', 'PENDIENTES', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅'];
$estados_db = array_merge($estados, ['PENDIENTE']);
$asesor_data = [];
$chart_data = [];
@ -84,7 +85,7 @@ if ($user_role === 'Administrador' || $user_role === 'Control Logistico' || $use
];
// 3. Construir y ejecutar una consulta única para todos los pedidos del mes seleccionado
$placeholders = rtrim(str_repeat('?,', count($estados)), ',');
$placeholders = rtrim(str_repeat('?,', count($estados_db)), ',');
$sql = "
SELECT asesor_id, estado, COUNT(*) as total_pedidos
FROM pedidos
@ -92,13 +93,16 @@ if ($user_role === 'Administrador' || $user_role === 'Control Logistico' || $use
GROUP BY asesor_id, estado
";
$stmt_pedidos = $pdo->prepare($sql);
$params = array_merge($estados, [$selected_month, $selected_year]);
$params = array_merge($estados_db, [$selected_month, $selected_year]);
$stmt_pedidos->execute($params);
// 4. Llenar la estructura con los datos de la consulta
while ($row = $stmt_pedidos->fetch(PDO::FETCH_ASSOC)) {
$asesor_id = $row['asesor_id'];
$estado = $row['estado'];
if ($estado === 'PENDIENTE') {
$estado = 'PENDIENTES';
}
$total_pedidos = (int)$row['total_pedidos'];
// Si el asesor_id es nulo o no corresponde a un asesor conocido, se agrupa en "Sin Asignar".
@ -213,8 +217,9 @@ if ($user_role === 'Administrador' || $user_role === 'Control Logistico' || $use
if (!empty($asesora_ids)) {
// PROVINCIA (se cuenta por fecha de registro del pedido)
$estados_provincia = ['ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅'];
$placeholders_estados_prov = implode(',', array_fill(0, count($estados_provincia), '?'));
$estados_provincia = ['ROTULADO 📦', 'PENDIENTES', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅'];
$estados_provincia_db = array_merge($estados_provincia, ['PENDIENTE']);
$placeholders_estados_prov = implode(',', array_fill(0, count($estados_provincia_db), '?'));
$placeholders_asesores = implode(',', array_fill(0, count($asesora_ids), '?'));
$asesor_data_provincia = [];
foreach ($asesoras_mensual as $asesora) {
@ -234,7 +239,7 @@ if ($user_role === 'Administrador' || $user_role === 'Control Logistico' || $use
AND p.asesor_id IN ($placeholders_asesores)
GROUP BY dia, p.asesor_id
");
$params_prov = array_merge($estados_provincia, [$selected_month, $selected_year], $asesora_ids);
$params_prov = array_merge($estados_provincia_db, [$selected_month, $selected_year], $asesora_ids);
$stmt_prov->execute($params_prov);
$pedidos_prov_data = $stmt_prov->fetchAll(PDO::FETCH_ASSOC);
@ -274,6 +279,9 @@ if ($user_role === 'Administrador' || $user_role === 'Control Logistico' || $use
foreach ($resumen_prov_data as $row) {
$asesor_id = (int)$row['asesor_id'];
$estado = $row['estado'];
if ($estado === 'PENDIENTE') {
$estado = 'PENDIENTES';
}
$total_pedidos = (int)$row['total_pedidos'];
if (isset($asesor_data_provincia[$asesor_id])) {
@ -651,7 +659,7 @@ $selected_month] . ' ' . $selected_year; ?>)</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<?php $estados_provincia = ['ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅']; ?>
<?php $estados_provincia = ['ROTULADO 📦', 'PENDIENTES', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅']; ?>
<table class="table table-bordered" id="dataTableProvincia" width="100%" cellspacing="0">
<thead>
<tr>

View File

@ -0,0 +1,27 @@
-- Rename the pending logistics status from PENDIENTE to PENDIENTES.
-- This keeps the dashboard and rotulado flow aligned with the new label.
UPDATE pedidos
SET estado = 'PENDIENTES'
WHERE estado = 'PENDIENTE';
UPDATE pedidos
SET pendientes = 'PENDIENTES'
WHERE pendientes = 'PENDIENTE';
ALTER TABLE pedidos MODIFY COLUMN estado ENUM(
'ROTULADO 📦',
'EN TRANSITO 🚛',
'EN DESTINO 🏬',
'COMPLETADO ✅',
'Gestion',
'RUTA_CONTRAENTREGA',
'ENTREGA EXITOSA',
'RETORNADO',
'Duplicados',
'NO CONTESTO, DEVOLVER LLAMADA',
'CANCELADO',
'REPROGRAMADO',
'NO CONTESTO, VOLVER A LLAMAR',
'PENDIENTES'
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 'ROTULADO 📦';

View File

@ -20,7 +20,7 @@ try {
p.producto,
p.cantidad
FROM pedidos p
WHERE (p.estado = 'ROTULADO 📦' OR p.estado = 'PENDIENTE')
WHERE (p.estado = 'ROTULADO 📦' OR p.estado IN ('PENDIENTE', 'PENDIENTES'))
ORDER BY p.id DESC
";
$stmt = $pdo->prepare($query);

View File

@ -13,11 +13,11 @@ try {
$pdo = db();
$type = $_GET['type'] ?? 'all';
// Filtramos los pedidos ROTULADO 📦 y PENDIENTE (listados como 'Pedidos Rotulados') y de la agencia 'SHALOM'
// Filtramos los pedidos ROTULADO 📦 y PENDIENTES (listados como 'Pedidos Rotulados') y de la agencia 'SHALOM'
$query = "
SELECT p.dni_cliente, p.celular, p.sede_envio, p.agencia, p.producto, p.cantidad
FROM pedidos p
WHERE (p.estado = 'ROTULADO 📦' OR p.estado = 'PENDIENTE')
WHERE (p.estado = 'ROTULADO 📦' OR p.estado IN ('PENDIENTE', 'PENDIENTES'))
AND p.agencia LIKE '%SHALOM%'
";

View File

@ -40,7 +40,7 @@ function getStatusStyle($status) {
function getPendientesStyle($pendientes) {
$v = strtoupper(trim((string)$pendientes));
if ($v === 'PENDIENTE') return 'background-color: #ffc107; color: black;'; // Warning yellow
if ($v === 'PENDIENTE' || $v === 'PENDIENTES') return 'background-color: #ffc107; color: black;'; // Warning yellow
if ($v === 'GESTIONADO') return 'background-color: #198754; color: white;'; // Success green
return 'background-color: #6c757d; color: white;'; // Secondary grey
}
@ -49,7 +49,7 @@ function formatPendientesLabel($pendientes) {
$raw = (string)$pendientes;
$v = strtoupper(trim($raw));
if ($v === 'PENDIENTE') return 'PENDIENTE A GESTIONAR';
if ($v === 'PENDIENTE' || $v === 'PENDIENTES') return 'PENDIENTES A GESTIONAR';
if ($v === 'GESTIONADO') return 'GESTIONADO';
if ($v === '') return 'Seleccionar';
@ -262,7 +262,7 @@ $(document).ready(function() {
function getPendientesStyleJS(pendientes) {
const v = (pendientes || '').toString().toUpperCase().trim();
if (v === 'PENDIENTE') return 'background-color: #ffc107; color: black;';
if (v === 'PENDIENTE' || v === 'PENDIENTES') return 'background-color: #ffc107; color: black;';
if (v === 'GESTIONADO') return 'background-color: #198754; color: white;';
return 'background-color: #6c757d; color: white;';
}
@ -270,7 +270,7 @@ function getPendientesStyleJS(pendientes) {
function formatPendientesLabelJS(value) {
const v = (value || '').toString().toUpperCase().trim();
if (!v) return 'Seleccionar';
if (v === 'PENDIENTE') return 'PENDIENTE A GESTIONAR';
if (v === 'PENDIENTE' || v === 'PENDIENTES') return 'PENDIENTES A GESTIONAR';
return v;
}
@ -285,6 +285,7 @@ document.addEventListener('DOMContentLoaded', function() {
if (cell.querySelector('select')) return; // Already editing
const currentVal = cell.dataset.value;
const normalizedCurrentVal = (currentVal || '').toString().toUpperCase().trim() === 'PENDIENTE' ? 'PENDIENTES' : currentVal;
const pedidoId = cell.dataset.id;
const select = document.createElement('select');
@ -292,7 +293,7 @@ document.addEventListener('DOMContentLoaded', function() {
const options = [
{val: '', text: 'Seleccionar'},
{val: 'PENDIENTE', text: 'PENDIENTE A GESTIONAR'},
{val: 'PENDIENTES', text: 'PENDIENTES A GESTIONAR'},
{val: 'GESTIONADO', text: 'GESTIONADO'}
];
@ -300,7 +301,7 @@ document.addEventListener('DOMContentLoaded', function() {
const option = document.createElement('option');
option.value = opt.val;
option.text = opt.text;
if (opt.val === currentVal) option.selected = true;
if (opt.val === normalizedCurrentVal) option.selected = true;
select.appendChild(option);
});
@ -318,7 +319,7 @@ document.addEventListener('DOMContentLoaded', function() {
cell.dataset.value = newVal;
cell.innerHTML = `<span class="badge" style="${getPendientesStyleJS(newVal)}">${formatPendientesLabelJS(newVal)}</span>`;
if (newVal === currentVal) return; // No change
if (newVal === normalizedCurrentVal) return; // No change
// Send data to server
fetch('update_pendientes.php', {

View File

@ -36,7 +36,7 @@ function getStatusStyle($status) {
function getPendientesStyle($pendientes) {
$v = strtoupper(trim((string)$pendientes));
if ($v === 'PENDIENTE') return 'background-color: #ffc107; color: black;'; // Warning yellow
if ($v === 'PENDIENTE' || $v === 'PENDIENTES') return 'background-color: #ffc107; color: black;'; // Warning yellow
if ($v === 'GESTIONADO') return 'background-color: #198754; color: white;'; // Success green
return 'background-color: #6c757d; color: white;'; // Secondary grey
}
@ -45,7 +45,7 @@ function formatPendientesLabel($pendientes) {
$raw = (string)$pendientes;
$v = strtoupper(trim($raw));
if ($v === 'PENDIENTE') return 'PENDIENTE A GESTIONAR';
if ($v === 'PENDIENTE' || $v === 'PENDIENTES') return 'PENDIENTES A GESTIONAR';
if ($v === 'GESTIONADO') return 'GESTIONADO';
if ($v === '') return 'Seleccionar';
@ -258,7 +258,7 @@ $(document).ready(function() {
function getPendientesStyleJS(pendientes) {
const v = (pendientes || '').toString().toUpperCase().trim();
if (v === 'PENDIENTE') return 'background-color: #ffc107; color: black;';
if (v === 'PENDIENTE' || v === 'PENDIENTES') return 'background-color: #ffc107; color: black;';
if (v === 'GESTIONADO') return 'background-color: #198754; color: white;';
return 'background-color: #6c757d; color: white;';
}
@ -266,7 +266,7 @@ function getPendientesStyleJS(pendientes) {
function formatPendientesLabelJS(value) {
const v = (value || '').toString().toUpperCase().trim();
if (!v) return 'Seleccionar';
if (v === 'PENDIENTE') return 'PENDIENTE A GESTIONAR';
if (v === 'PENDIENTE' || v === 'PENDIENTES') return 'PENDIENTES A GESTIONAR';
return v;
}
@ -281,6 +281,7 @@ document.addEventListener('DOMContentLoaded', function() {
if (cell.querySelector('select')) return; // Already editing
const currentVal = cell.dataset.value;
const normalizedCurrentVal = (currentVal || '').toString().toUpperCase().trim() === 'PENDIENTE' ? 'PENDIENTES' : currentVal;
const pedidoId = cell.dataset.id;
const select = document.createElement('select');
@ -288,7 +289,7 @@ document.addEventListener('DOMContentLoaded', function() {
const options = [
{val: '', text: 'Seleccionar'},
{val: 'PENDIENTE', text: 'PENDIENTE A GESTIONAR'},
{val: 'PENDIENTES', text: 'PENDIENTES A GESTIONAR'},
{val: 'GESTIONADO', text: 'GESTIONADO'}
];
@ -296,7 +297,7 @@ document.addEventListener('DOMContentLoaded', function() {
const option = document.createElement('option');
option.value = opt.val;
option.text = opt.text;
if (opt.val === currentVal) option.selected = true;
if (opt.val === normalizedCurrentVal) option.selected = true;
select.appendChild(option);
});
@ -314,7 +315,7 @@ document.addEventListener('DOMContentLoaded', function() {
cell.dataset.value = newVal;
cell.innerHTML = `<span class="badge" style="${getPendientesStyleJS(newVal)}">${formatPendientesLabelJS(newVal)}</span>`;
if (newVal === currentVal) return; // No change
if (newVal === normalizedCurrentVal) return; // No change
// Send data to server
fetch('update_pendientes.php', {

View File

@ -25,7 +25,7 @@ $is_today = isset($_GET['today']);
$sql = "SELECT p.*
FROM pedidos p
WHERE (p.estado = 'ROTULADO 📦' OR p.estado = 'PENDIENTE')";
WHERE (p.estado = 'ROTULADO 📦' OR p.estado IN ('PENDIENTE', 'PENDIENTES'))";
$params = [];
if ($user_role === 'Asesor') {
@ -197,7 +197,7 @@ $label_generated_date = date('d/m');
<?php if (empty($pedidos)): ?>
<div style="text-align: center; margin-top: 50px;">
<h3>No hay pedidos rotulados o pendientes para mostrar (ROTULADO 📦 / PENDIENTE).</h3>
<h3>No hay pedidos rotulados o pendientes para mostrar (ROTULADO 📦 / PENDIENTES).</h3>
</div>
<?php endif; ?>

View File

@ -251,13 +251,13 @@ function cc_test_default_assessor_keys(): array
function cc_test_is_tuani_store_key(string $storeKey): bool
{
$storeKey = trim(mb_strtolower($storeKey));
return in_array($storeKey, ['otra_tienda', 'tuani_recuperables'], true);
return in_array($storeKey, ['flower', 'otra_tienda', 'tuani_recuperables'], true);
}
function cc_test_is_tuani_main_store_key(string $storeKey): bool
{
$storeKey = trim(mb_strtolower($storeKey));
return $storeKey === 'otra_tienda';
return in_array($storeKey, ['flower', 'otra_tienda'], true);
}
if (!function_exists('cc_test_normalize_phone_digits')) {

View File

@ -18,7 +18,13 @@ function drive_test_available_stores(): array
'label' => 'Flower',
'spreadsheet_id' => '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c',
'sheet_gid' => null,
'startRow' => 7796, // Pedido #37431
'startRow' => 8687, // Inicio solicitado para Flower
'status_sync_start_row' => 8687,
'status_sync_column' => 'V',
'advisor_sync_start_row' => 8687,
'advisor_sync_column' => 'X',
'delivery_sync_start_row' => 8687,
'delivery_sync_column' => 'W',
'min_codigo_number' => 37431,
'enforce_db_start_row' => true,
'incremental_sync' => true,

View File

@ -248,11 +248,12 @@ include 'layout_header.php';
<small class="text-muted">Ingresa un número de operación válido y selecciona el banco para ver la clave.</small>
</div>
<div class="col-md-6 mb-3">
<label for="pendientes" class="form-label">Pendientes</label>
<label for="pendientes" class="form-label">PENDIENTES</label>
<?php $current_pendientes = strtoupper(trim((string)($pedido['pendientes'] ?? ''))); if ($current_pendientes === 'PENDIENTE') { $current_pendientes = 'PENDIENTES'; } ?>
<select class="form-select" id="pendientes" name="pendientes">
<option value="" <?php echo (empty($pedido['pendientes'])) ? 'selected' : ''; ?>>Seleccionar</option>
<option value="PENDIENTE" <?php echo ($pedido['pendientes'] == 'PENDIENTE') ? 'selected' : ''; ?>>PENDIENTE</option>
<option value="GESTIONADO" <?php echo ($pedido['pendientes'] == 'GESTIONADO') ? 'selected' : ''; ?>>GESTIONADO</option>
<option value="" <?php echo ($current_pendientes === '') ? 'selected' : ''; ?>>Seleccionar</option>
<option value="PENDIENTES" <?php echo ($current_pendientes === 'PENDIENTES') ? 'selected' : ''; ?>>PENDIENTES</option>
<option value="GESTIONADO" <?php echo ($current_pendientes === 'GESTIONADO') ? 'selected' : ''; ?>>GESTIONADO</option>
</select>
</div>
</div>

View File

@ -32,6 +32,7 @@ function getStatusStyle($status) {
$bgColor = '#198754'; // dark green
break;
case 'PENDIENTE':
case 'PENDIENTES':
$bgColor = '#dc3545'; // red
$style = 'color: white;';
$shadow = 'box-shadow: 0 0.6rem 1.2rem rgba(220, 53, 69, 0.35) !important;';
@ -62,28 +63,43 @@ function getStatusStyle($status) {
return "background-color: {$bgColor} !important; {$style} {$shadow}";
}
function formatStatusLabel($status) {
$normalized = strtoupper(trim((string) $status));
if ($normalized === 'PENDIENTE' || $normalized === 'PENDIENTES') {
return 'PENDIENTES';
}
if ($normalized === 'GESTION') {
return 'GESTIONES ⚙️';
}
return (string) $status;
}
$pdo = db();
// Auto-promoción: los pedidos rotulados que ya superaron 1 día pasan a estado PENDIENTE
// Auto-promoción: los pedidos rotulados que ya superaron 1 día pasan a estado PENDIENTES
// (para que se vean más resaltantes en la operación).
$auto_promoted_count = 0;
try {
$auto_stmt = $pdo->prepare("
UPDATE pedidos
SET estado = 'PENDIENTE'
SET estado = 'PENDIENTES'
WHERE estado = 'ROTULADO 📦'
AND created_at <= NOW() - INTERVAL 1 DAY
");
$auto_stmt->execute();
$auto_promoted_count = (int)$auto_stmt->rowCount();
} catch (PDOException $e) {
error_log('Auto-promote ROTULADO 📦 -> PENDIENTE failed: ' . $e->getMessage());
error_log('Auto-promote ROTULADO 📦 -> PENDIENTES failed: ' . $e->getMessage());
}
if ($auto_promoted_count > 0 && empty($_SESSION['flash_message'])) {
$_SESSION['flash_message'] = [
'type' => 'warning',
'message' => 'Se marcaron automáticamente ' . $auto_promoted_count . ' pedido(s) como "PENDIENTE" (pasaron 1 día desde su creación) y siguen visibles en Pedidos Rotulados.'
'message' => 'Se marcaron automáticamente ' . $auto_promoted_count . ' pedido(s) como "PENDIENTES" (pasaron 1 día desde su creación) y siguen visibles en Pedidos Rotulados.'
];
}
@ -107,7 +123,7 @@ $selected_month = $_GET['mes'] ?? '';
$selected_year = $_GET['año'] ?? '';
$search_query = $_GET['q'] ?? '';
$sql = "SELECT p.*, u.nombre_asesor as asesor_nombre FROM pedidos p LEFT JOIN users u ON p.asesor_id = u.id WHERE (p.estado = 'ROTULADO 📦' OR (p.estado = 'PENDIENTE' AND p.created_at <= NOW() - INTERVAL 1 DAY))";
$sql = "SELECT p.*, u.nombre_asesor as asesor_nombre FROM pedidos p LEFT JOIN users u ON p.asesor_id = u.id WHERE (p.estado = 'ROTULADO 📦' OR (p.estado IN ('PENDIENTE', 'PENDIENTES') AND p.created_at <= NOW() - INTERVAL 1 DAY))";
$params = [];
if ($user_role === 'Asesor') {
@ -258,7 +274,7 @@ include 'layout_header.php';
<td class="editable-dblclick" data-id="<?php echo $pedido['id']; ?>" data-field="nota_adicional">
<?php echo htmlspecialchars($pedido['nota_adicional'] ?? 'N/A'); ?>
</td>
<td><span class="badge" style="<?php echo getStatusStyle($pedido['estado']); ?>"><?php echo ($pedido['estado'] == 'Gestion') ? 'GESTIONES ⚙️' : htmlspecialchars($pedido['estado']); ?></span></td>
<td><span class="badge" style="<?php echo getStatusStyle($pedido['estado']); ?>"><?php echo htmlspecialchars(formatStatusLabel($pedido['estado'])); ?></span></td>
<?php if ($user_role !== 'Asesor'): ?><td><?php echo htmlspecialchars($pedido['asesor_nombre'] ?? 'N/A'); ?></td><?php endif; ?>
<td><?php echo htmlspecialchars($pedido['created_at']); ?></td>
<td>

View File

@ -337,7 +337,10 @@ include 'layout_header.php';
<select class="form-select" id="estado" name="estado" required <?php echo (!empty($pedido['id']) && !$can_edit_estado_contraentrega) ? 'disabled' : ''; ?>>
<?php
$current_status = $pedido['estado'] ?? 'RUTA_CONTRAENTREGA';
$statuses = ['RUTA_CONTRAENTREGA', 'PENDIENTE', 'NO CONTESTO, VOLVER A LLAMAR', 'CANCELADO', 'REPROGRAMADO', 'ENTREGA EXITOSA'];
if ($current_status === 'PENDIENTE') {
$current_status = 'PENDIENTES';
}
$statuses = ['RUTA_CONTRAENTREGA', 'PENDIENTES', 'NO CONTESTO, VOLVER A LLAMAR', 'CANCELADO', 'REPROGRAMADO', 'ENTREGA EXITOSA'];
foreach ($statuses as $status) {
$selected = ($current_status == $status) ? 'selected' : '';
echo "<option value='{$status}' {$selected}>" . htmlspecialchars($status) . "</option>";

View File

@ -11,32 +11,43 @@ if (php_sapi_name() !== 'cli') {
}
$pdo = db();
$storeKey = 'otra_tienda';
$availableStores = drive_test_available_stores();
$storeConfig = $availableStores[$storeKey] ?? [];
$assessors = cc_test_fetch_assessors($pdo);
$repartoSettings = cc_test_fetch_reparto_settings($pdo, $storeKey, $assessors);
$processedAnyStore = false;
if (!cc_test_reparto_mode_is_auto($repartoSettings['reparto_mode'] ?? 'manual')) {
echo '[' . date('Y-m-d H:i:s') . '] Reparto automático omitido: modo manual en ' . $storeKey . PHP_EOL;
exit(0);
foreach ($availableStores as $storeKey => $storeConfig) {
if (!cc_test_is_tuani_main_store_key((string) $storeKey)) {
continue;
}
$processedAnyStore = true;
$repartoSettings = cc_test_fetch_reparto_settings($pdo, (string) $storeKey, $assessors);
if (!cc_test_reparto_mode_is_auto($repartoSettings['reparto_mode'] ?? 'manual')) {
echo '[' . date('Y-m-d H:i:s') . '] Reparto automático omitido: modo manual en ' . $storeKey . PHP_EOL;
continue;
}
$result = cc_test_run_locked_reparto_job(
$pdo,
(string) $storeKey,
$assessors,
static function () use ($pdo, $storeKey): array {
return cc_test_load_reparto_orders($pdo, (string) $storeKey);
},
$repartoSettings,
static function (array $orders) use ($storeKey, $storeConfig): array {
return cc_test_filter_reparto_orders($orders, (string) $storeKey, $storeConfig);
}
);
$assignedCount = (int) ($result['assigned_count'] ?? 0);
$message = (string) ($result['message'] ?? '');
$status = !empty($result['locked']) ? 'ok' : 'busy';
echo '[' . date('Y-m-d H:i:s') . '] ' . $storeKey . ' | estado=' . $status . ' | asignados=' . $assignedCount . ' | ' . $message . PHP_EOL;
}
$result = cc_test_run_locked_reparto_job(
$pdo,
$storeKey,
$assessors,
static function () use ($pdo, $storeKey): array {
return cc_test_load_reparto_orders($pdo, $storeKey);
},
$repartoSettings,
static function (array $orders) use ($storeKey, $storeConfig): array {
return cc_test_filter_reparto_orders($orders, $storeKey, $storeConfig);
}
);
$assignedCount = (int) ($result['assigned_count'] ?? 0);
$message = (string) ($result['message'] ?? '');
$status = !empty($result['locked']) ? 'ok' : 'busy';
echo '[' . date('Y-m-d H:i:s') . '] ' . $storeKey . ' | estado=' . $status . ' | asignados=' . $assignedCount . ' | ' . $message . PHP_EOL;
if (!$processedAnyStore) {
echo '[' . date('Y-m-d H:i:s') . '] No se encontraron tiendas principales para reparto automático.' . PHP_EOL;
}

View File

@ -62,6 +62,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$codigo_tracking = trim($_POST['codigo_tracking'] ?? '');
$clave = trim($_POST['clave'] ?? '');
$pendientes = trim($_POST['pendientes'] ?? '');
if (strcasecmp($pendientes, 'PENDIENTE') === 0) {
$pendientes = 'PENDIENTES';
}
// --- Manejo de multiples productos ---
@ -90,6 +93,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$numero_operacion = trim($_POST['numero_operacion']);
$banco = trim($_POST['banco'] ?? '');
$estado = $_POST['estado'];
if (strcasecmp((string) $estado, 'PENDIENTE') === 0) {
$estado = 'PENDIENTES';
}
$notas = trim($_POST['notas']);
$nota_adicional = trim($_POST['nota_adicional'] ?? '');
$observacion = trim($_POST['observacion'] ?? '');

View File

@ -18,6 +18,9 @@ if (in_array($user_role, ['Logistica', 'Logística'], true)) {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$pedido_id = $_POST['pedido_id'] ?? $_POST['id'] ?? null;
$estado = $_POST['estado'] ?? null;
if ($estado === 'PENDIENTE') {
$estado = 'PENDIENTES';
}
if (!$pedido_id) {
echo json_encode(['success' => false, 'message' => 'ID de pedido faltante']);
@ -27,7 +30,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$pdo = db();
$valid_states = ['RUTA_CONTRAENTREGA', 'PENDIENTE', 'NO CONTESTO, VOLVER A LLAMAR', 'CANCELADO', 'REPROGRAMADO', 'ENTREGA EXITOSA', 'RETORNADO', 'NO CONTESTO, DEVOLVER LLAMADA', 'COMPLETADO ✅', 'Gestion'];
$valid_states = ['RUTA_CONTRAENTREGA', 'PENDIENTES', 'NO CONTESTO, VOLVER A LLAMAR', 'CANCELADO', 'REPROGRAMADO', 'ENTREGA EXITOSA', 'RETORNADO', 'NO CONTESTO, DEVOLVER LLAMADA', 'COMPLETADO ✅', 'Gestion'];
if (!in_array($estado, $valid_states, true)) {
echo json_encode(['success' => false, 'message' => 'Estado inválido: ' . $estado]);
exit;

View File

@ -18,6 +18,9 @@ if (!$data || !isset($data['id']) || !isset($data['value'])) {
$pedido_id = $data['id'];
$pendientes = $data['value'];
if (strcasecmp((string) $pendientes, 'PENDIENTE') === 0) {
$pendientes = 'PENDIENTES';
}
try {
$pdo = db();