Autosave: 20260525-090633

This commit is contained in:
Flatlogic Bot 2026-05-25 09:06:38 +00:00
parent 891893f221
commit bd9aa664a9
4 changed files with 331 additions and 32 deletions

View File

@ -3,6 +3,11 @@ session_start();
require_once 'db/config.php';
require_once 'includes/callcenter_test_helpers.php';
require_once 'includes/drive_test_orders.php';
require_once 'includes/contraentrega_cobertura.php';
$provinciasPorDepartamentoContraentrega = contraentregaProvinciasPorDepartamento();
$distritosPorProvinciaContraentrega = contraentregaDistritosPorProvincia();
$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.';
@ -69,12 +74,12 @@ function cc_test_order_label(array $order): string
function cc_test_badge_class(string $estado): string
{
return match ($estado) {
'CONFIRMADO CONTRAENTREGA', 'CONFIRMADO CONTRAENTREGA FECHA', 'CONFIRMADO ENVIO', 'CONFIRMADO FECHA', 'CONTRAENTREGA CONFIRMADO' => 'bg-success-subtle text-success-emphasis',
return match (cc_test_normalize_state($estado)) {
'CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO' => 'bg-success-subtle text-success-emphasis',
'DEVOLVER LLAMADA' => 'bg-info-subtle text-info-emphasis',
'OBSERVADO' => 'bg-warning-subtle text-warning-emphasis',
'CANCELADO' => 'bg-danger-subtle text-danger-emphasis',
'REPETIDO', 'ENVIO REPETIDO' => 'bg-secondary-subtle text-secondary-emphasis',
'REPETIDO' => 'bg-secondary-subtle text-secondary-emphasis',
'SE ENVIO NUMERO DE CUENTA' => 'bg-primary-subtle text-primary-emphasis',
default => 'bg-dark-subtle text-dark-emphasis',
};
@ -82,7 +87,7 @@ function cc_test_badge_class(string $estado): string
function cc_test_order_time(array $order): int
{
foreach (['proxima_llamada_at', 'ultima_gestion_at', 'seguimiento_actualizado', 'import_id'] as $field) {
foreach (['proxima_llamada_at', 'numero_cuenta_enviado_at', 'ultima_gestion_at', 'seguimiento_actualizado', 'import_id'] as $field) {
$date = cc_test_parse_datetime($order[$field] ?? null);
if ($date) {
return $date->getTimestamp();
@ -92,6 +97,15 @@ function cc_test_order_time(array $order): int
return 0;
}
function cc_test_followup_semaforo(array $order): ?array
{
if (cc_test_normalize_state((string) ($order['estado'] ?? '')) !== 'SE ENVIO NUMERO DE CUENTA') {
return null;
}
return cc_test_account_followup_semaforo($order['numero_cuenta_enviado_at'] ?? null);
}
$view = $_GET['view'] ?? 'pendientes_hoy';
$allowedViews = [
'pendientes_hoy' => 'Pendientes de hoy',
@ -147,6 +161,7 @@ try {
$closedStates = cc_test_closed_states();
foreach ($orders as &$order) {
$order['estado'] = cc_test_normalize_state((string) ($order['estado'] ?? ''));
$order['total_llamadas'] = (int) ($callCounts[$order['source_key']] ?? 0);
$importDate = cc_test_parse_datetime($order['import_id'] ?? null);
$proximaDate = cc_test_parse_datetime($order['proxima_llamada_at'] ?? null);
@ -239,7 +254,7 @@ require_once 'layout_header.php';
</div>
<div class="col-lg-4">
<div class="small text-muted">Campos editables del módulo</div>
<div class="fw-semibold">Dirección, referencia, sede / ID, ciudad, distrito, DNI y observaciones</div>
<div class="fw-semibold">Dirección, referencia, departamento, provincia, distrito, DNI y observaciones</div>
</div>
</div>
</section>
@ -311,7 +326,7 @@ require_once 'layout_header.php';
<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]); ?></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="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>
@ -340,8 +355,14 @@ require_once 'layout_header.php';
<?php else: ?>
<?php foreach ($visibleOrders as $order): ?>
<?php
$departamentoSeleccionado = trim((string) ($order['sede'] ?? ''));
$provinciaSeleccionada = trim((string) ($order['ciudad'] ?? ''));
$distritoSeleccionado = trim((string) ($order['distrito'] ?? ''));
$provinciasSeleccionadas = $provinciasPorDepartamentoContraentrega[$departamentoSeleccionado] ?? [];
$distritosSeleccionados = $distritosPorProvinciaContraentrega[$provinciaSeleccionada] ?? [];
$modalId = 'modalDriveTest' . $order['source_key'];
$badgeClass = cc_test_badge_class($order['estado']);
$semaforoCuenta = cc_test_followup_semaforo($order);
$historial = cc_test_fetch_historial(db(), $order['source_key']);
ob_start();
?>
@ -358,8 +379,12 @@ require_once 'layout_header.php';
<td>
<div class="small"><strong>Dirección:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['direccion'])); ?></div>
<div class="small"><strong>Referencia:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['referencia'])); ?></div>
<div class="small"><strong>Ciudad / Distrito:</strong> <?php echo htmlspecialchars(cc_test_display_value(trim(($order['ciudad'] ?? '') . ' / ' . ($order['distrito'] ?? '')), 'No registrado')); ?></div>
<div class="small"><strong>Sede / ID:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['sede'])); ?></div>
<div class="small"><strong>Departamento:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['sede'])); ?></div>
<div class="small"><strong>Provincia:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['ciudad'])); ?></div>
<div class="small"><strong>Distrito:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['distrito'])); ?></div>
<?php if (!empty(trim((string) ($order['distrito_drive'] ?? '')))): ?>
<div class="small"><strong>DISTRITO 1:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['distrito_drive'])); ?></div>
<?php endif; ?>
</td>
<td>
<div class="fw-semibold"><?php echo htmlspecialchars(cc_test_display_value($order['producto'], 'Sin producto')); ?></div>
@ -371,10 +396,16 @@ require_once 'layout_header.php';
<div class="d-flex flex-wrap gap-2 mb-2">
<span class="badge rounded-pill <?php echo htmlspecialchars($badgeClass); ?>"><?php echo htmlspecialchars(cc_test_state_label($order['estado'])); ?></span>
<span class="badge rounded-pill bg-light text-dark border"><span class="js-call-count-number" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>"><?php echo (int) $order['total_llamadas']; ?></span> llamadas</span>
<?php if ($semaforoCuenta !== null): ?>
<span class="badge rounded-pill <?php echo htmlspecialchars($semaforoCuenta['class']); ?>">Seguimiento <?php echo htmlspecialchars($semaforoCuenta['label']); ?></span>
<?php endif; ?>
</div>
<?php if ($semaforoCuenta !== null): ?>
<div class="small text-muted mb-1">Número de cuenta enviado hace <?php echo (int) $semaforoCuenta['days']; ?> día<?php echo ((int) $semaforoCuenta['days'] === 1) ? '' : 's'; ?> · <?php echo htmlspecialchars($semaforoCuenta['range']); ?></div>
<?php endif; ?>
<div class="small text-muted">Próxima llamada: <?php echo htmlspecialchars(cc_test_format_datetime($order['proxima_llamada_at'] ?? null)); ?></div>
<?php if (!empty($order['fecha_entrega_programada'])): ?>
<div class="small text-muted">Entrega programada 📅: <?php echo htmlspecialchars(cc_test_format_date($order['fecha_entrega_programada'] ?? null)); ?></div>
<div class="small text-muted">Entrega programada: <?php echo htmlspecialchars(cc_test_format_date($order['fecha_entrega_programada'] ?? null)); ?></div>
<?php endif; ?>
<div class="small text-muted">Última gestión: <?php echo htmlspecialchars(cc_test_format_datetime($order['ultima_gestion_at'] ?? ($order['seguimiento_actualizado'] ?? null), 'Aún no gestionado')); ?></div>
<div class="small text-muted mt-1">Nota: <?php echo htmlspecialchars(cc_test_display_value($order['nota_seguimiento'], 'Sin nota interna')); ?></div>
@ -426,6 +457,14 @@ require_once 'layout_header.php';
<div class="border rounded p-3 h-100 bg-light-subtle">
<div class="small text-muted">Estado actual</div>
<div class="fw-semibold"><?php echo htmlspecialchars(cc_test_state_label($order['estado'])); ?></div>
<?php if ($semaforoCuenta !== null): ?>
<div class="mt-2">
<span class="badge rounded-pill <?php echo htmlspecialchars($semaforoCuenta['class']); ?>">Seguimiento <?php echo htmlspecialchars($semaforoCuenta['label']); ?></span>
</div>
<div class="small text-muted mt-2">Enviado hace <?php echo (int) $semaforoCuenta['days']; ?> día<?php echo ((int) $semaforoCuenta['days'] === 1) ? '' : 's'; ?> · <?php echo htmlspecialchars($semaforoCuenta['description']); ?></div>
<?php else: ?>
<div class="small text-muted mt-2">El semáforo se activa cuando marques “Se envió número de cuenta”.</div>
<?php endif; ?>
</div>
</div>
<div class="col-lg-3 col-md-6">
@ -467,13 +506,13 @@ require_once 'layout_header.php';
<input type="datetime-local" class="form-control" id="proxima-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars(cc_test_format_datetime_input($order['proxima_llamada_at'] ?? null)); ?>">
</div>
<div class="col-md-4 js-delivery-group <?php echo cc_test_requires_delivery_date($order['estado']) ? '' : 'd-none'; ?>" id="delivery-group-<?php echo htmlspecialchars($order['source_key']); ?>">
<label for="fecha-entrega-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Fecha de entrega 📅</label>
<label for="fecha-entrega-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Fecha de entrega</label>
<input type="date" class="form-control" id="fecha-entrega-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars(cc_test_format_date_input($order['fecha_entrega_programada'] ?? null)); ?>">
<div class="form-text">Úsalo cuando el cliente quede en Confirmado fecha.</div>
</div>
<div class="col-md-4">
<label class="form-label">Resumen</label>
<div class="border rounded px-3 py-2 bg-light-subtle small h-100 d-flex align-items-center">Los estados abiertos vuelven a <strong class="ms-1">Pendientes de hoy</strong>; Confirmado contraentrega 📅 te permite programar la entrega.</div>
<div class="border rounded px-3 py-2 bg-light-subtle small h-100 d-flex align-items-center">Los estados abiertos vuelven a <strong class="ms-1">Pendientes de hoy</strong>; Confirmado contraentrega te permite programar la entrega.</div>
</div>
<div class="col-12">
<label for="nota-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Nota interna</label>
@ -494,16 +533,55 @@ require_once 'layout_header.php';
<textarea class="form-control" id="referencia-<?php echo htmlspecialchars($order['source_key']); ?>" rows="2"><?php echo htmlspecialchars((string) ($order['referencia'] ?? '')); ?></textarea>
</div>
<div class="col-md-4">
<label for="sede-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Sede / ID</label>
<input type="text" class="form-control" id="sede-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars((string) ($order['sede'] ?? '')); ?>">
<label for="sede-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Departamento</label>
<select class="form-select js-location-department" id="sede-<?php echo htmlspecialchars($order['source_key']); ?>">
<option value="">Seleccione departamento</option>
<?php foreach ($departamentosContraentrega as $departamentoOption): ?>
<option value="<?php echo htmlspecialchars($departamentoOption); ?>" <?php echo $departamentoSeleccionado === $departamentoOption ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($departamentoOption); ?>
</option>
<?php endforeach; ?>
<?php if ($departamentoSeleccionado !== '' && !in_array($departamentoSeleccionado, $departamentosContraentrega, true)): ?>
<option value="<?php echo htmlspecialchars($departamentoSeleccionado); ?>" selected>
<?php echo htmlspecialchars($departamentoSeleccionado); ?>
</option>
<?php endif; ?>
</select>
</div>
<div class="col-md-4">
<label for="ciudad-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Ciudad</label>
<input type="text" class="form-control" id="ciudad-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars((string) ($order['ciudad'] ?? '')); ?>">
<label for="ciudad-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Provincia</label>
<select class="form-select js-location-province" id="ciudad-<?php echo htmlspecialchars($order['source_key']); ?>">
<option value="">Seleccione provincia</option>
<?php foreach ($provinciasSeleccionadas as $provinciaOption): ?>
<option value="<?php echo htmlspecialchars($provinciaOption); ?>" <?php echo $provinciaSeleccionada === $provinciaOption ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($provinciaOption); ?>
</option>
<?php endforeach; ?>
<?php if ($provinciaSeleccionada !== '' && !in_array($provinciaSeleccionada, $provinciasSeleccionadas, true)): ?>
<option value="<?php echo htmlspecialchars($provinciaSeleccionada); ?>" selected>
<?php echo htmlspecialchars($provinciaSeleccionada); ?>
</option>
<?php endif; ?>
</select>
</div>
<div class="col-md-4">
<label for="distrito-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Distrito</label>
<input type="text" class="form-control" id="distrito-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars((string) ($order['distrito'] ?? '')); ?>">
<label for="distrito_select-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Distrito</label>
<select class="form-select js-location-district <?php echo empty($distritosSeleccionados) ? 'd-none' : ''; ?>" id="distrito_select-<?php echo htmlspecialchars($order['source_key']); ?>">
<option value="">Seleccione primero provincia</option>
<?php foreach ($distritosSeleccionados as $distritoOption): ?>
<option value="<?php echo htmlspecialchars($distritoOption); ?>" <?php echo $distritoSeleccionado === $distritoOption ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($distritoOption); ?>
</option>
<?php endforeach; ?>
<?php if ($distritoSeleccionado !== '' && !in_array($distritoSeleccionado, $distritosSeleccionados, true)): ?>
<option value="<?php echo htmlspecialchars($distritoSeleccionado); ?>" selected>
<?php echo htmlspecialchars($distritoSeleccionado); ?>
</option>
<?php endif; ?>
</select>
<input type="text" class="form-control mt-2 js-location-district-manual <?php echo empty($distritosSeleccionados) ? '' : 'd-none'; ?>" id="distrito_manual-<?php echo htmlspecialchars($order['source_key']); ?>" placeholder="Escriba el distrito si aún no está en cobertura" value="<?php echo htmlspecialchars($distritoSeleccionado); ?>">
<input type="hidden" id="distrito-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars($distritoSeleccionado); ?>">
<div class="form-text">Si la provincia aún no tiene cobertura cargada, podrás escribir el distrito manualmente.</div>
</div>
<div class="col-md-4">
<label for="dni-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">DNI</label>
@ -541,6 +619,8 @@ require_once 'layout_header.php';
<div class="fw-semibold mb-2">Origen vs edición</div>
<div><strong>Dirección Drive:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['direccion_drive'] ?? null)); ?></div>
<div><strong>Referencia Drive:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['referencia_drive'] ?? null)); ?></div>
<div><strong>Distrito 1:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['distrito_drive'] ?? null)); ?></div>
<div><strong>Distrito:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['distrito'] ?? null)); ?></div>
<div><strong>Observación Drive:</strong> <?php echo htmlspecialchars(cc_test_display_value($order['observaciones_drive'] ?? null)); ?></div>
</div>
</div>
@ -610,13 +690,134 @@ require_once 'layout_header.php';
</main>
<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); ?>;
function getLocationControls(sourceKey) {
return {
department: document.getElementById('sede-' + sourceKey),
province: document.getElementById('ciudad-' + sourceKey),
districtSelect: document.getElementById('distrito_select-' + sourceKey),
districtManual: document.getElementById('distrito_manual-' + sourceKey),
districtHidden: document.getElementById('distrito-' + sourceKey)
};
}
function syncDistrictHidden(sourceKey, value) {
const controls = getLocationControls(sourceKey);
if (controls.districtHidden) {
controls.districtHidden.value = value || '';
}
}
function renderDistrictOptions(sourceKey, preserveSelection = true) {
const controls = getLocationControls(sourceKey);
if (!controls.province || !controls.districtSelect || !controls.districtManual || !controls.districtHidden) {
return;
}
const province = controls.province.value || '';
const currentValue = preserveSelection ? (controls.districtHidden.value || controls.districtSelect.value || controls.districtManual.value || '') : '';
const districts = province ? (distritosPorProvincia[province] || []) : [];
controls.districtSelect.innerHTML = '';
const emptyOption = document.createElement('option');
emptyOption.value = '';
emptyOption.textContent = province ? (districts.length ? 'Seleccione distrito' : 'Sin cobertura registrada') : 'Seleccione primero provincia';
controls.districtSelect.appendChild(emptyOption);
if (!province) {
controls.districtSelect.classList.remove('d-none');
controls.districtSelect.disabled = true;
controls.districtManual.classList.add('d-none');
controls.districtManual.value = '';
syncDistrictHidden(sourceKey, '');
return;
}
if (districts.length > 0) {
districts.forEach(function(distrito) {
const option = document.createElement('option');
option.value = distrito;
option.textContent = distrito;
if (distrito === currentValue) {
option.selected = true;
}
controls.districtSelect.appendChild(option);
});
if (currentValue && !districts.includes(currentValue)) {
const legacyOption = document.createElement('option');
legacyOption.value = currentValue;
legacyOption.textContent = currentValue + ' (actual)';
legacyOption.selected = true;
controls.districtSelect.appendChild(legacyOption);
}
controls.districtSelect.classList.remove('d-none');
controls.districtSelect.disabled = false;
controls.districtManual.classList.add('d-none');
controls.districtManual.value = '';
controls.districtSelect.value = currentValue || controls.districtSelect.value || '';
syncDistrictHidden(sourceKey, controls.districtSelect.value);
return;
}
controls.districtSelect.classList.add('d-none');
controls.districtSelect.disabled = true;
controls.districtManual.classList.remove('d-none');
controls.districtManual.value = currentValue;
syncDistrictHidden(sourceKey, currentValue);
}
function renderProvinceOptions(sourceKey, preserveSelection = true) {
const controls = getLocationControls(sourceKey);
if (!controls.department || !controls.province) {
return;
}
const department = controls.department.value || '';
const currentValue = preserveSelection ? (controls.province.value || '') : '';
const provinces = department ? (provinciasPorDepartamento[department] || []) : [];
controls.province.innerHTML = '';
const emptyOption = document.createElement('option');
emptyOption.value = '';
emptyOption.textContent = department ? (provinces.length ? 'Seleccione provincia' : 'Sin cobertura registrada') : 'Seleccione primero departamento';
controls.province.appendChild(emptyOption);
provinces.forEach(function(provincia) {
const option = document.createElement('option');
option.value = provincia;
option.textContent = provincia;
if (provincia === currentValue) {
option.selected = true;
}
controls.province.appendChild(option);
});
if (currentValue && !provinces.includes(currentValue)) {
const legacyOption = document.createElement('option');
legacyOption.value = currentValue;
legacyOption.textContent = currentValue + ' (actual)';
legacyOption.selected = true;
controls.province.appendChild(legacyOption);
}
controls.province.disabled = !department && currentValue === '';
controls.province.value = currentValue || controls.province.value || '';
renderDistrictOptions(sourceKey, preserveSelection);
}
function toggleAgendaFields(sourceKey) {
const estado = document.getElementById('estado-' + sourceKey)?.value || '';
const nextCallGroup = document.getElementById('next-call-group-' + sourceKey);
const deliveryGroup = document.getElementById('delivery-group-' + sourceKey);
const nextCallInput = document.getElementById('proxima-' + sourceKey);
const deliveryInput = document.getElementById('fecha-entrega-' + sourceKey);
const needsDeliveryDate = estado === 'CONFIRMADO CONTRAENTREGA' || estado === 'CONFIRMADO CONTRAENTREGA FECHA' || estado === 'CONFIRMADO FECHA';
const needsDeliveryDate = estado === 'CONFIRMADO CONTRAENTREGA';
const needsNextCall = ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'].includes(estado);
if (nextCallGroup) {
@ -637,6 +838,35 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('select[id^="estado-"]').forEach(select => {
toggleAgendaFields(select.id.replace('estado-', ''));
});
document.querySelectorAll('select[id^="sede-"]').forEach(select => {
const sourceKey = select.id.replace('sede-', '');
renderProvinceOptions(sourceKey, true);
select.addEventListener('change', () => {
renderProvinceOptions(sourceKey, false);
});
});
document.querySelectorAll('select[id^="ciudad-"]').forEach(select => {
const sourceKey = select.id.replace('ciudad-', '');
select.addEventListener('change', () => {
renderDistrictOptions(sourceKey, false);
});
});
document.querySelectorAll('select[id^="distrito_select-"]').forEach(select => {
const sourceKey = select.id.replace('distrito_select-', '');
select.addEventListener('change', () => {
syncDistrictHidden(sourceKey, select.value);
});
});
document.querySelectorAll('input[id^="distrito_manual-"]').forEach(input => {
const sourceKey = input.id.replace('distrito_manual-', '');
input.addEventListener('input', () => {
syncDistrictHidden(sourceKey, input.value);
});
});
});
function guardarGestion(sourceKey, trigger) {

View File

@ -39,6 +39,7 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
`observaciones` TEXT NULL,
`proxima_llamada_at` DATETIME NULL,
`fecha_entrega_programada` DATE NULL,
`numero_cuenta_enviado_at` DATETIME NULL,
`ultima_gestion_at` DATETIME NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
@ -57,7 +58,8 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'observaciones', 'TEXT NULL AFTER `dni`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'proxima_llamada_at', 'DATETIME NULL AFTER `observaciones`');
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', 'ultima_gestion_at', 'DATETIME NULL AFTER `fecha_entrega_programada`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'numero_cuenta_enviado_at', 'DATETIME NULL AFTER `fecha_entrega_programada`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ultima_gestion_at', 'DATETIME NULL AFTER `numero_cuenta_enviado_at`');
$checked = true;
}
@ -84,6 +86,17 @@ function cc_test_ensure_historial_llamadas_table(PDO $pdo): void
$checked = true;
}
function cc_test_normalize_state(string $estado): string
{
$estado = trim($estado);
return match ($estado) {
'CONFIRMADO CONTRAENTREGA FECHA', 'CONFIRMADO FECHA', 'CONTRAENTREGA CONFIRMADO' => 'CONFIRMADO CONTRAENTREGA',
'ENVIO REPETIDO' => 'REPETIDO',
default => $estado,
};
}
function cc_test_valid_states(): array
{
return [
@ -105,7 +118,7 @@ function cc_test_open_states(): array
function cc_test_confirmed_states(): array
{
return ['CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO', 'CONFIRMADO FECHA', 'CONTRAENTREGA CONFIRMADO'];
return ['CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO'];
}
function cc_test_closed_states(): array
@ -115,20 +128,58 @@ function cc_test_closed_states(): array
function cc_test_requires_delivery_date(string $estado): bool
{
return in_array($estado, ['CONFIRMADO CONTRAENTREGA', 'CONFIRMADO FECHA'], true);
return cc_test_normalize_state($estado) === 'CONFIRMADO CONTRAENTREGA';
}
function cc_test_account_followup_semaforo(?string $value): ?array
{
$date = cc_test_parse_datetime($value);
if (!$date) {
return null;
}
$now = new DateTimeImmutable('now');
$days = (int) $date->diff($now)->format('%a');
if ($date > $now) {
$days = 0;
}
if ($days <= 1) {
return [
'class' => 'bg-success-subtle text-success-emphasis',
'label' => 'Verde',
'range' => '01 días',
'days' => $days,
'description' => 'Aún está en ventana de seguimiento amable.',
];
}
if ($days === 2) {
return [
'class' => 'bg-warning-subtle text-warning-emphasis',
'label' => 'Amarillo',
'range' => '2 días',
'days' => $days,
'description' => 'Ya conviene insistir con llamada o WhatsApp.',
];
}
return [
'class' => 'bg-danger-subtle text-danger-emphasis',
'label' => 'Rojo',
'range' => '3+ días',
'days' => $days,
'description' => 'Necesita seguimiento urgente para no perder el pedido.',
];
}
function cc_test_state_label(string $estado): string
{
return match ($estado) {
return match (cc_test_normalize_state($estado)) {
'SE ENVIO NUMERO DE CUENTA' => 'SE ENVIÓ NÚMERO DE CUENTA',
'CONFIRMADO CONTRAENTREGA' => 'CONFIRMADO CONTRAENTREGA 📅',
'CONFIRMADO CONTRAENTREGA FECHA' => 'CONFIRMADO CONTRAENTREGA 📅',
'CONFIRMADO FECHA' => 'CONFIRMADO CONTRAENTREGA 📅',
'CONFIRMADO CONTRAENTREGA' => 'CONFIRMADO CONTRAENTREGA',
'CONFIRMADO ENVIO' => 'CONFIRMADO ENVIO',
'CONTRAENTREGA CONFIRMADO' => 'CONFIRMADO ENVIO',
'REPETIDO' => 'REPETIDO',
'ENVIO REPETIDO' => 'REPETIDO',
default => $estado,
};
}

View File

@ -120,7 +120,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, sede, ciudad, distrito, dni, observaciones, proxima_llamada_at, fecha_entrega_programada, ultima_gestion_at, updated_at FROM callcenter_test_tracking WHERE source_key IN ($placeholders)");
$stmt = $pdo->prepare("SELECT source_key, estado, nota_seguimiento, direccion, referencia, sede, ciudad, distrito, dni, observaciones, 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 = [];
@ -142,6 +142,7 @@ function drive_test_merge_tracking(array $orders, array $tracking): array
$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;
$order['numero_cuenta_enviado_at'] = $current['numero_cuenta_enviado_at'] ?? null;
$order['ultima_gestion_at'] = $current['ultima_gestion_at'] ?? ($current['updated_at'] ?? null);
foreach ($editableFields as $field) {

View File

@ -32,7 +32,7 @@ function cc_test_normalize_nullable_text(string $key, int $maxLen = 3000): ?stri
}
$sourceKey = trim((string) ($_POST['source_key'] ?? ''));
$estado = trim((string) ($_POST['estado'] ?? 'POR LLAMAR'));
$estado = cc_test_normalize_state(trim((string) ($_POST['estado'] ?? 'POR LLAMAR')));
$validStates = cc_test_valid_states();
if ($sourceKey === '' || !preg_match('/^[a-f0-9]{40}$/', $sourceKey)) {
@ -57,6 +57,13 @@ try {
$dni = cc_test_normalize_nullable_text('dni', 40);
$observaciones = cc_test_normalize_nullable_text('observaciones', 3000);
$pdo = db();
cc_test_ensure_tracking_table($pdo);
$stmtCurrent = $pdo->prepare('SELECT estado, numero_cuenta_enviado_at FROM callcenter_test_tracking WHERE source_key = ? LIMIT 1');
$stmtCurrent->execute([$sourceKey]);
$currentTracking = $stmtCurrent->fetch(PDO::FETCH_ASSOC) ?: null;
$proximaRaw = trim((string) ($_POST['proxima_llamada_at'] ?? ''));
$proximaLlamada = null;
if ($proximaRaw !== '') {
@ -78,7 +85,7 @@ try {
}
if (cc_test_requires_delivery_date($estado) && $fechaEntrega === null) {
throw new RuntimeException('Debes seleccionar la fecha de entrega para CONFIRMADO CONTRAENTREGA 📅.');
throw new RuntimeException('Debes seleccionar la fecha de entrega para CONFIRMADO CONTRAENTREGA.');
}
if (!in_array($estado, cc_test_open_states(), true)) {
@ -89,8 +96,13 @@ try {
$fechaEntrega = null;
}
$pdo = db();
cc_test_ensure_tracking_table($pdo);
$numeroCuentaEnviadoAt = $currentTracking['numero_cuenta_enviado_at'] ?? null;
$currentState = cc_test_normalize_state((string) ($currentTracking['estado'] ?? ''));
if ($estado === 'SE ENVIO NUMERO DE CUENTA') {
if ($currentState !== 'SE ENVIO NUMERO DE CUENTA' || trim((string) $numeroCuentaEnviadoAt) === '') {
$numeroCuentaEnviadoAt = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
}
}
$stmt = $pdo->prepare(
'INSERT INTO callcenter_test_tracking (
@ -107,6 +119,7 @@ try {
observaciones,
proxima_llamada_at,
fecha_entrega_programada,
numero_cuenta_enviado_at,
ultima_gestion_at
) VALUES (
:source_key,
@ -122,6 +135,7 @@ try {
:observaciones,
:proxima_llamada_at,
:fecha_entrega_programada,
:numero_cuenta_enviado_at,
CURRENT_TIMESTAMP
)
ON DUPLICATE KEY UPDATE
@ -137,6 +151,7 @@ try {
observaciones = VALUES(observaciones),
proxima_llamada_at = VALUES(proxima_llamada_at),
fecha_entrega_programada = VALUES(fecha_entrega_programada),
numero_cuenta_enviado_at = VALUES(numero_cuenta_enviado_at),
ultima_gestion_at = CURRENT_TIMESTAMP,
updated_at = CURRENT_TIMESTAMP'
);
@ -155,6 +170,7 @@ try {
':observaciones' => $observaciones,
':proxima_llamada_at' => $proximaLlamada,
':fecha_entrega_programada' => $fechaEntrega,
':numero_cuenta_enviado_at' => $numeroCuentaEnviadoAt,
]);
echo json_encode([
@ -163,6 +179,7 @@ try {
'estado' => $estado,
'proxima_llamada_at' => $proximaLlamada,
'fecha_entrega_programada' => $fechaEntrega,
'numero_cuenta_enviado_at' => $numeroCuentaEnviadoAt,
]);
} catch (Throwable $exception) {
http_response_code(500);