Autosave: 20260710-084941
This commit is contained in:
parent
723db4cb01
commit
c1dd787c2d
@ -8,6 +8,7 @@ require_once 'includes/contraentrega_cobertura.php';
|
||||
$provinciasPorDepartamentoContraentrega = contraentregaProvinciasPorDepartamento();
|
||||
$distritosPorProvinciaContraentrega = contraentregaDistritosPorProvincia();
|
||||
$departamentosContraentrega = array_keys($provinciasPorDepartamentoContraentrega);
|
||||
$sedesShalom = cc_test_fetch_shalom_sedes(db());
|
||||
|
||||
$pageTitle = 'Pedidos Asignados | Bandeja de la asesora';
|
||||
$pageDescription = 'Bandeja para que cada asesora gestione sus pedidos asignados con la misma interfaz, estados, historial y acciones.';
|
||||
@ -147,10 +148,11 @@ function cc_test_followup_semaforo(array $order): ?array
|
||||
|
||||
$view = $_GET['view'] ?? 'pendientes_hoy';
|
||||
$allowedViews = [
|
||||
'pendientes_hoy' => 'Pendientes por llamar',
|
||||
'pendientes_hoy' => 'Bandeja principal',
|
||||
'nuevos_hoy' => 'Nuevos de hoy',
|
||||
'confirmados' => 'Confirmados',
|
||||
'seguimiento' => 'Seguimiento',
|
||||
'promo_final' => 'Promo Final',
|
||||
'observados' => 'Observados',
|
||||
'cerrados' => 'Cerrados / descartados',
|
||||
'todos' => 'Todos los pedidos cargados',
|
||||
@ -185,6 +187,7 @@ $stats = [
|
||||
'nuevos_hoy' => 0,
|
||||
'confirmados' => 0,
|
||||
'seguimiento' => 0,
|
||||
'promo_final' => 0,
|
||||
'observados' => 0,
|
||||
'cerrados' => 0,
|
||||
];
|
||||
@ -301,25 +304,39 @@ try {
|
||||
$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);
|
||||
$followupDayInfo = cc_test_followup_day_info($order);
|
||||
$order['seguimiento_dia_info'] = $followupDayInfo;
|
||||
$order['dias_seguimiento'] = $followupDayInfo['day_number'] ?? null;
|
||||
$order['promo_final_habilitado'] = !empty($followupDayInfo['is_promo_final']);
|
||||
$order['promo_final_evidencia_cargada'] = trim((string) ($order['promo_final_evidencia_path'] ?? '')) !== '';
|
||||
$order['es_promo_final'] = $order['promo_final_habilitado'] && $order['promo_final_evidencia_cargada'];
|
||||
$order['promo_final_pendiente'] = $order['promo_final_habilitado'] && !$order['promo_final_evidencia_cargada'];
|
||||
$order['pendiente_logistica_destino'] = $storeKey === 'otra_tienda'
|
||||
? cc_test_pending_logistica_destination($order)
|
||||
: null;
|
||||
$order['pendiente_logistica'] = $order['pendiente_logistica_destino'] !== null;
|
||||
|
||||
$order['es_nuevo_hoy'] = $importDate ? $importDate->format('Y-m-d') === $todayStart->format('Y-m-d') : false;
|
||||
$order['es_pendiente_hoy'] = in_array($order['estado'], $openStates, true);
|
||||
$order['es_pendiente_hoy'] = !$order['es_promo_final'] && (in_array($order['estado'], $openStates, true) || $order['pendiente_logistica']);
|
||||
$order['es_cerrado'] = in_array($order['estado'], $closedStates, true);
|
||||
|
||||
$stats['total']++;
|
||||
if ($order['es_nuevo_hoy']) {
|
||||
$stats['nuevos_hoy']++;
|
||||
}
|
||||
if ($order['es_promo_final']) {
|
||||
$stats['promo_final']++;
|
||||
}
|
||||
if ($order['es_pendiente_hoy']) {
|
||||
$stats['pendientes_hoy']++;
|
||||
}
|
||||
if (in_array($order['estado'], cc_test_confirmed_states(), true)) {
|
||||
$stats['confirmados']++;
|
||||
}
|
||||
if ($order['estado'] === 'SE ENVIO NUMERO DE CUENTA') {
|
||||
if ($order['estado'] === 'SE ENVIO NUMERO DE CUENTA' && !$order['es_promo_final']) {
|
||||
$stats['seguimiento']++;
|
||||
}
|
||||
if ($order['estado'] === 'OBSERVADO') {
|
||||
if ($order['estado'] === 'OBSERVADO' && !$order['es_promo_final']) {
|
||||
$stats['observados']++;
|
||||
}
|
||||
if ($order['es_cerrado']) {
|
||||
@ -333,14 +350,15 @@ try {
|
||||
'pendientes_hoy' => (bool) ($order['es_pendiente_hoy'] ?? false),
|
||||
'nuevos_hoy' => (bool) ($order['es_nuevo_hoy'] ?? false),
|
||||
'confirmados' => in_array(($order['estado'] ?? ''), cc_test_confirmed_states(), true),
|
||||
'seguimiento' => ($order['estado'] ?? '') === 'SE ENVIO NUMERO DE CUENTA',
|
||||
'observados' => ($order['estado'] ?? '') === 'OBSERVADO',
|
||||
'seguimiento' => ($order['estado'] ?? '') === 'SE ENVIO NUMERO DE CUENTA' && !($order['es_promo_final'] ?? false),
|
||||
'promo_final' => (bool) ($order['es_promo_final'] ?? false),
|
||||
'observados' => ($order['estado'] ?? '') === 'OBSERVADO' && !($order['es_promo_final'] ?? false),
|
||||
'cerrados' => (bool) ($order['es_cerrado'] ?? false),
|
||||
default => true,
|
||||
};
|
||||
}));
|
||||
|
||||
usort($visibleOrders, static function (array $a, array $b) use ($view): int {
|
||||
usort($visibleOrders, static function (array $a, array $b) use ($view, $storeKey): int {
|
||||
$aAgregado = !empty($a['is_agregado']);
|
||||
$bAgregado = !empty($b['is_agregado']);
|
||||
if ($aAgregado !== $bAgregado) {
|
||||
@ -350,11 +368,30 @@ try {
|
||||
$aImport = cc_test_import_time($a);
|
||||
$bImport = cc_test_import_time($b);
|
||||
|
||||
if ($aImport !== $bImport) {
|
||||
return $bImport <=> $aImport;
|
||||
if ($view === 'promo_final') {
|
||||
$aDays = (int) ($a['dias_seguimiento'] ?? 0);
|
||||
$bDays = (int) ($b['dias_seguimiento'] ?? 0);
|
||||
if ($aDays !== $bDays) {
|
||||
return $bDays <=> $aDays;
|
||||
}
|
||||
if ($aImport !== $bImport) {
|
||||
return $aImport <=> $bImport;
|
||||
}
|
||||
}
|
||||
|
||||
if ($view === "pendientes_hoy") {
|
||||
$aPendientePromoFinal = !empty($a['promo_final_pendiente']);
|
||||
$bPendientePromoFinal = !empty($b['promo_final_pendiente']);
|
||||
if ($aPendientePromoFinal !== $bPendientePromoFinal) {
|
||||
return $aPendientePromoFinal ? -1 : 1;
|
||||
}
|
||||
|
||||
$aPendienteLogistica = !empty($a['pendiente_logistica']);
|
||||
$bPendienteLogistica = !empty($b['pendiente_logistica']);
|
||||
if ($aPendienteLogistica !== $bPendienteLogistica) {
|
||||
return $aPendienteLogistica ? -1 : 1;
|
||||
}
|
||||
|
||||
$aDue = cc_test_parse_datetime($a["proxima_llamada_at"] ?? null);
|
||||
$bDue = cc_test_parse_datetime($b["proxima_llamada_at"] ?? null);
|
||||
if ($aDue && $bDue && $aDue != $bDue) {
|
||||
@ -366,6 +403,18 @@ try {
|
||||
if (!$aDue && $bDue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($storeKey === 'otra_tienda') {
|
||||
$aSourceRow = (int) ($a['source_row'] ?? 0);
|
||||
$bSourceRow = (int) ($b['source_row'] ?? 0);
|
||||
if ($aSourceRow > 0 && $bSourceRow > 0 && $aSourceRow !== $bSourceRow) {
|
||||
return $bSourceRow <=> $aSourceRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($aImport !== $bImport) {
|
||||
return $bImport <=> $aImport;
|
||||
}
|
||||
|
||||
return cc_test_order_time($b) <=> cc_test_order_time($a);
|
||||
@ -400,7 +449,7 @@ require_once 'layout_header.php';
|
||||
<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">Agregados: <?php echo (int) $agregadosCount; ?></span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2"><?php if ($storeKey === 'otra_tienda' && $tuaniLastProcessedRow !== null): ?>TUANI: último procesado <?php echo (int) $tuaniLastProcessedRow; ?> · próxima desde <?php echo (int) $startRow; ?><?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"><?php if ($storeKey === 'otra_tienda' && $tuaniLastProcessedRow !== null): ?>TUANI: distribución desde fila <?php echo (int) ($storeConfig['startRow'] ?? $startRow); ?> · último procesado <?php echo (int) $tuaniLastProcessedRow; ?><?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">Auto actualización: 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>
|
||||
</div>
|
||||
@ -423,7 +472,7 @@ require_once 'layout_header.php';
|
||||
<a href="?view=pendientes_hoy&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'pendientes_hoy' ? 'bg-dark text-white' : 'bg-white'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase opacity-75 mb-2">Pendientes por llamar</div>
|
||||
<div class="small text-uppercase opacity-75 mb-2">Bandeja principal</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['pendientes_hoy']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
@ -459,6 +508,16 @@ require_once 'layout_header.php';
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="?view=promo_final&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'promo_final' ? 'bg-danger-subtle border border-danger' : 'bg-white'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Promo Final</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['promo_final']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="?view=observados&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'observados' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
|
||||
@ -495,7 +554,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. <strong>Promo Final</strong> se habilita desde el Día 4 y exige imagen de sustento para mover el pedido.</p>
|
||||
</div>
|
||||
<span class="badge bg-light text-dark border"><?php echo count($visibleOrders); ?> pedidos en esta bandeja</span>
|
||||
</div>
|
||||
@ -530,12 +589,48 @@ require_once 'layout_header.php';
|
||||
$distritosSeleccionados = $distritosPorProvinciaContraentrega[$provinciaSeleccionada] ?? [];
|
||||
$modalId = 'modalDriveTest' . $order['source_key'];
|
||||
$badgeClass = cc_test_badge_class($order['estado']);
|
||||
$followupDayInfo = $order['seguimiento_dia_info'] ?? null;
|
||||
$semaforoCuenta = cc_test_followup_semaforo($order);
|
||||
$historial = cc_test_fetch_historial(db(), $order['source_key']);
|
||||
$logisticaPendienteLabel = !empty($order['pendiente_logistica']) ? cc_test_pending_logistica_label($order) : null;
|
||||
$logisticaPendienteDetalle = match ($order['pendiente_logistica_destino'] ?? null) {
|
||||
'ruta' => 'Falta subirlo a Ruta Contraentrega.',
|
||||
'rotulado' => 'Falta subirlo a Pedidos Rotulados.',
|
||||
default => '',
|
||||
};
|
||||
$subirLogisticaButtonLabel = 'Subir pedido';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-muted';
|
||||
$subirLogisticaNoteHtml = 'Selecciona <strong>CONFIRMADO CONTRAENTREGA</strong> o <strong>CONFIRMADO ENVIO</strong> para enviarlo a logística.';
|
||||
if ($storeKey === 'otra_tienda') {
|
||||
if ($order['estado'] === 'CONFIRMADO CONTRAENTREGA') {
|
||||
if (!empty($order['ruta_contraentrega_pedido_id'])) {
|
||||
$subirLogisticaButtonLabel = 'Actualizar en ruta';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-success fw-semibold';
|
||||
$subirLogisticaNoteHtml = 'En Ruta Contraentrega #' . (int) $order['ruta_contraentrega_pedido_id'] . '. Si cambias algo, usa este botón para actualizarlo.';
|
||||
} else {
|
||||
$subirLogisticaButtonLabel = 'Subir a ruta';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-warning-emphasis';
|
||||
$subirLogisticaNoteHtml = '<strong>PENDIENTE A SUBIR</strong>: este pedido ya está en <strong>CONFIRMADO CONTRAENTREGA</strong>. Súbelo a Ruta Contraentrega con este botón.';
|
||||
}
|
||||
} elseif ($order['estado'] === 'CONFIRMADO ENVIO') {
|
||||
if (!empty($order['pedido_rotulado_pedido_id'])) {
|
||||
$subirLogisticaButtonLabel = 'Actualizar rotulado';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-success fw-semibold';
|
||||
$subirLogisticaNoteHtml = 'En Pedidos Rotulados #' . (int) $order['pedido_rotulado_pedido_id'] . '. Si cambias algo, usa este botón para actualizarlo.';
|
||||
} else {
|
||||
$subirLogisticaButtonLabel = 'Subir a rotulados';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-warning-emphasis';
|
||||
$subirLogisticaNoteHtml = '<strong>PENDIENTE A SUBIR</strong>: este pedido ya está en <strong>CONFIRMADO ENVIO</strong>. Súbelo a Pedidos Rotulados con este botón.';
|
||||
}
|
||||
}
|
||||
}
|
||||
ob_start();
|
||||
?>
|
||||
<tr class="cc-callcenter-row" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>" style="<?php echo htmlspecialchars(cc_test_row_background_style((string) ($order['estado'] ?? 'POR LLAMAR'))); ?>">
|
||||
<td>
|
||||
<?php if ($followupDayInfo !== null): ?>
|
||||
<div class="small text-uppercase fw-semibold <?php echo htmlspecialchars($followupDayInfo['text_class']); ?> mb-1"><?php echo htmlspecialchars($followupDayInfo['display_label']); ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="fw-bold fs-6"><?php echo htmlspecialchars(cc_test_order_label($order)); ?></div>
|
||||
<div class="small text-muted">ID Drive: <?php echo htmlspecialchars(cc_test_display_value($order['import_id'] ?? null, 'Sin ID')); ?></div>
|
||||
</td>
|
||||
@ -565,13 +660,25 @@ 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 (!empty($logisticaPendienteLabel)): ?>
|
||||
<span class="badge rounded-pill bg-warning-subtle text-warning-emphasis border"><?php echo htmlspecialchars($logisticaPendienteLabel); ?></span>
|
||||
<?php endif; ?>
|
||||
<?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 (!empty($logisticaPendienteDetalle)): ?>
|
||||
<div class="small text-warning-emphasis fw-semibold mb-1"><?php echo htmlspecialchars($logisticaPendienteDetalle); ?></div>
|
||||
<?php endif; ?>
|
||||
<?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; ?>
|
||||
<?php if ($followupDayInfo !== null): ?>
|
||||
<div class="small text-muted mb-1">Seguimiento: <?php echo htmlspecialchars($followupDayInfo['display_label']); ?> · desde <?php echo htmlspecialchars($followupDayInfo['started_at_label']); ?></div>
|
||||
<?php if (!empty($followupDayInfo['notice_text'])): ?>
|
||||
<div class="small <?php echo htmlspecialchars($followupDayInfo['row_notice_class']); ?> fw-semibold mb-1"><?php echo htmlspecialchars($followupDayInfo['notice_text']); ?></div>
|
||||
<?php endif; ?>
|
||||
<?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>
|
||||
@ -579,6 +686,9 @@ require_once 'layout_header.php';
|
||||
<?php if (!empty($order['ruta_contraentrega_pedido_id'])): ?>
|
||||
<div class="small text-success">Subido a Ruta Contraentrega #<?php echo (int) $order['ruta_contraentrega_pedido_id']; ?><?php if (!empty($order['ruta_contraentrega_subido_at'])): ?> · <?php echo htmlspecialchars(cc_test_format_datetime($order['ruta_contraentrega_subido_at'] ?? null, '')); ?><?php endif; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['pedido_rotulado_pedido_id'])): ?>
|
||||
<div class="small text-primary">Subido a Pedidos Rotulados #<?php echo (int) $order['pedido_rotulado_pedido_id']; ?><?php if (!empty($order['pedido_rotulado_subido_at'])): ?> · <?php echo htmlspecialchars(cc_test_format_datetime($order['pedido_rotulado_subido_at'] ?? null, '')); ?><?php endif; ?></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>
|
||||
</td>
|
||||
@ -606,6 +716,8 @@ require_once 'layout_header.php';
|
||||
$assignFormClass .= ' bg-primary-subtle border-primary';
|
||||
} elseif ($assignedAssessorKey === 'ESTEFANYA') {
|
||||
$assignFormClass .= ' bg-success-subtle border-success';
|
||||
} elseif ($assignedAssessorKey === 'CARMEN') {
|
||||
$assignFormClass .= ' bg-warning-subtle border-warning';
|
||||
} else {
|
||||
$assignFormClass .= ' bg-secondary-subtle border-secondary';
|
||||
}
|
||||
@ -629,6 +741,8 @@ require_once 'layout_header.php';
|
||||
<span class="badge bg-primary-subtle text-primary-emphasis border">KARINA</span>
|
||||
<?php elseif ($assignedAssessorKey === 'ESTEFANYA'): ?>
|
||||
<span class="badge bg-success-subtle text-success-emphasis border">ESTEFANYA</span>
|
||||
<?php elseif ($assignedAssessorKey === 'CARMEN'): ?>
|
||||
<span class="badge bg-warning-subtle text-warning-emphasis border">CARMEN</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary-subtle text-secondary-emphasis border">Asignado</span>
|
||||
<?php endif; ?>
|
||||
@ -683,23 +797,52 @@ require_once 'layout_header.php';
|
||||
<div class="modal-content">
|
||||
<div class="modal-header align-items-start gap-2">
|
||||
<div class="flex-grow-1 min-w-0">
|
||||
<div class="small text-primary fw-semibold mb-1"><?php echo htmlspecialchars(cc_test_order_label($order)); ?></div>
|
||||
<div class="d-flex flex-wrap align-items-center gap-2 mb-1">
|
||||
<div class="small text-primary fw-semibold mb-0"><?php echo htmlspecialchars(cc_test_order_label($order)); ?></div>
|
||||
<?php if ($followupDayInfo !== null): ?>
|
||||
<span class="badge rounded-pill <?php echo htmlspecialchars($followupDayInfo['badge_class']); ?>"><?php echo htmlspecialchars($followupDayInfo['display_label']); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<h3 class="modal-title h5 mb-0"><?php echo htmlspecialchars(cc_test_display_value($order['nombre'], 'Cliente sin nombre')); ?></h3>
|
||||
<?php if ($storeKey === 'otra_tienda'): ?>
|
||||
<button type="button" class="btn btn-primary btn-sm px-3" id="subir-ruta-<?php echo htmlspecialchars($order['source_key']); ?>" onclick="guardarGestion('<?php echo htmlspecialchars($order['source_key']); ?>', this, { subirRuta: true })"><?php echo !empty($order['ruta_contraentrega_pedido_id']) ? 'Actualizar en ruta' : 'Subir pedido'; ?></button>
|
||||
<button type="button" class="btn btn-primary btn-sm px-3" id="subir-logistica-<?php echo htmlspecialchars($order['source_key']); ?>" data-route-pedido-id="<?php echo (int) ($order['ruta_contraentrega_pedido_id'] ?? 0); ?>" data-rotulado-pedido-id="<?php echo (int) ($order['pedido_rotulado_pedido_id'] ?? 0); ?>" onclick="guardarGestion('<?php echo htmlspecialchars($order['source_key']); ?>', this, { subirLogistica: true })"><?php echo htmlspecialchars($subirLogisticaButtonLabel); ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="small text-muted mt-1"><?php echo htmlspecialchars(cc_test_display_value($order['producto'], 'Sin producto')); ?> · <?php echo htmlspecialchars(cc_test_display_value($order['celular'], 'Sin celular')); ?></div>
|
||||
<?php if ($storeKey === 'otra_tienda'): ?>
|
||||
<div class="small mt-2 <?php echo !empty($order['ruta_contraentrega_pedido_id']) ? 'text-success fw-semibold' : 'text-muted'; ?>">
|
||||
<?php if (!empty($order['ruta_contraentrega_pedido_id'])): ?>
|
||||
En Ruta Contraentrega #<?php echo (int) $order['ruta_contraentrega_pedido_id']; ?>. Si cambias algo, usa este botón para actualizarlo.
|
||||
<?php else: ?>
|
||||
Súbelo cuando quede en <strong>CONFIRMADO CONTRAENTREGA</strong>.
|
||||
<?php endif; ?>
|
||||
<?php if ($followupDayInfo !== null): ?>
|
||||
<div class="small mt-2 <?php echo htmlspecialchars(!empty($followupDayInfo['notice_text']) ? $followupDayInfo['text_class'] : 'text-muted'); ?><?php echo !empty($followupDayInfo['notice_text']) ? ' fw-semibold' : ''; ?>">
|
||||
<?php echo htmlspecialchars(!empty($followupDayInfo['notice_text']) ? $followupDayInfo['notice_text'] : ('Seguimiento iniciado el ' . $followupDayInfo['started_at_label'] . '.')); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php $promoFinalProofPath = trim((string) ($order['promo_final_evidencia_path'] ?? '')); ?>
|
||||
<?php $promoFinalMoveAvailable = !empty($order['promo_final_habilitado']) && in_array($order['estado'], cc_test_followup_tracking_states(), true); ?>
|
||||
<?php if ($promoFinalMoveAvailable || $promoFinalProofPath !== ''): ?>
|
||||
<div
|
||||
class="mt-2 js-promo-final-controls"
|
||||
id="promo-final-controls-<?php echo htmlspecialchars($order['source_key']); ?>"
|
||||
data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>"
|
||||
data-promo-final-candidate="<?php echo !empty($order['promo_final_habilitado']) ? '1' : '0'; ?>"
|
||||
data-has-existing="<?php echo $promoFinalProofPath !== '' ? '1' : '0'; ?>"
|
||||
>
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<button type="button" class="btn btn-outline-danger btn-sm px-3 <?php echo ($promoFinalMoveAvailable && $promoFinalProofPath === '') ? '' : 'd-none'; ?>" id="mover-promo-final-<?php echo htmlspecialchars($order['source_key']); ?>" onclick="guardarGestion('<?php echo htmlspecialchars($order['source_key']); ?>', this, { moverPromoFinal: true })">Mover a Promo Final</button>
|
||||
<?php if ($promoFinalProofPath !== ''): ?>
|
||||
<a href="<?php echo htmlspecialchars($promoFinalProofPath); ?>" class="btn btn-outline-success btn-sm" target="_blank" rel="noopener">Ver sustento Promo Final</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="small mt-2 <?php echo $promoFinalProofPath !== '' ? 'text-success fw-semibold' : 'text-danger-emphasis'; ?>" id="mover-promo-final-note-<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<?php if ($promoFinalProofPath !== ''): ?>
|
||||
Promo Final con sustento cargado<?php if (!empty($order['promo_final_evidencia_subido_at'])): ?> · <?php echo htmlspecialchars(cc_test_format_datetime($order['promo_final_evidencia_subido_at'] ?? null, '')); ?><?php endif; ?>.
|
||||
<?php else: ?>
|
||||
Debes subir una imagen de sustento y luego usar este botón para moverlo a Promo Final.
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($storeKey === 'otra_tienda'): ?>
|
||||
<div class="<?php echo htmlspecialchars($subirLogisticaNoteClass); ?>" id="subir-logistica-note-<?php echo htmlspecialchars($order['source_key']); ?>"><?php echo $subirLogisticaNoteHtml; ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<button type="button" class="btn-close mt-1" data-bs-dismiss="modal" aria-label="Cerrar"></button>
|
||||
</div>
|
||||
@ -862,6 +1005,42 @@ require_once 'layout_header.php';
|
||||
<label for="nota-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">COLOCAR AQUI OBSERVACION DEL CLIENTE O (DEDICATORIA O GRABADO)</label>
|
||||
<textarea class="form-control" id="nota-<?php echo htmlspecialchars($order['source_key']); ?>" rows="3" placeholder="Ej.: Solicita dedicatoria para hija María, Ej.: Desea antes de las 4PM"><?php echo htmlspecialchars($order['nota_seguimiento']); ?></textarea>
|
||||
</div>
|
||||
<?php $numeroCuentaSedeIdSeleccionado = trim((string) ($order['numero_cuenta_sede_id'] ?? '')); ?>
|
||||
<?php $numeroCuentaDniSeleccionado = trim((string) ($order['numero_cuenta_dni'] ?? '')); ?>
|
||||
<?php $mostrarCamposNumeroCuenta = cc_test_requires_account_number_fields($order['estado']); ?>
|
||||
<div class="col-md-6 js-numero-cuenta-group <?php echo $mostrarCamposNumeroCuenta ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="numero_cuenta_sede_id-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">SEDE / ID</label>
|
||||
<input type="text" class="form-control" id="numero_cuenta_sede_id-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars($numeroCuentaSedeIdSeleccionado); ?>" placeholder="Ej.: SJL / 1542">
|
||||
<div class="form-text">Solo aparece en <strong>SE ENVIO NUMERO DE CUENTA</strong>.</div>
|
||||
</div>
|
||||
<div class="col-md-6 js-numero-cuenta-group <?php echo $mostrarCamposNumeroCuenta ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="numero_cuenta_dni-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">N° DNI</label>
|
||||
<input type="text" class="form-control" id="numero_cuenta_dni-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars($numeroCuentaDniSeleccionado); ?>" placeholder="Ej.: 76543210">
|
||||
</div>
|
||||
<?php $promoFinalProofPath = trim((string) ($order['promo_final_evidencia_path'] ?? '')); ?>
|
||||
<?php $canceladoProofPath = trim((string) ($order['cancelado_evidencia_path'] ?? '')); ?>
|
||||
<?php $mostrarPromoFinalProof = !empty($order['promo_final_habilitado']) || $promoFinalProofPath !== ''; ?>
|
||||
<?php $mostrarCanceladoProof = $order['estado'] === 'CANCELADO' || $canceladoProofPath !== ''; ?>
|
||||
<div class="col-12 js-promo-final-proof-group <?php echo $mostrarPromoFinalProof ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>" data-promo-final-candidate="<?php echo !empty($order['promo_final_habilitado']) ? '1' : '0'; ?>" data-has-existing="<?php echo $promoFinalProofPath !== '' ? '1' : '0'; ?>">
|
||||
<label for="promo_final_evidencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Imagen de sustento para Promo Final</label>
|
||||
<input type="file" class="form-control" id="promo_final_evidencia-<?php echo htmlspecialchars($order['source_key']); ?>" accept=".jpg,.jpeg,.png,.webp">
|
||||
<div class="form-text">Obligatoria al mover el pedido a <strong>Promo Final</strong> desde el Día 4. La supervisora podrá revisarla.</div>
|
||||
<?php if ($promoFinalProofPath !== ''): ?>
|
||||
<div class="small text-success mt-2">
|
||||
Sustento actual: <a href="<?php echo htmlspecialchars($promoFinalProofPath); ?>" target="_blank" rel="noopener">ver imagen</a><?php if (!empty($order['promo_final_evidencia_subido_at'])): ?> · <?php echo htmlspecialchars(cc_test_format_datetime($order['promo_final_evidencia_subido_at'] ?? null, '')); ?><?php endif; ?>.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="col-12 js-cancelado-proof-group <?php echo $mostrarCanceladoProof ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>" data-has-existing="<?php echo $canceladoProofPath !== '' ? '1' : '0'; ?>">
|
||||
<label for="cancelado_evidencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Imagen de sustento para Cancelado</label>
|
||||
<input type="file" class="form-control" id="cancelado_evidencia-<?php echo htmlspecialchars($order['source_key']); ?>" accept=".jpg,.jpeg,.png,.webp">
|
||||
<div class="form-text">Obligatoria cuando el estado sea <strong>Cancelado</strong>. Así evitas cancelaciones sin motivo.</div>
|
||||
<?php if ($canceladoProofPath !== ''): ?>
|
||||
<div class="small text-success mt-2">
|
||||
Sustento actual: <a href="<?php echo htmlspecialchars($canceladoProofPath); ?>" target="_blank" rel="noopener">ver imagen</a><?php if (!empty($order['cancelado_evidencia_subido_at'])): ?> · <?php echo htmlspecialchars(cc_test_format_datetime($order['cancelado_evidencia_subido_at'] ?? null, '')); ?><?php endif; ?>.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
@ -876,8 +1055,10 @@ require_once 'layout_header.php';
|
||||
<label for="referencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Referencia</label>
|
||||
<textarea class="form-control" id="referencia-<?php echo htmlspecialchars($order['source_key']); ?>" rows="2"><?php echo htmlspecialchars((string) ($order['referencia'] ?? '')); ?></textarea>
|
||||
</div>
|
||||
<?php $agenciaSeleccionada = strtoupper(trim((string) ($order['agencia'] ?? ''))); ?>
|
||||
<?php $agenciaSeleccionada = cc_test_normalize_shipping_agency($order['agencia'] ?? '') ?? strtoupper(trim((string) ($order['agencia'] ?? ''))); ?>
|
||||
<?php $sedeAgenciaSeleccionada = trim((string) ($order['sede_agencia'] ?? '')); ?>
|
||||
<?php $montoAdelantadoSeleccionado = trim((string) ($order['monto_adelantado'] ?? '')); ?>
|
||||
<?php $mostrarCamposEnvio = cc_test_requires_shipping_details($order['estado']); ?>
|
||||
<div class="col-md-4">
|
||||
<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']); ?>">
|
||||
@ -934,33 +1115,34 @@ require_once 'layout_header.php';
|
||||
<input type="text" class="form-control" id="coordenadas-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars((string) ($order['coordenadas'] ?? '')); ?>" inputmode="decimal" autocomplete="off" spellcheck="false" placeholder="-12.082029, -77.069024">
|
||||
<div class="form-text">Obligatorias para <strong>Subir pedido</strong> a Ruta Contraentrega.</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-4 js-envio-group <?php echo $mostrarCamposEnvio ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="agencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Agencia</label>
|
||||
<select class="form-select" id="agencia-<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<option value="">Seleccione agencia</option>
|
||||
<?php foreach (['SHALOM', 'OLVA COURIER', 'OTROS'] as $agenciaOption): ?>
|
||||
<?php foreach (cc_test_shipping_agency_options() as $agenciaOption): ?>
|
||||
<option value="<?php echo htmlspecialchars($agenciaOption); ?>" <?php echo $agenciaSeleccionada === $agenciaOption ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($agenciaOption); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php if ($agenciaSeleccionada !== '' && !in_array($agenciaSeleccionada, ['SHALOM', 'OLVA COURIER', 'OTROS'], true)): ?>
|
||||
<?php if ($agenciaSeleccionada !== '' && !in_array($agenciaSeleccionada, cc_test_shipping_agency_options(), true)): ?>
|
||||
<option value="<?php echo htmlspecialchars($agenciaSeleccionada); ?>" selected>
|
||||
<?php echo htmlspecialchars($agenciaSeleccionada); ?>
|
||||
</option>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="sede_agencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Sede</label>
|
||||
<input type="text" class="form-control" id="sede_agencia-<?php echo htmlspecialchars($order['source_key']); ?>" placeholder="Escriba la sede para envío por agencia" value="<?php echo htmlspecialchars($sedeAgenciaSeleccionada); ?>">
|
||||
<div class="col-md-4 js-envio-group <?php echo $mostrarCamposEnvio ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="sede_agencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Sede de envío</label>
|
||||
<input type="text" class="form-control" id="sede_agencia-<?php echo htmlspecialchars($order['source_key']); ?>" placeholder="Seleccione o escriba la sede de envío" value="<?php echo htmlspecialchars($sedeAgenciaSeleccionada); ?>" <?php echo ($agenciaSeleccionada === 'SHALOM' && !empty($sedesShalom)) ? 'list="cc-sedes-shalom-list"' : ''; ?>>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-2 js-envio-group <?php echo $mostrarCamposEnvio ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="dni-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">DNI</label>
|
||||
<input type="text" class="form-control" id="dni-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars((string) ($order['dni'] ?? '')); ?>">
|
||||
<div class="form-text">Obligatorio para <strong>CONFIRMADO ENVIO</strong>.</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="observaciones-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Observaciones del pedido</label>
|
||||
<textarea class="form-control" id="observaciones-<?php echo htmlspecialchars($order['source_key']); ?>" rows="2"><?php echo htmlspecialchars((string) ($order['observaciones'] ?? '')); ?></textarea>
|
||||
<div class="col-md-2 js-envio-group <?php echo $mostrarCamposEnvio ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="monto_adelantado-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Monto de adelanto</label>
|
||||
<input type="text" class="form-control" id="monto_adelantado-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars($montoAdelantadoSeleccionado); ?>" inputmode="decimal" placeholder="0.00">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1002,7 +1184,7 @@ require_once 'layout_header.php';
|
||||
<div class="modal-footer d-flex justify-content-between flex-wrap gap-2">
|
||||
<div class="small text-muted">
|
||||
<?php if ($storeKey === 'otra_tienda'): ?>
|
||||
<strong>Guardar gestión</strong> solo guarda el historial. Para enviarlo a logística, usa <strong>Subir pedido</strong> junto al nombre del cliente.
|
||||
<strong>Guardar gestión</strong> solo guarda el historial. Para enviarlo a logística, usa el <strong>botón del encabezado</strong> junto al nombre del cliente.
|
||||
<?php else: ?>
|
||||
Los cambios se guardan en la base local del módulo de prueba.
|
||||
<?php endif; ?>
|
||||
@ -1055,6 +1237,13 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($sedesShalom)): ?>
|
||||
<datalist id="cc-sedes-shalom-list">
|
||||
<?php foreach ($sedesShalom as $sedeShalomOption): ?>
|
||||
<option value="<?php echo htmlspecialchars($sedeShalomOption); ?>"></option>
|
||||
<?php endforeach; ?>
|
||||
</datalist>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
@ -1179,14 +1368,107 @@ function renderProvinceOptions(sourceKey, preserveSelection = true) {
|
||||
renderDistrictOptions(sourceKey, preserveSelection);
|
||||
}
|
||||
|
||||
function updateShippingSedeList(sourceKey) {
|
||||
const agenciaSelect = document.getElementById('agencia-' + sourceKey);
|
||||
const sedeInput = document.getElementById('sede_agencia-' + sourceKey);
|
||||
const sedesListId = 'cc-sedes-shalom-list';
|
||||
|
||||
if (!agenciaSelect || !sedeInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (agenciaSelect.value === 'SHALOM' && document.getElementById(sedesListId)) {
|
||||
sedeInput.setAttribute('list', sedesListId);
|
||||
} else {
|
||||
sedeInput.removeAttribute('list');
|
||||
}
|
||||
}
|
||||
|
||||
function updateLogisticaButton(sourceKey) {
|
||||
const estado = document.getElementById('estado-' + sourceKey)?.value || '';
|
||||
const button = document.getElementById('subir-logistica-' + sourceKey);
|
||||
const note = document.getElementById('subir-logistica-note-' + sourceKey);
|
||||
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
const routeId = parseInt(button.dataset.routePedidoId || '0', 10);
|
||||
const rotuladoId = parseInt(button.dataset.rotuladoPedidoId || '0', 10);
|
||||
|
||||
if (estado === 'CONFIRMADO CONTRAENTREGA') {
|
||||
button.disabled = false;
|
||||
button.textContent = routeId > 0 ? 'Actualizar en ruta' : 'Subir a ruta';
|
||||
if (note) {
|
||||
note.className = 'small mt-2 ' + (routeId > 0 ? 'text-success fw-semibold' : 'text-warning-emphasis');
|
||||
note.innerHTML = routeId > 0
|
||||
? 'En Ruta Contraentrega #' + routeId + '. Si cambias algo, usa este botón para actualizarlo.'
|
||||
: '<strong>PENDIENTE A SUBIR</strong>: este pedido ya está en <strong>CONFIRMADO CONTRAENTREGA</strong>. Súbelo a Ruta Contraentrega con este botón.';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (estado === 'CONFIRMADO ENVIO') {
|
||||
button.disabled = false;
|
||||
button.textContent = rotuladoId > 0 ? 'Actualizar rotulado' : 'Subir a rotulados';
|
||||
if (note) {
|
||||
note.className = 'small mt-2 ' + (rotuladoId > 0 ? 'text-success fw-semibold' : 'text-warning-emphasis');
|
||||
note.innerHTML = rotuladoId > 0
|
||||
? 'En Pedidos Rotulados #' + rotuladoId + '. Si cambias algo, usa este botón para actualizarlo.'
|
||||
: '<strong>PENDIENTE A SUBIR</strong>: este pedido ya está en <strong>CONFIRMADO ENVIO</strong>. Súbelo a Pedidos Rotulados con este botón.';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Subir pedido';
|
||||
if (note) {
|
||||
note.className = 'small mt-2 text-muted';
|
||||
note.textContent = 'Selecciona CONFIRMADO CONTRAENTREGA o CONFIRMADO ENVIO para enviarlo a logística.';
|
||||
}
|
||||
}
|
||||
|
||||
function updatePromoFinalControls(sourceKey) {
|
||||
const estado = document.getElementById('estado-' + sourceKey)?.value || '';
|
||||
const followupStates = ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO', 'SE ENVIO NUMERO DE CUENTA'];
|
||||
|
||||
document.querySelectorAll('.js-promo-final-controls[data-source-key="' + sourceKey + '"]').forEach(block => {
|
||||
const isCandidate = block.dataset.promoFinalCandidate === '1';
|
||||
const hasExisting = block.dataset.hasExisting === '1';
|
||||
const canUsePromoFinal = isCandidate && followupStates.includes(estado);
|
||||
block.classList.toggle('d-none', !hasExisting && !canUsePromoFinal);
|
||||
|
||||
const moveButton = block.querySelector('button[id^="mover-promo-final-"]');
|
||||
if (moveButton) {
|
||||
moveButton.classList.toggle('d-none', !canUsePromoFinal || hasExisting);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.js-promo-final-proof-group[data-source-key="' + sourceKey + '"]').forEach(group => {
|
||||
const isCandidate = group.dataset.promoFinalCandidate === '1';
|
||||
const hasExisting = group.dataset.hasExisting === '1';
|
||||
const canUsePromoFinal = isCandidate && followupStates.includes(estado);
|
||||
group.classList.toggle('d-none', !hasExisting && !canUsePromoFinal);
|
||||
});
|
||||
}
|
||||
|
||||
function updateCanceladoEvidenceControls(sourceKey) {
|
||||
const estado = document.getElementById('estado-' + sourceKey)?.value || '';
|
||||
document.querySelectorAll('.js-cancelado-proof-group[data-source-key="' + sourceKey + '"]').forEach(group => {
|
||||
const hasExisting = group.dataset.hasExisting === '1';
|
||||
group.classList.toggle('d-none', !hasExisting && estado !== 'CANCELADO');
|
||||
});
|
||||
}
|
||||
|
||||
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 routeButton = document.getElementById('subir-ruta-' + sourceKey);
|
||||
const needsDeliveryDate = estado === 'CONFIRMADO CONTRAENTREGA';
|
||||
const needsShippingDetails = estado === 'CONFIRMADO ENVIO';
|
||||
const needsAccountNumberDetails = estado === 'SE ENVIO NUMERO DE CUENTA';
|
||||
const needsNextCall = ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'].includes(estado);
|
||||
|
||||
if (nextCallGroup) {
|
||||
@ -1195,15 +1477,23 @@ function toggleAgendaFields(sourceKey) {
|
||||
if (deliveryGroup) {
|
||||
deliveryGroup.classList.toggle('d-none', !needsDeliveryDate);
|
||||
}
|
||||
document.querySelectorAll('.js-envio-group[data-source-key="' + sourceKey + '"]').forEach(group => {
|
||||
group.classList.toggle('d-none', !needsShippingDetails);
|
||||
});
|
||||
document.querySelectorAll('.js-numero-cuenta-group[data-source-key="' + sourceKey + '"]').forEach(group => {
|
||||
group.classList.toggle('d-none', !needsAccountNumberDetails);
|
||||
});
|
||||
if (!needsNextCall && nextCallInput) {
|
||||
nextCallInput.value = '';
|
||||
}
|
||||
if (!needsDeliveryDate && deliveryInput) {
|
||||
deliveryInput.value = '';
|
||||
}
|
||||
if (routeButton) {
|
||||
routeButton.disabled = !needsDeliveryDate;
|
||||
}
|
||||
|
||||
updateShippingSedeList(sourceKey);
|
||||
updateLogisticaButton(sourceKey);
|
||||
updatePromoFinalControls(sourceKey);
|
||||
updateCanceladoEvidenceControls(sourceKey);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@ -1211,6 +1501,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
toggleAgendaFields(select.id.replace('estado-', ''));
|
||||
});
|
||||
|
||||
document.querySelectorAll('select[id^="agencia-"]').forEach(select => {
|
||||
const sourceKey = select.id.replace('agencia-', '');
|
||||
updateShippingSedeList(sourceKey);
|
||||
select.addEventListener('change', () => {
|
||||
updateShippingSedeList(sourceKey);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('select[id^="sede-"]').forEach(select => {
|
||||
const sourceKey = select.id.replace('sede-', '');
|
||||
renderProvinceOptions(sourceKey, true);
|
||||
@ -1296,10 +1594,30 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
function guardarGestion(sourceKey, trigger, options = {}) {
|
||||
const subirRuta = options.subirRuta === true;
|
||||
const body = new URLSearchParams({
|
||||
const subirLogistica = options.subirLogistica === true || options.subirRuta === true;
|
||||
const moverPromoFinal = options.moverPromoFinal === true;
|
||||
const estado = document.getElementById('estado-' + sourceKey)?.value || 'POR LLAMAR';
|
||||
const promoProofInput = document.getElementById('promo_final_evidencia-' + sourceKey);
|
||||
const cancelProofInput = document.getElementById('cancelado_evidencia-' + sourceKey);
|
||||
const promoProofGroup = document.querySelector('.js-promo-final-proof-group[data-source-key="' + sourceKey + '"]');
|
||||
const cancelProofGroup = document.querySelector('.js-cancelado-proof-group[data-source-key="' + sourceKey + '"]');
|
||||
const hasExistingPromoProof = promoProofGroup?.dataset.hasExisting === '1';
|
||||
const hasExistingCancelProof = cancelProofGroup?.dataset.hasExisting === '1';
|
||||
|
||||
if (moverPromoFinal && !hasExistingPromoProof && (!promoProofInput?.files || promoProofInput.files.length === 0)) {
|
||||
alert('Debes subir una imagen de sustento antes de mover el pedido a Promo Final.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (estado === 'CANCELADO' && !hasExistingCancelProof && (!cancelProofInput?.files || cancelProofInput.files.length === 0)) {
|
||||
alert('Debes subir una imagen de sustento para guardar el pedido en estado Cancelado.');
|
||||
return;
|
||||
}
|
||||
|
||||
const body = new FormData();
|
||||
const fields = {
|
||||
source_key: sourceKey,
|
||||
estado: document.getElementById('estado-' + sourceKey)?.value || 'POR LLAMAR',
|
||||
estado: estado,
|
||||
proxima_llamada_at: document.getElementById('proxima-' + sourceKey)?.value || '',
|
||||
fecha_entrega_programada: document.getElementById('fecha-entrega-' + sourceKey)?.value || '',
|
||||
nota_seguimiento: document.getElementById('nota-' + sourceKey)?.value || '',
|
||||
@ -1312,6 +1630,9 @@ function guardarGestion(sourceKey, trigger, options = {}) {
|
||||
distrito: document.getElementById('distrito-' + sourceKey)?.value || '',
|
||||
coordenadas: document.getElementById('coordenadas-' + sourceKey)?.value || '',
|
||||
dni: document.getElementById('dni-' + sourceKey)?.value || '',
|
||||
numero_cuenta_sede_id: document.getElementById('numero_cuenta_sede_id-' + sourceKey)?.value || '',
|
||||
numero_cuenta_dni: document.getElementById('numero_cuenta_dni-' + sourceKey)?.value || '',
|
||||
monto_adelantado: document.getElementById('monto_adelantado-' + sourceKey)?.value || '',
|
||||
observaciones: document.getElementById('observaciones-' + sourceKey)?.value || '',
|
||||
producto: document.getElementById('producto-' + sourceKey)?.value || '',
|
||||
cantidad: document.getElementById('cantidad-' + sourceKey)?.value || '',
|
||||
@ -1322,25 +1643,37 @@ function guardarGestion(sourceKey, trigger, options = {}) {
|
||||
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 || '',
|
||||
subir_a_ruta: subirRuta ? '1' : '0'
|
||||
subir_a_logistica: subirLogistica ? '1' : '0',
|
||||
mover_a_promo_final: moverPromoFinal ? '1' : '0'
|
||||
};
|
||||
|
||||
Object.entries(fields).forEach(([key, value]) => {
|
||||
body.append(key, value);
|
||||
});
|
||||
|
||||
if (moverPromoFinal && promoProofInput?.files?.[0]) {
|
||||
body.append('promo_final_evidencia', promoProofInput.files[0]);
|
||||
}
|
||||
|
||||
if (estado === 'CANCELADO' && cancelProofInput?.files?.[0]) {
|
||||
body.append('cancelado_evidencia', cancelProofInput.files[0]);
|
||||
}
|
||||
|
||||
if (trigger) {
|
||||
trigger.disabled = true;
|
||||
}
|
||||
|
||||
fetch('update_callcenter_test_tracking.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
body: body.toString()
|
||||
body: body
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
throw new Error(data.message || 'No se pudo guardar la gestión');
|
||||
}
|
||||
if (subirRuta) {
|
||||
alert(data.message || 'Pedido subido a Ruta Contraentrega correctamente.');
|
||||
if (subirLogistica || moverPromoFinal) {
|
||||
alert(data.message || 'Gestión actualizada correctamente.');
|
||||
}
|
||||
window.location.reload();
|
||||
})
|
||||
|
||||
@ -9,6 +9,7 @@ require_once 'includes/contraentrega_cobertura.php';
|
||||
$provinciasPorDepartamentoContraentrega = contraentregaProvinciasPorDepartamento();
|
||||
$distritosPorProvinciaContraentrega = contraentregaDistritosPorProvincia();
|
||||
$departamentosContraentrega = array_keys($provinciasPorDepartamentoContraentrega);
|
||||
$sedesShalom = cc_test_fetch_shalom_sedes(db());
|
||||
|
||||
$pageTitle = 'Base de Datos Pedidos | Asignación';
|
||||
$pageDescription = 'Bandejas de asignación y gestión de estados para el Call Center, cargadas desde Drive.';
|
||||
@ -148,10 +149,11 @@ function cc_test_followup_semaforo(array $order): ?array
|
||||
|
||||
$view = $_GET['view'] ?? 'pendientes_hoy';
|
||||
$allowedViews = [
|
||||
'pendientes_hoy' => 'Pendientes por llamar',
|
||||
'pendientes_hoy' => 'Bandeja principal',
|
||||
'nuevos_hoy' => 'Nuevos de hoy',
|
||||
'confirmados' => 'Confirmados',
|
||||
'seguimiento' => 'Seguimiento',
|
||||
'promo_final' => 'Promo Final',
|
||||
'observados' => 'Observados',
|
||||
'cerrados' => 'Cerrados / descartados',
|
||||
'todos' => 'Todos los pedidos cargados',
|
||||
@ -192,6 +194,7 @@ $stats = [
|
||||
'nuevos_hoy' => 0,
|
||||
'confirmados' => 0,
|
||||
'seguimiento' => 0,
|
||||
'promo_final' => 0,
|
||||
'observados' => 0,
|
||||
'cerrados' => 0,
|
||||
];
|
||||
@ -361,7 +364,7 @@ try {
|
||||
} elseif (empty($eligibleUnassigned)) {
|
||||
$noticeMessage = 'No hay pedidos sin asignar (pendientes) para rellenar los cupos.';
|
||||
} else {
|
||||
usort($eligibleUnassigned, static function (array $a, array $b): int {
|
||||
usort($eligibleUnassigned, static function (array $a, array $b) use ($storeKey): int {
|
||||
$aDue = cc_test_parse_datetime($a['proxima_llamada_at'] ?? null);
|
||||
$bDue = cc_test_parse_datetime($b['proxima_llamada_at'] ?? null);
|
||||
|
||||
@ -375,11 +378,19 @@ try {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($storeKey === 'otra_tienda') {
|
||||
$aSourceRow = (int) ($a['source_row'] ?? 0);
|
||||
$bSourceRow = (int) ($b['source_row'] ?? 0);
|
||||
if ($aSourceRow > 0 && $bSourceRow > 0 && $aSourceRow !== $bSourceRow) {
|
||||
return $bSourceRow <=> $aSourceRow;
|
||||
}
|
||||
}
|
||||
|
||||
return cc_test_order_time($a) <=> cc_test_order_time($b);
|
||||
});
|
||||
|
||||
$orderedAssessorIds = [];
|
||||
foreach (['KARINA', 'ESTEFANYA'] as $assessorKey) {
|
||||
foreach (cc_test_allowed_module_user_keys() as $assessorKey) {
|
||||
if (isset($assessors[$assessorKey])) {
|
||||
$orderedAssessorIds[] = (int) ($assessors[$assessorKey]['id'] ?? 0);
|
||||
}
|
||||
@ -470,16 +481,27 @@ try {
|
||||
$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);
|
||||
$followupDayInfo = cc_test_followup_day_info($order);
|
||||
$order['seguimiento_dia_info'] = $followupDayInfo;
|
||||
$order['dias_seguimiento'] = $followupDayInfo['day_number'] ?? null;
|
||||
$order['promo_final_habilitado'] = !empty($followupDayInfo['is_promo_final']);
|
||||
$order['promo_final_evidencia_cargada'] = trim((string) ($order['promo_final_evidencia_path'] ?? '')) !== '';
|
||||
$order['es_promo_final'] = $order['promo_final_habilitado'] && $order['promo_final_evidencia_cargada'];
|
||||
$order['promo_final_pendiente'] = $order['promo_final_habilitado'] && !$order['promo_final_evidencia_cargada'];
|
||||
$order['pendiente_logistica_destino'] = $storeKey === 'otra_tienda'
|
||||
? cc_test_pending_logistica_destination($order)
|
||||
: null;
|
||||
$order['pendiente_logistica'] = $order['pendiente_logistica_destino'] !== null;
|
||||
|
||||
$order['es_nuevo_hoy'] = $importDate ? $importDate->format('Y-m-d') === $todayStart->format('Y-m-d') : false;
|
||||
$order['es_pendiente_hoy'] = in_array($order['estado'], $openStates, true);
|
||||
$order['es_pendiente_hoy'] = !$order['es_promo_final'] && (in_array($order['estado'], $openStates, true) || $order['pendiente_logistica']);
|
||||
$order['es_cerrado'] = in_array($order['estado'], $closedStates, true);
|
||||
|
||||
if ($order['user_id'] !== null) {
|
||||
$userId = (int) $order['user_id'];
|
||||
if (isset($advisorTotalCounts[$userId])) {
|
||||
$advisorTotalCounts[$userId]++;
|
||||
if (in_array($order['estado'], $openStates, true)) {
|
||||
if ($order['es_pendiente_hoy']) {
|
||||
$advisorOpenCounts[$userId]++;
|
||||
}
|
||||
}
|
||||
@ -489,16 +511,19 @@ try {
|
||||
if ($order['es_nuevo_hoy']) {
|
||||
$stats['nuevos_hoy']++;
|
||||
}
|
||||
if ($order['es_promo_final']) {
|
||||
$stats['promo_final']++;
|
||||
}
|
||||
if ($order['es_pendiente_hoy']) {
|
||||
$stats['pendientes_hoy']++;
|
||||
}
|
||||
if (in_array($order['estado'], cc_test_confirmed_states(), true)) {
|
||||
$stats['confirmados']++;
|
||||
}
|
||||
if ($order['estado'] === 'SE ENVIO NUMERO DE CUENTA') {
|
||||
if ($order['estado'] === 'SE ENVIO NUMERO DE CUENTA' && !$order['es_promo_final']) {
|
||||
$stats['seguimiento']++;
|
||||
}
|
||||
if ($order['estado'] === 'OBSERVADO') {
|
||||
if ($order['estado'] === 'OBSERVADO' && !$order['es_promo_final']) {
|
||||
$stats['observados']++;
|
||||
}
|
||||
if ($order['es_cerrado']) {
|
||||
@ -507,8 +532,7 @@ try {
|
||||
}
|
||||
unset($order);
|
||||
|
||||
$orderedAssessorKeys = ['KARINA', 'ESTEFANYA'];
|
||||
$orderedAssessorKeys = array_values(array_filter($orderedAssessorKeys, static function (string $key) use ($assessors): bool {
|
||||
$orderedAssessorKeys = array_values(array_filter(cc_test_allowed_module_user_keys(), static function (string $key) use ($assessors): bool {
|
||||
return isset($assessors[$key]);
|
||||
}));
|
||||
|
||||
@ -527,19 +551,53 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
$assessorSummaryStyles = [
|
||||
'KARINA' => ['text' => 'text-primary', 'badge' => 'bg-primary-subtle text-primary-emphasis border'],
|
||||
'ESTEFANYA' => ['text' => 'text-success', 'badge' => 'bg-success-subtle text-success-emphasis border'],
|
||||
'CARMEN' => ['text' => 'text-warning', 'badge' => 'bg-warning-subtle text-warning-emphasis border'],
|
||||
];
|
||||
$assessorSummaryCards = [];
|
||||
$tieneCupos = false;
|
||||
foreach ($orderedAssessorKeys as $assessorKey) {
|
||||
if (!isset($assessors[$assessorKey])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$assessorId = (int) ($assessors[$assessorKey]['id'] ?? 0);
|
||||
if ($assessorId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pending = (int) ($advisorOpenCounts[$assessorId] ?? 0);
|
||||
$remaining = max(0, (int) $cupoObjetivoPorAsesora - $pending);
|
||||
if ($remaining > 0) {
|
||||
$tieneCupos = true;
|
||||
}
|
||||
|
||||
$styles = $assessorSummaryStyles[$assessorKey] ?? ['text' => 'text-secondary', 'badge' => 'bg-secondary-subtle text-secondary-emphasis border'];
|
||||
$assessorSummaryCards[] = [
|
||||
'label' => (string) ($assessors[$assessorKey]['label'] ?? $assessorKey),
|
||||
'pending' => $pending,
|
||||
'remaining' => $remaining,
|
||||
'text_class' => (string) ($styles['text'] ?? 'text-secondary'),
|
||||
'badge_class' => (string) ($styles['badge'] ?? 'bg-secondary-subtle text-secondary-emphasis border'),
|
||||
];
|
||||
}
|
||||
|
||||
$visibleOrders = array_values(array_filter($orders, static function (array $order) use ($view): bool {
|
||||
return match ($view) {
|
||||
'pendientes_hoy' => (bool) ($order['es_pendiente_hoy'] ?? false),
|
||||
'nuevos_hoy' => (bool) ($order['es_nuevo_hoy'] ?? false),
|
||||
'confirmados' => in_array(($order['estado'] ?? ''), cc_test_confirmed_states(), true),
|
||||
'seguimiento' => ($order['estado'] ?? '') === 'SE ENVIO NUMERO DE CUENTA',
|
||||
'observados' => ($order['estado'] ?? '') === 'OBSERVADO',
|
||||
'seguimiento' => ($order['estado'] ?? '') === 'SE ENVIO NUMERO DE CUENTA' && !($order['es_promo_final'] ?? false),
|
||||
'promo_final' => (bool) ($order['es_promo_final'] ?? false),
|
||||
'observados' => ($order['estado'] ?? '') === 'OBSERVADO' && !($order['es_promo_final'] ?? false),
|
||||
'cerrados' => (bool) ($order['es_cerrado'] ?? false),
|
||||
default => true,
|
||||
};
|
||||
}));
|
||||
|
||||
usort($visibleOrders, static function (array $a, array $b) use ($view): int {
|
||||
usort($visibleOrders, static function (array $a, array $b) use ($view, $storeKey): int {
|
||||
$aAgregado = !empty($a['is_agregado']);
|
||||
$bAgregado = !empty($b['is_agregado']);
|
||||
if ($aAgregado !== $bAgregado) {
|
||||
@ -549,11 +607,30 @@ try {
|
||||
$aImport = cc_test_import_time($a);
|
||||
$bImport = cc_test_import_time($b);
|
||||
|
||||
if ($aImport !== $bImport) {
|
||||
return $bImport <=> $aImport;
|
||||
if ($view === 'promo_final') {
|
||||
$aDays = (int) ($a['dias_seguimiento'] ?? 0);
|
||||
$bDays = (int) ($b['dias_seguimiento'] ?? 0);
|
||||
if ($aDays !== $bDays) {
|
||||
return $bDays <=> $aDays;
|
||||
}
|
||||
if ($aImport !== $bImport) {
|
||||
return $aImport <=> $bImport;
|
||||
}
|
||||
}
|
||||
|
||||
if ($view === "pendientes_hoy") {
|
||||
$aPendientePromoFinal = !empty($a['promo_final_pendiente']);
|
||||
$bPendientePromoFinal = !empty($b['promo_final_pendiente']);
|
||||
if ($aPendientePromoFinal !== $bPendientePromoFinal) {
|
||||
return $aPendientePromoFinal ? -1 : 1;
|
||||
}
|
||||
|
||||
$aPendienteLogistica = !empty($a['pendiente_logistica']);
|
||||
$bPendienteLogistica = !empty($b['pendiente_logistica']);
|
||||
if ($aPendienteLogistica !== $bPendienteLogistica) {
|
||||
return $aPendienteLogistica ? -1 : 1;
|
||||
}
|
||||
|
||||
$aDue = cc_test_parse_datetime($a["proxima_llamada_at"] ?? null);
|
||||
$bDue = cc_test_parse_datetime($b["proxima_llamada_at"] ?? null);
|
||||
if ($aDue && $bDue && $aDue != $bDue) {
|
||||
@ -565,6 +642,18 @@ try {
|
||||
if (!$aDue && $bDue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($storeKey === 'otra_tienda') {
|
||||
$aSourceRow = (int) ($a['source_row'] ?? 0);
|
||||
$bSourceRow = (int) ($b['source_row'] ?? 0);
|
||||
if ($aSourceRow > 0 && $bSourceRow > 0 && $aSourceRow !== $bSourceRow) {
|
||||
return $bSourceRow <=> $aSourceRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($aImport !== $bImport) {
|
||||
return $bImport <=> $aImport;
|
||||
}
|
||||
|
||||
return cc_test_order_time($b) <=> cc_test_order_time($a);
|
||||
@ -597,7 +686,7 @@ require_once 'layout_header.php';
|
||||
<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">Agregados: <?php echo (int) $agregadosCount; ?></span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2"><?php if ($storeKey === 'otra_tienda' && $tuaniLastProcessedRow !== null): ?>TUANI: último procesado <?php echo (int) $tuaniLastProcessedRow; ?> · próxima desde <?php echo (int) $startRow; ?><?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"><?php if ($storeKey === 'otra_tienda' && $tuaniLastProcessedRow !== null): ?>TUANI: distribución desde fila <?php echo (int) ($storeConfig['startRow'] ?? $startRow); ?> · último procesado <?php echo (int) $tuaniLastProcessedRow; ?><?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">Auto actualización: 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>
|
||||
</div>
|
||||
@ -662,7 +751,7 @@ require_once 'layout_header.php';
|
||||
<a href="?view=pendientes_hoy&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'pendientes_hoy' ? 'bg-dark text-white' : 'bg-white'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase opacity-75 mb-2">Pendientes por llamar</div>
|
||||
<div class="small text-uppercase opacity-75 mb-2">Bandeja principal</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['pendientes_hoy']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
@ -698,6 +787,16 @@ require_once 'layout_header.php';
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="?view=promo_final&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'promo_final' ? 'bg-danger-subtle border border-danger' : 'bg-white'; ?>">
|
||||
<div class="card-body">
|
||||
<div class="small text-uppercase text-muted mb-2">Promo Final</div>
|
||||
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['promo_final']; ?></div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="?view=observados&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'observados' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
|
||||
@ -733,16 +832,6 @@ require_once 'layout_header.php';
|
||||
<section class="mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<?php
|
||||
$karinaId = (int) ($assessors['KARINA']['id'] ?? 0);
|
||||
$estefId = (int) ($assessors['ESTEFANYA']['id'] ?? 0);
|
||||
$karinaPendientes = $karinaId > 0 ? (int) ($advisorOpenCounts[$karinaId] ?? 0) : 0;
|
||||
$estefPendientes = $estefId > 0 ? (int) ($advisorOpenCounts[$estefId] ?? 0) : 0;
|
||||
$karinaCupoRestante = max(0, (int) $cupoObjetivoPorAsesora - $karinaPendientes);
|
||||
$estefCupoRestante = max(0, (int) $cupoObjetivoPorAsesora - $estefPendientes);
|
||||
$tieneCupos = ($karinaCupoRestante > 0 || $estefCupoRestante > 0);
|
||||
?>
|
||||
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
|
||||
<div>
|
||||
<h2 class="h5 fw-bold mb-1">
|
||||
@ -752,27 +841,18 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 align-items-stretch justify-content-lg-end">
|
||||
<div class="border rounded-3 bg-light px-3 py-2" style="min-width: 240px;">
|
||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||
<div class="fw-bold text-primary">KARINA</div>
|
||||
<span class="badge bg-primary-subtle text-primary-emphasis border">Pendientes: <?php echo (int) $karinaPendientes; ?></span>
|
||||
<?php foreach ($assessorSummaryCards as $assessorCard): ?>
|
||||
<div class="border rounded-3 bg-light px-3 py-2" style="min-width: 240px;">
|
||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||
<div class="fw-bold <?php echo htmlspecialchars((string) $assessorCard['text_class']); ?>"><?php echo htmlspecialchars((string) $assessorCard['label']); ?></div>
|
||||
<span class="badge <?php echo htmlspecialchars((string) $assessorCard['badge_class']); ?>">Pendientes: <?php echo (int) $assessorCard['pending']; ?></span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
|
||||
<div class="h3 fw-bold mb-0"><?php echo (int) $assessorCard['remaining']; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
|
||||
<div class="h3 fw-bold mb-0"><?php echo (int) $karinaCupoRestante; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-3 bg-light px-3 py-2" style="min-width: 240px;">
|
||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||
<div class="fw-bold text-success">ESTEFANYA</div>
|
||||
<span class="badge bg-success-subtle text-success-emphasis border">Pendientes: <?php echo (int) $estefPendientes; ?></span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
|
||||
<div class="h3 fw-bold mb-0"><?php echo (int) $estefCupoRestante; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<form method="post" class="d-flex flex-column gap-2" style="min-width: 220px;">
|
||||
<input type="hidden" name="action" value="auto_assign_cupo">
|
||||
@ -791,7 +871,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. <strong>Promo Final</strong> se habilita desde el Día 4 y exige imagen de sustento para mover el pedido.</p>
|
||||
</div>
|
||||
<span class="badge bg-light text-dark border"><?php echo count($visibleOrders); ?> pedidos en esta bandeja</span>
|
||||
</div>
|
||||
@ -826,12 +906,48 @@ require_once 'layout_header.php';
|
||||
$distritosSeleccionados = $distritosPorProvinciaContraentrega[$provinciaSeleccionada] ?? [];
|
||||
$modalId = 'modalDriveTest' . $order['source_key'];
|
||||
$badgeClass = cc_test_badge_class($order['estado']);
|
||||
$followupDayInfo = $order['seguimiento_dia_info'] ?? null;
|
||||
$semaforoCuenta = cc_test_followup_semaforo($order);
|
||||
$historial = cc_test_fetch_historial(db(), $order['source_key']);
|
||||
$logisticaPendienteLabel = !empty($order['pendiente_logistica']) ? cc_test_pending_logistica_label($order) : null;
|
||||
$logisticaPendienteDetalle = match ($order['pendiente_logistica_destino'] ?? null) {
|
||||
'ruta' => 'Falta subirlo a Ruta Contraentrega.',
|
||||
'rotulado' => 'Falta subirlo a Pedidos Rotulados.',
|
||||
default => '',
|
||||
};
|
||||
$subirLogisticaButtonLabel = 'Subir pedido';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-muted';
|
||||
$subirLogisticaNoteHtml = 'Selecciona <strong>CONFIRMADO CONTRAENTREGA</strong> o <strong>CONFIRMADO ENVIO</strong> para enviarlo a logística.';
|
||||
if ($storeKey === 'otra_tienda') {
|
||||
if ($order['estado'] === 'CONFIRMADO CONTRAENTREGA') {
|
||||
if (!empty($order['ruta_contraentrega_pedido_id'])) {
|
||||
$subirLogisticaButtonLabel = 'Actualizar en ruta';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-success fw-semibold';
|
||||
$subirLogisticaNoteHtml = 'En Ruta Contraentrega #' . (int) $order['ruta_contraentrega_pedido_id'] . '. Si cambias algo, usa este botón para actualizarlo.';
|
||||
} else {
|
||||
$subirLogisticaButtonLabel = 'Subir a ruta';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-warning-emphasis';
|
||||
$subirLogisticaNoteHtml = '<strong>PENDIENTE A SUBIR</strong>: este pedido ya está en <strong>CONFIRMADO CONTRAENTREGA</strong>. Súbelo a Ruta Contraentrega con este botón.';
|
||||
}
|
||||
} elseif ($order['estado'] === 'CONFIRMADO ENVIO') {
|
||||
if (!empty($order['pedido_rotulado_pedido_id'])) {
|
||||
$subirLogisticaButtonLabel = 'Actualizar rotulado';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-success fw-semibold';
|
||||
$subirLogisticaNoteHtml = 'En Pedidos Rotulados #' . (int) $order['pedido_rotulado_pedido_id'] . '. Si cambias algo, usa este botón para actualizarlo.';
|
||||
} else {
|
||||
$subirLogisticaButtonLabel = 'Subir a rotulados';
|
||||
$subirLogisticaNoteClass = 'small mt-2 text-warning-emphasis';
|
||||
$subirLogisticaNoteHtml = '<strong>PENDIENTE A SUBIR</strong>: este pedido ya está en <strong>CONFIRMADO ENVIO</strong>. Súbelo a Pedidos Rotulados con este botón.';
|
||||
}
|
||||
}
|
||||
}
|
||||
ob_start();
|
||||
?>
|
||||
<tr class="cc-callcenter-row" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>" style="<?php echo htmlspecialchars(cc_test_row_background_style((string) ($order['estado'] ?? 'POR LLAMAR'))); ?>">
|
||||
<td>
|
||||
<?php if ($followupDayInfo !== null): ?>
|
||||
<div class="small text-uppercase fw-semibold <?php echo htmlspecialchars($followupDayInfo['text_class']); ?> mb-1"><?php echo htmlspecialchars($followupDayInfo['display_label']); ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="fw-bold fs-6"><?php echo htmlspecialchars(cc_test_order_label($order)); ?></div>
|
||||
<div class="small text-muted">ID Drive: <?php echo htmlspecialchars(cc_test_display_value($order['import_id'] ?? null, 'Sin ID')); ?></div>
|
||||
</td>
|
||||
@ -861,13 +977,25 @@ 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 (!empty($logisticaPendienteLabel)): ?>
|
||||
<span class="badge rounded-pill bg-warning-subtle text-warning-emphasis border"><?php echo htmlspecialchars($logisticaPendienteLabel); ?></span>
|
||||
<?php endif; ?>
|
||||
<?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 (!empty($logisticaPendienteDetalle)): ?>
|
||||
<div class="small text-warning-emphasis fw-semibold mb-1"><?php echo htmlspecialchars($logisticaPendienteDetalle); ?></div>
|
||||
<?php endif; ?>
|
||||
<?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; ?>
|
||||
<?php if ($followupDayInfo !== null): ?>
|
||||
<div class="small text-muted mb-1">Seguimiento: <?php echo htmlspecialchars($followupDayInfo['display_label']); ?> · desde <?php echo htmlspecialchars($followupDayInfo['started_at_label']); ?></div>
|
||||
<?php if (!empty($followupDayInfo['notice_text'])): ?>
|
||||
<div class="small <?php echo htmlspecialchars($followupDayInfo['row_notice_class']); ?> fw-semibold mb-1"><?php echo htmlspecialchars($followupDayInfo['notice_text']); ?></div>
|
||||
<?php endif; ?>
|
||||
<?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>
|
||||
@ -875,6 +1003,9 @@ require_once 'layout_header.php';
|
||||
<?php if (!empty($order['ruta_contraentrega_pedido_id'])): ?>
|
||||
<div class="small text-success">Subido a Ruta Contraentrega #<?php echo (int) $order['ruta_contraentrega_pedido_id']; ?><?php if (!empty($order['ruta_contraentrega_subido_at'])): ?> · <?php echo htmlspecialchars(cc_test_format_datetime($order['ruta_contraentrega_subido_at'] ?? null, '')); ?><?php endif; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['pedido_rotulado_pedido_id'])): ?>
|
||||
<div class="small text-primary">Subido a Pedidos Rotulados #<?php echo (int) $order['pedido_rotulado_pedido_id']; ?><?php if (!empty($order['pedido_rotulado_subido_at'])): ?> · <?php echo htmlspecialchars(cc_test_format_datetime($order['pedido_rotulado_subido_at'] ?? null, '')); ?><?php endif; ?></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>
|
||||
</td>
|
||||
@ -902,6 +1033,8 @@ require_once 'layout_header.php';
|
||||
$assignFormClass .= ' bg-primary-subtle border-primary';
|
||||
} elseif ($assignedAssessorKey === 'ESTEFANYA') {
|
||||
$assignFormClass .= ' bg-success-subtle border-success';
|
||||
} elseif ($assignedAssessorKey === 'CARMEN') {
|
||||
$assignFormClass .= ' bg-warning-subtle border-warning';
|
||||
} else {
|
||||
$assignFormClass .= ' bg-secondary-subtle border-secondary';
|
||||
}
|
||||
@ -929,6 +1062,8 @@ require_once 'layout_header.php';
|
||||
<span class="badge bg-primary-subtle text-primary-emphasis border">KARINA</span>
|
||||
<?php elseif ($assignedAssessorKey === 'ESTEFANYA'): ?>
|
||||
<span class="badge bg-success-subtle text-success-emphasis border">ESTEFANYA</span>
|
||||
<?php elseif ($assignedAssessorKey === 'CARMEN'): ?>
|
||||
<span class="badge bg-warning-subtle text-warning-emphasis border">CARMEN</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary-subtle text-secondary-emphasis border">Asignado</span>
|
||||
<?php endif; ?>
|
||||
@ -983,23 +1118,52 @@ require_once 'layout_header.php';
|
||||
<div class="modal-content">
|
||||
<div class="modal-header align-items-start gap-2">
|
||||
<div class="flex-grow-1 min-w-0">
|
||||
<div class="small text-primary fw-semibold mb-1"><?php echo htmlspecialchars(cc_test_order_label($order)); ?></div>
|
||||
<div class="d-flex flex-wrap align-items-center gap-2 mb-1">
|
||||
<div class="small text-primary fw-semibold mb-0"><?php echo htmlspecialchars(cc_test_order_label($order)); ?></div>
|
||||
<?php if ($followupDayInfo !== null): ?>
|
||||
<span class="badge rounded-pill <?php echo htmlspecialchars($followupDayInfo['badge_class']); ?>"><?php echo htmlspecialchars($followupDayInfo['display_label']); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<h3 class="modal-title h5 mb-0"><?php echo htmlspecialchars(cc_test_display_value($order['nombre'], 'Cliente sin nombre')); ?></h3>
|
||||
<?php if ($storeKey === 'otra_tienda'): ?>
|
||||
<button type="button" class="btn btn-primary btn-sm px-3" id="subir-ruta-<?php echo htmlspecialchars($order['source_key']); ?>" onclick="guardarGestion('<?php echo htmlspecialchars($order['source_key']); ?>', this, { subirRuta: true })"><?php echo !empty($order['ruta_contraentrega_pedido_id']) ? 'Actualizar en ruta' : 'Subir pedido'; ?></button>
|
||||
<button type="button" class="btn btn-primary btn-sm px-3" id="subir-logistica-<?php echo htmlspecialchars($order['source_key']); ?>" data-route-pedido-id="<?php echo (int) ($order['ruta_contraentrega_pedido_id'] ?? 0); ?>" data-rotulado-pedido-id="<?php echo (int) ($order['pedido_rotulado_pedido_id'] ?? 0); ?>" onclick="guardarGestion('<?php echo htmlspecialchars($order['source_key']); ?>', this, { subirLogistica: true })"><?php echo htmlspecialchars($subirLogisticaButtonLabel); ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="small text-muted mt-1"><?php echo htmlspecialchars(cc_test_display_value($order['producto'], 'Sin producto')); ?> · <?php echo htmlspecialchars(cc_test_display_value($order['celular'], 'Sin celular')); ?></div>
|
||||
<?php if ($storeKey === 'otra_tienda'): ?>
|
||||
<div class="small mt-2 <?php echo !empty($order['ruta_contraentrega_pedido_id']) ? 'text-success fw-semibold' : 'text-muted'; ?>">
|
||||
<?php if (!empty($order['ruta_contraentrega_pedido_id'])): ?>
|
||||
En Ruta Contraentrega #<?php echo (int) $order['ruta_contraentrega_pedido_id']; ?>. Si cambias algo, usa este botón para actualizarlo.
|
||||
<?php else: ?>
|
||||
Súbelo cuando quede en <strong>CONFIRMADO CONTRAENTREGA</strong>.
|
||||
<?php endif; ?>
|
||||
<?php if ($followupDayInfo !== null): ?>
|
||||
<div class="small mt-2 <?php echo htmlspecialchars(!empty($followupDayInfo['notice_text']) ? $followupDayInfo['text_class'] : 'text-muted'); ?><?php echo !empty($followupDayInfo['notice_text']) ? ' fw-semibold' : ''; ?>">
|
||||
<?php echo htmlspecialchars(!empty($followupDayInfo['notice_text']) ? $followupDayInfo['notice_text'] : ('Seguimiento iniciado el ' . $followupDayInfo['started_at_label'] . '.')); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php $promoFinalProofPath = trim((string) ($order['promo_final_evidencia_path'] ?? '')); ?>
|
||||
<?php $promoFinalMoveAvailable = !empty($order['promo_final_habilitado']) && in_array($order['estado'], cc_test_followup_tracking_states(), true); ?>
|
||||
<?php if ($promoFinalMoveAvailable || $promoFinalProofPath !== ''): ?>
|
||||
<div
|
||||
class="mt-2 js-promo-final-controls"
|
||||
id="promo-final-controls-<?php echo htmlspecialchars($order['source_key']); ?>"
|
||||
data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>"
|
||||
data-promo-final-candidate="<?php echo !empty($order['promo_final_habilitado']) ? '1' : '0'; ?>"
|
||||
data-has-existing="<?php echo $promoFinalProofPath !== '' ? '1' : '0'; ?>"
|
||||
>
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<button type="button" class="btn btn-outline-danger btn-sm px-3 <?php echo ($promoFinalMoveAvailable && $promoFinalProofPath === '') ? '' : 'd-none'; ?>" id="mover-promo-final-<?php echo htmlspecialchars($order['source_key']); ?>" onclick="guardarGestion('<?php echo htmlspecialchars($order['source_key']); ?>', this, { moverPromoFinal: true })">Mover a Promo Final</button>
|
||||
<?php if ($promoFinalProofPath !== ''): ?>
|
||||
<a href="<?php echo htmlspecialchars($promoFinalProofPath); ?>" class="btn btn-outline-success btn-sm" target="_blank" rel="noopener">Ver sustento Promo Final</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="small mt-2 <?php echo $promoFinalProofPath !== '' ? 'text-success fw-semibold' : 'text-danger-emphasis'; ?>" id="mover-promo-final-note-<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<?php if ($promoFinalProofPath !== ''): ?>
|
||||
Promo Final con sustento cargado<?php if (!empty($order['promo_final_evidencia_subido_at'])): ?> · <?php echo htmlspecialchars(cc_test_format_datetime($order['promo_final_evidencia_subido_at'] ?? null, '')); ?><?php endif; ?>.
|
||||
<?php else: ?>
|
||||
Debes subir una imagen de sustento y luego usar este botón para moverlo a Promo Final.
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($storeKey === 'otra_tienda'): ?>
|
||||
<div class="<?php echo htmlspecialchars($subirLogisticaNoteClass); ?>" id="subir-logistica-note-<?php echo htmlspecialchars($order['source_key']); ?>"><?php echo $subirLogisticaNoteHtml; ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<button type="button" class="btn-close mt-1" data-bs-dismiss="modal" aria-label="Cerrar"></button>
|
||||
</div>
|
||||
@ -1162,6 +1326,42 @@ require_once 'layout_header.php';
|
||||
<label for="nota-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">COLOCAR AQUI OBSERVACION DEL CLIENTE O (DEDICATORIA O GRABADO)</label>
|
||||
<textarea class="form-control" id="nota-<?php echo htmlspecialchars($order['source_key']); ?>" rows="3" placeholder="Ej.: Solicita dedicatoria para hija María, Ej.: Desea antes de las 4PM"><?php echo htmlspecialchars($order['nota_seguimiento']); ?></textarea>
|
||||
</div>
|
||||
<?php $numeroCuentaSedeIdSeleccionado = trim((string) ($order['numero_cuenta_sede_id'] ?? '')); ?>
|
||||
<?php $numeroCuentaDniSeleccionado = trim((string) ($order['numero_cuenta_dni'] ?? '')); ?>
|
||||
<?php $mostrarCamposNumeroCuenta = cc_test_requires_account_number_fields($order['estado']); ?>
|
||||
<div class="col-md-6 js-numero-cuenta-group <?php echo $mostrarCamposNumeroCuenta ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="numero_cuenta_sede_id-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">SEDE / ID</label>
|
||||
<input type="text" class="form-control" id="numero_cuenta_sede_id-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars($numeroCuentaSedeIdSeleccionado); ?>" placeholder="Ej.: SJL / 1542">
|
||||
<div class="form-text">Solo aparece en <strong>SE ENVIO NUMERO DE CUENTA</strong>.</div>
|
||||
</div>
|
||||
<div class="col-md-6 js-numero-cuenta-group <?php echo $mostrarCamposNumeroCuenta ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="numero_cuenta_dni-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">N° DNI</label>
|
||||
<input type="text" class="form-control" id="numero_cuenta_dni-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars($numeroCuentaDniSeleccionado); ?>" placeholder="Ej.: 76543210">
|
||||
</div>
|
||||
<?php $promoFinalProofPath = trim((string) ($order['promo_final_evidencia_path'] ?? '')); ?>
|
||||
<?php $canceladoProofPath = trim((string) ($order['cancelado_evidencia_path'] ?? '')); ?>
|
||||
<?php $mostrarPromoFinalProof = !empty($order['promo_final_habilitado']) || $promoFinalProofPath !== ''; ?>
|
||||
<?php $mostrarCanceladoProof = $order['estado'] === 'CANCELADO' || $canceladoProofPath !== ''; ?>
|
||||
<div class="col-12 js-promo-final-proof-group <?php echo $mostrarPromoFinalProof ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>" data-promo-final-candidate="<?php echo !empty($order['promo_final_habilitado']) ? '1' : '0'; ?>" data-has-existing="<?php echo $promoFinalProofPath !== '' ? '1' : '0'; ?>">
|
||||
<label for="promo_final_evidencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Imagen de sustento para Promo Final</label>
|
||||
<input type="file" class="form-control" id="promo_final_evidencia-<?php echo htmlspecialchars($order['source_key']); ?>" accept=".jpg,.jpeg,.png,.webp">
|
||||
<div class="form-text">Obligatoria al mover el pedido a <strong>Promo Final</strong> desde el Día 4. La supervisora podrá revisarla.</div>
|
||||
<?php if ($promoFinalProofPath !== ''): ?>
|
||||
<div class="small text-success mt-2">
|
||||
Sustento actual: <a href="<?php echo htmlspecialchars($promoFinalProofPath); ?>" target="_blank" rel="noopener">ver imagen</a><?php if (!empty($order['promo_final_evidencia_subido_at'])): ?> · <?php echo htmlspecialchars(cc_test_format_datetime($order['promo_final_evidencia_subido_at'] ?? null, '')); ?><?php endif; ?>.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="col-12 js-cancelado-proof-group <?php echo $mostrarCanceladoProof ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>" data-has-existing="<?php echo $canceladoProofPath !== '' ? '1' : '0'; ?>">
|
||||
<label for="cancelado_evidencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Imagen de sustento para Cancelado</label>
|
||||
<input type="file" class="form-control" id="cancelado_evidencia-<?php echo htmlspecialchars($order['source_key']); ?>" accept=".jpg,.jpeg,.png,.webp">
|
||||
<div class="form-text">Obligatoria cuando el estado sea <strong>Cancelado</strong>. Así evitas cancelaciones sin motivo.</div>
|
||||
<?php if ($canceladoProofPath !== ''): ?>
|
||||
<div class="small text-success mt-2">
|
||||
Sustento actual: <a href="<?php echo htmlspecialchars($canceladoProofPath); ?>" target="_blank" rel="noopener">ver imagen</a><?php if (!empty($order['cancelado_evidencia_subido_at'])): ?> · <?php echo htmlspecialchars(cc_test_format_datetime($order['cancelado_evidencia_subido_at'] ?? null, '')); ?><?php endif; ?>.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
@ -1176,8 +1376,10 @@ require_once 'layout_header.php';
|
||||
<label for="referencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Referencia</label>
|
||||
<textarea class="form-control" id="referencia-<?php echo htmlspecialchars($order['source_key']); ?>" rows="2"><?php echo htmlspecialchars((string) ($order['referencia'] ?? '')); ?></textarea>
|
||||
</div>
|
||||
<?php $agenciaSeleccionada = strtoupper(trim((string) ($order['agencia'] ?? ''))); ?>
|
||||
<?php $agenciaSeleccionada = cc_test_normalize_shipping_agency($order['agencia'] ?? '') ?? strtoupper(trim((string) ($order['agencia'] ?? ''))); ?>
|
||||
<?php $sedeAgenciaSeleccionada = trim((string) ($order['sede_agencia'] ?? '')); ?>
|
||||
<?php $montoAdelantadoSeleccionado = trim((string) ($order['monto_adelantado'] ?? '')); ?>
|
||||
<?php $mostrarCamposEnvio = cc_test_requires_shipping_details($order['estado']); ?>
|
||||
<div class="col-md-4">
|
||||
<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']); ?>">
|
||||
@ -1234,33 +1436,34 @@ require_once 'layout_header.php';
|
||||
<input type="text" class="form-control" id="coordenadas-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars((string) ($order['coordenadas'] ?? '')); ?>" inputmode="decimal" autocomplete="off" spellcheck="false" placeholder="-12.082029, -77.069024">
|
||||
<div class="form-text">Obligatorias para <strong>Subir pedido</strong> a Ruta Contraentrega.</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-4 js-envio-group <?php echo $mostrarCamposEnvio ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="agencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Agencia</label>
|
||||
<select class="form-select" id="agencia-<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<option value="">Seleccione agencia</option>
|
||||
<?php foreach (['SHALOM', 'OLVA COURIER', 'OTROS'] as $agenciaOption): ?>
|
||||
<?php foreach (cc_test_shipping_agency_options() as $agenciaOption): ?>
|
||||
<option value="<?php echo htmlspecialchars($agenciaOption); ?>" <?php echo $agenciaSeleccionada === $agenciaOption ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($agenciaOption); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php if ($agenciaSeleccionada !== '' && !in_array($agenciaSeleccionada, ['SHALOM', 'OLVA COURIER', 'OTROS'], true)): ?>
|
||||
<?php if ($agenciaSeleccionada !== '' && !in_array($agenciaSeleccionada, cc_test_shipping_agency_options(), true)): ?>
|
||||
<option value="<?php echo htmlspecialchars($agenciaSeleccionada); ?>" selected>
|
||||
<?php echo htmlspecialchars($agenciaSeleccionada); ?>
|
||||
</option>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="sede_agencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Sede</label>
|
||||
<input type="text" class="form-control" id="sede_agencia-<?php echo htmlspecialchars($order['source_key']); ?>" placeholder="Escriba la sede para envío por agencia" value="<?php echo htmlspecialchars($sedeAgenciaSeleccionada); ?>">
|
||||
<div class="col-md-4 js-envio-group <?php echo $mostrarCamposEnvio ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="sede_agencia-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Sede de envío</label>
|
||||
<input type="text" class="form-control" id="sede_agencia-<?php echo htmlspecialchars($order['source_key']); ?>" placeholder="Seleccione o escriba la sede de envío" value="<?php echo htmlspecialchars($sedeAgenciaSeleccionada); ?>" <?php echo ($agenciaSeleccionada === 'SHALOM' && !empty($sedesShalom)) ? 'list="cc-sedes-shalom-list"' : ''; ?>>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-2 js-envio-group <?php echo $mostrarCamposEnvio ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="dni-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">DNI</label>
|
||||
<input type="text" class="form-control" id="dni-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars((string) ($order['dni'] ?? '')); ?>">
|
||||
<div class="form-text">Obligatorio para <strong>CONFIRMADO ENVIO</strong>.</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="observaciones-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Observaciones del pedido</label>
|
||||
<textarea class="form-control" id="observaciones-<?php echo htmlspecialchars($order['source_key']); ?>" rows="2"><?php echo htmlspecialchars((string) ($order['observaciones'] ?? '')); ?></textarea>
|
||||
<div class="col-md-2 js-envio-group <?php echo $mostrarCamposEnvio ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="monto_adelantado-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">Monto de adelanto</label>
|
||||
<input type="text" class="form-control" id="monto_adelantado-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars($montoAdelantadoSeleccionado); ?>" inputmode="decimal" placeholder="0.00">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1302,7 +1505,7 @@ require_once 'layout_header.php';
|
||||
<div class="modal-footer d-flex justify-content-between flex-wrap gap-2">
|
||||
<div class="small text-muted">
|
||||
<?php if ($storeKey === 'otra_tienda'): ?>
|
||||
<strong>Guardar gestión</strong> solo guarda el historial. Para enviarlo a logística, usa <strong>Subir pedido</strong> junto al nombre del cliente.
|
||||
<strong>Guardar gestión</strong> solo guarda el historial. Para enviarlo a logística, usa el <strong>botón del encabezado</strong> junto al nombre del cliente.
|
||||
<?php else: ?>
|
||||
Los cambios se guardan en la base local del módulo de prueba.
|
||||
<?php endif; ?>
|
||||
@ -1355,6 +1558,13 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($sedesShalom)): ?>
|
||||
<datalist id="cc-sedes-shalom-list">
|
||||
<?php foreach ($sedesShalom as $sedeShalomOption): ?>
|
||||
<option value="<?php echo htmlspecialchars($sedeShalomOption); ?>"></option>
|
||||
<?php endforeach; ?>
|
||||
</datalist>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
@ -1479,14 +1689,107 @@ function renderProvinceOptions(sourceKey, preserveSelection = true) {
|
||||
renderDistrictOptions(sourceKey, preserveSelection);
|
||||
}
|
||||
|
||||
function updateShippingSedeList(sourceKey) {
|
||||
const agenciaSelect = document.getElementById('agencia-' + sourceKey);
|
||||
const sedeInput = document.getElementById('sede_agencia-' + sourceKey);
|
||||
const sedesListId = 'cc-sedes-shalom-list';
|
||||
|
||||
if (!agenciaSelect || !sedeInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (agenciaSelect.value === 'SHALOM' && document.getElementById(sedesListId)) {
|
||||
sedeInput.setAttribute('list', sedesListId);
|
||||
} else {
|
||||
sedeInput.removeAttribute('list');
|
||||
}
|
||||
}
|
||||
|
||||
function updateLogisticaButton(sourceKey) {
|
||||
const estado = document.getElementById('estado-' + sourceKey)?.value || '';
|
||||
const button = document.getElementById('subir-logistica-' + sourceKey);
|
||||
const note = document.getElementById('subir-logistica-note-' + sourceKey);
|
||||
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
const routeId = parseInt(button.dataset.routePedidoId || '0', 10);
|
||||
const rotuladoId = parseInt(button.dataset.rotuladoPedidoId || '0', 10);
|
||||
|
||||
if (estado === 'CONFIRMADO CONTRAENTREGA') {
|
||||
button.disabled = false;
|
||||
button.textContent = routeId > 0 ? 'Actualizar en ruta' : 'Subir a ruta';
|
||||
if (note) {
|
||||
note.className = 'small mt-2 ' + (routeId > 0 ? 'text-success fw-semibold' : 'text-warning-emphasis');
|
||||
note.innerHTML = routeId > 0
|
||||
? 'En Ruta Contraentrega #' + routeId + '. Si cambias algo, usa este botón para actualizarlo.'
|
||||
: '<strong>PENDIENTE A SUBIR</strong>: este pedido ya está en <strong>CONFIRMADO CONTRAENTREGA</strong>. Súbelo a Ruta Contraentrega con este botón.';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (estado === 'CONFIRMADO ENVIO') {
|
||||
button.disabled = false;
|
||||
button.textContent = rotuladoId > 0 ? 'Actualizar rotulado' : 'Subir a rotulados';
|
||||
if (note) {
|
||||
note.className = 'small mt-2 ' + (rotuladoId > 0 ? 'text-success fw-semibold' : 'text-warning-emphasis');
|
||||
note.innerHTML = rotuladoId > 0
|
||||
? 'En Pedidos Rotulados #' + rotuladoId + '. Si cambias algo, usa este botón para actualizarlo.'
|
||||
: '<strong>PENDIENTE A SUBIR</strong>: este pedido ya está en <strong>CONFIRMADO ENVIO</strong>. Súbelo a Pedidos Rotulados con este botón.';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Subir pedido';
|
||||
if (note) {
|
||||
note.className = 'small mt-2 text-muted';
|
||||
note.textContent = 'Selecciona CONFIRMADO CONTRAENTREGA o CONFIRMADO ENVIO para enviarlo a logística.';
|
||||
}
|
||||
}
|
||||
|
||||
function updatePromoFinalControls(sourceKey) {
|
||||
const estado = document.getElementById('estado-' + sourceKey)?.value || '';
|
||||
const followupStates = ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO', 'SE ENVIO NUMERO DE CUENTA'];
|
||||
|
||||
document.querySelectorAll('.js-promo-final-controls[data-source-key="' + sourceKey + '"]').forEach(block => {
|
||||
const isCandidate = block.dataset.promoFinalCandidate === '1';
|
||||
const hasExisting = block.dataset.hasExisting === '1';
|
||||
const canUsePromoFinal = isCandidate && followupStates.includes(estado);
|
||||
block.classList.toggle('d-none', !hasExisting && !canUsePromoFinal);
|
||||
|
||||
const moveButton = block.querySelector('button[id^="mover-promo-final-"]');
|
||||
if (moveButton) {
|
||||
moveButton.classList.toggle('d-none', !canUsePromoFinal || hasExisting);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.js-promo-final-proof-group[data-source-key="' + sourceKey + '"]').forEach(group => {
|
||||
const isCandidate = group.dataset.promoFinalCandidate === '1';
|
||||
const hasExisting = group.dataset.hasExisting === '1';
|
||||
const canUsePromoFinal = isCandidate && followupStates.includes(estado);
|
||||
group.classList.toggle('d-none', !hasExisting && !canUsePromoFinal);
|
||||
});
|
||||
}
|
||||
|
||||
function updateCanceladoEvidenceControls(sourceKey) {
|
||||
const estado = document.getElementById('estado-' + sourceKey)?.value || '';
|
||||
document.querySelectorAll('.js-cancelado-proof-group[data-source-key="' + sourceKey + '"]').forEach(group => {
|
||||
const hasExisting = group.dataset.hasExisting === '1';
|
||||
group.classList.toggle('d-none', !hasExisting && estado !== 'CANCELADO');
|
||||
});
|
||||
}
|
||||
|
||||
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 routeButton = document.getElementById('subir-ruta-' + sourceKey);
|
||||
const needsDeliveryDate = estado === 'CONFIRMADO CONTRAENTREGA';
|
||||
const needsShippingDetails = estado === 'CONFIRMADO ENVIO';
|
||||
const needsAccountNumberDetails = estado === 'SE ENVIO NUMERO DE CUENTA';
|
||||
const needsNextCall = ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'].includes(estado);
|
||||
|
||||
if (nextCallGroup) {
|
||||
@ -1495,15 +1798,23 @@ function toggleAgendaFields(sourceKey) {
|
||||
if (deliveryGroup) {
|
||||
deliveryGroup.classList.toggle('d-none', !needsDeliveryDate);
|
||||
}
|
||||
document.querySelectorAll('.js-envio-group[data-source-key="' + sourceKey + '"]').forEach(group => {
|
||||
group.classList.toggle('d-none', !needsShippingDetails);
|
||||
});
|
||||
document.querySelectorAll('.js-numero-cuenta-group[data-source-key="' + sourceKey + '"]').forEach(group => {
|
||||
group.classList.toggle('d-none', !needsAccountNumberDetails);
|
||||
});
|
||||
if (!needsNextCall && nextCallInput) {
|
||||
nextCallInput.value = '';
|
||||
}
|
||||
if (!needsDeliveryDate && deliveryInput) {
|
||||
deliveryInput.value = '';
|
||||
}
|
||||
if (routeButton) {
|
||||
routeButton.disabled = !needsDeliveryDate;
|
||||
}
|
||||
|
||||
updateShippingSedeList(sourceKey);
|
||||
updateLogisticaButton(sourceKey);
|
||||
updatePromoFinalControls(sourceKey);
|
||||
updateCanceladoEvidenceControls(sourceKey);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@ -1511,6 +1822,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
toggleAgendaFields(select.id.replace('estado-', ''));
|
||||
});
|
||||
|
||||
document.querySelectorAll('select[id^="agencia-"]').forEach(select => {
|
||||
const sourceKey = select.id.replace('agencia-', '');
|
||||
updateShippingSedeList(sourceKey);
|
||||
select.addEventListener('change', () => {
|
||||
updateShippingSedeList(sourceKey);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('select[id^="sede-"]').forEach(select => {
|
||||
const sourceKey = select.id.replace('sede-', '');
|
||||
renderProvinceOptions(sourceKey, true);
|
||||
@ -1596,10 +1915,30 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
function guardarGestion(sourceKey, trigger, options = {}) {
|
||||
const subirRuta = options.subirRuta === true;
|
||||
const body = new URLSearchParams({
|
||||
const subirLogistica = options.subirLogistica === true || options.subirRuta === true;
|
||||
const moverPromoFinal = options.moverPromoFinal === true;
|
||||
const estado = document.getElementById('estado-' + sourceKey)?.value || 'POR LLAMAR';
|
||||
const promoProofInput = document.getElementById('promo_final_evidencia-' + sourceKey);
|
||||
const cancelProofInput = document.getElementById('cancelado_evidencia-' + sourceKey);
|
||||
const promoProofGroup = document.querySelector('.js-promo-final-proof-group[data-source-key="' + sourceKey + '"]');
|
||||
const cancelProofGroup = document.querySelector('.js-cancelado-proof-group[data-source-key="' + sourceKey + '"]');
|
||||
const hasExistingPromoProof = promoProofGroup?.dataset.hasExisting === '1';
|
||||
const hasExistingCancelProof = cancelProofGroup?.dataset.hasExisting === '1';
|
||||
|
||||
if (moverPromoFinal && !hasExistingPromoProof && (!promoProofInput?.files || promoProofInput.files.length === 0)) {
|
||||
alert('Debes subir una imagen de sustento antes de mover el pedido a Promo Final.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (estado === 'CANCELADO' && !hasExistingCancelProof && (!cancelProofInput?.files || cancelProofInput.files.length === 0)) {
|
||||
alert('Debes subir una imagen de sustento para guardar el pedido en estado Cancelado.');
|
||||
return;
|
||||
}
|
||||
|
||||
const body = new FormData();
|
||||
const fields = {
|
||||
source_key: sourceKey,
|
||||
estado: document.getElementById('estado-' + sourceKey)?.value || 'POR LLAMAR',
|
||||
estado: estado,
|
||||
proxima_llamada_at: document.getElementById('proxima-' + sourceKey)?.value || '',
|
||||
fecha_entrega_programada: document.getElementById('fecha-entrega-' + sourceKey)?.value || '',
|
||||
nota_seguimiento: document.getElementById('nota-' + sourceKey)?.value || '',
|
||||
@ -1612,6 +1951,9 @@ function guardarGestion(sourceKey, trigger, options = {}) {
|
||||
distrito: document.getElementById('distrito-' + sourceKey)?.value || '',
|
||||
coordenadas: document.getElementById('coordenadas-' + sourceKey)?.value || '',
|
||||
dni: document.getElementById('dni-' + sourceKey)?.value || '',
|
||||
numero_cuenta_sede_id: document.getElementById('numero_cuenta_sede_id-' + sourceKey)?.value || '',
|
||||
numero_cuenta_dni: document.getElementById('numero_cuenta_dni-' + sourceKey)?.value || '',
|
||||
monto_adelantado: document.getElementById('monto_adelantado-' + sourceKey)?.value || '',
|
||||
observaciones: document.getElementById('observaciones-' + sourceKey)?.value || '',
|
||||
producto: document.getElementById('producto-' + sourceKey)?.value || '',
|
||||
cantidad: document.getElementById('cantidad-' + sourceKey)?.value || '',
|
||||
@ -1622,25 +1964,37 @@ function guardarGestion(sourceKey, trigger, options = {}) {
|
||||
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 || '',
|
||||
subir_a_ruta: subirRuta ? '1' : '0'
|
||||
subir_a_logistica: subirLogistica ? '1' : '0',
|
||||
mover_a_promo_final: moverPromoFinal ? '1' : '0'
|
||||
};
|
||||
|
||||
Object.entries(fields).forEach(([key, value]) => {
|
||||
body.append(key, value);
|
||||
});
|
||||
|
||||
if (moverPromoFinal && promoProofInput?.files?.[0]) {
|
||||
body.append('promo_final_evidencia', promoProofInput.files[0]);
|
||||
}
|
||||
|
||||
if (estado === 'CANCELADO' && cancelProofInput?.files?.[0]) {
|
||||
body.append('cancelado_evidencia', cancelProofInput.files[0]);
|
||||
}
|
||||
|
||||
if (trigger) {
|
||||
trigger.disabled = true;
|
||||
}
|
||||
|
||||
fetch('update_callcenter_test_tracking.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
body: body.toString()
|
||||
body: body
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
throw new Error(data.message || 'No se pudo guardar la gestión');
|
||||
}
|
||||
if (subirRuta) {
|
||||
alert(data.message || 'Pedido subido a Ruta Contraentrega correctamente.');
|
||||
if (subirLogistica || moverPromoFinal) {
|
||||
alert(data.message || 'Gestión actualizada correctamente.');
|
||||
}
|
||||
window.location.reload();
|
||||
})
|
||||
|
||||
@ -43,6 +43,7 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
|
||||
`producto` VARCHAR(255) NULL,
|
||||
`cantidad` VARCHAR(50) NULL,
|
||||
`precio` VARCHAR(80) NULL,
|
||||
`monto_adelantado` DECIMAL(10,2) NULL,
|
||||
`confirmacion_producto` VARCHAR(255) NULL,
|
||||
`confirmacion_cantidad` VARCHAR(50) NULL,
|
||||
`confirmacion_precio` VARCHAR(80) NULL,
|
||||
@ -52,9 +53,20 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
|
||||
`proxima_llamada_at` DATETIME NULL,
|
||||
`fecha_entrega_programada` DATE NULL,
|
||||
`numero_cuenta_enviado_at` DATETIME NULL,
|
||||
`numero_cuenta_sede_id` VARCHAR(120) NULL,
|
||||
`numero_cuenta_dni` VARCHAR(40) NULL,
|
||||
`promo_final_evidencia_path` VARCHAR(255) NULL,
|
||||
`promo_final_evidencia_subido_at` DATETIME NULL,
|
||||
`promo_final_evidencia_subido_por` INT NULL,
|
||||
`cancelado_evidencia_path` VARCHAR(255) NULL,
|
||||
`cancelado_evidencia_subido_at` DATETIME NULL,
|
||||
`cancelado_evidencia_subido_por` INT NULL,
|
||||
`ruta_contraentrega_pedido_id` INT NULL,
|
||||
`ruta_contraentrega_subido_at` DATETIME NULL,
|
||||
`ruta_contraentrega_subido_por` INT NULL,
|
||||
`pedido_rotulado_pedido_id` INT NULL,
|
||||
`pedido_rotulado_subido_at` DATETIME NULL,
|
||||
`pedido_rotulado_subido_por` INT 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,
|
||||
@ -77,6 +89,7 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'producto', 'VARCHAR(255) NULL AFTER `coordenadas`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'cantidad', 'VARCHAR(50) NULL AFTER `producto`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'precio', 'VARCHAR(80) NULL AFTER `cantidad`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'monto_adelantado', 'DECIMAL(10,2) NULL AFTER `precio`');
|
||||
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`');
|
||||
@ -86,16 +99,36 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
|
||||
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`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ruta_contraentrega_pedido_id', 'INT NULL AFTER `numero_cuenta_enviado_at`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'numero_cuenta_sede_id', 'VARCHAR(120) NULL AFTER `numero_cuenta_enviado_at`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'numero_cuenta_dni', 'VARCHAR(40) NULL AFTER `numero_cuenta_sede_id`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'promo_final_evidencia_path', 'VARCHAR(255) NULL AFTER `numero_cuenta_dni`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'promo_final_evidencia_subido_at', 'DATETIME NULL AFTER `promo_final_evidencia_path`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'promo_final_evidencia_subido_por', 'INT NULL AFTER `promo_final_evidencia_subido_at`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'cancelado_evidencia_path', 'VARCHAR(255) NULL AFTER `promo_final_evidencia_subido_por`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'cancelado_evidencia_subido_at', 'DATETIME NULL AFTER `cancelado_evidencia_path`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'cancelado_evidencia_subido_por', 'INT NULL AFTER `cancelado_evidencia_subido_at`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ruta_contraentrega_pedido_id', 'INT NULL AFTER `cancelado_evidencia_subido_por`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ruta_contraentrega_subido_at', 'DATETIME NULL AFTER `ruta_contraentrega_pedido_id`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ruta_contraentrega_subido_por', 'INT NULL AFTER `ruta_contraentrega_subido_at`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'pedido_rotulado_pedido_id', 'INT NULL AFTER `ruta_contraentrega_subido_por`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'pedido_rotulado_subido_at', 'DATETIME NULL AFTER `pedido_rotulado_pedido_id`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'pedido_rotulado_subido_por', 'INT NULL AFTER `pedido_rotulado_subido_at`');
|
||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ultima_gestion_at', 'DATETIME NULL AFTER `ruta_contraentrega_subido_por`');
|
||||
|
||||
$checked = true;
|
||||
}
|
||||
|
||||
function cc_test_fetch_assessors(PDO $pdo, array $allowedNames = ['KARINA', 'ESTEFANYA']): array
|
||||
function cc_test_default_assessor_keys(): array
|
||||
{
|
||||
return ['KARINA', 'ESTEFANYA', 'CARMEN'];
|
||||
}
|
||||
|
||||
function cc_test_fetch_assessors(PDO $pdo, array $allowedNames = []): array
|
||||
{
|
||||
if (empty($allowedNames)) {
|
||||
$allowedNames = cc_test_default_assessor_keys();
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id, username, nombre_asesor FROM users WHERE role = 'Asesor'");
|
||||
$stmt->execute();
|
||||
|
||||
@ -131,7 +164,7 @@ function cc_test_normalize_user_key(?string $value): string
|
||||
|
||||
function cc_test_allowed_module_user_keys(): array
|
||||
{
|
||||
return ['KARINA', 'ESTEFANYA'];
|
||||
return cc_test_default_assessor_keys();
|
||||
}
|
||||
|
||||
function cc_test_is_allowed_module_user(?string $role = null, ?string $username = null, ?string $nombreAsesor = null): bool
|
||||
@ -266,6 +299,45 @@ function cc_test_requires_delivery_date(string $estado): bool
|
||||
return cc_test_normalize_state($estado) === 'CONFIRMADO CONTRAENTREGA';
|
||||
}
|
||||
|
||||
function cc_test_requires_shipping_details(string $estado): bool
|
||||
{
|
||||
return cc_test_normalize_state($estado) === 'CONFIRMADO ENVIO';
|
||||
}
|
||||
|
||||
function cc_test_requires_account_number_fields(string $estado): bool
|
||||
{
|
||||
return cc_test_normalize_state($estado) === 'SE ENVIO NUMERO DE CUENTA';
|
||||
}
|
||||
|
||||
function cc_test_pending_logistica_destination(array $order): ?string
|
||||
{
|
||||
$estado = cc_test_normalize_state((string) ($order['estado'] ?? ''));
|
||||
|
||||
if ($estado === 'CONFIRMADO CONTRAENTREGA') {
|
||||
return !empty($order['ruta_contraentrega_pedido_id']) && (int) $order['ruta_contraentrega_pedido_id'] > 0
|
||||
? null
|
||||
: 'ruta';
|
||||
}
|
||||
|
||||
if ($estado === 'CONFIRMADO ENVIO') {
|
||||
return !empty($order['pedido_rotulado_pedido_id']) && (int) $order['pedido_rotulado_pedido_id'] > 0
|
||||
? null
|
||||
: 'rotulado';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function cc_test_has_pending_logistica_upload(array $order): bool
|
||||
{
|
||||
return cc_test_pending_logistica_destination($order) !== null;
|
||||
}
|
||||
|
||||
function cc_test_pending_logistica_label(array $order): ?string
|
||||
{
|
||||
return cc_test_has_pending_logistica_upload($order) ? 'PENDIENTE A SUBIR' : null;
|
||||
}
|
||||
|
||||
function cc_test_account_followup_semaforo(?string $value): ?array
|
||||
{
|
||||
$date = cc_test_parse_datetime($value);
|
||||
@ -308,6 +380,119 @@ function cc_test_account_followup_semaforo(?string $value): ?array
|
||||
];
|
||||
}
|
||||
|
||||
function cc_test_followup_tracking_states(): array
|
||||
{
|
||||
return ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO', 'SE ENVIO NUMERO DE CUENTA'];
|
||||
}
|
||||
|
||||
function cc_test_followup_parse_datetime(?string $value): ?DateTimeImmutable
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$formats = [
|
||||
'Y-m-d\TH:i:sP',
|
||||
'Y-m-d\TH:i:s',
|
||||
'Y-m-d\TH:i',
|
||||
'Y-m-d H:i:s',
|
||||
'Y-m-d H:i',
|
||||
DateTimeInterface::ATOM,
|
||||
'Y-m-d',
|
||||
'd/m/Y H:i:s',
|
||||
'd/m/Y H:i',
|
||||
'd/m/Y',
|
||||
];
|
||||
|
||||
foreach ($formats as $format) {
|
||||
try {
|
||||
$date = DateTimeImmutable::createFromFormat($format, $value);
|
||||
if ($date instanceof DateTimeImmutable) {
|
||||
return $date;
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
// Intentar el siguiente formato.
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return new DateTimeImmutable($value);
|
||||
} catch (Throwable $exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function cc_test_followup_started_at(array $order): ?DateTimeImmutable
|
||||
{
|
||||
foreach (['drive_imported_at', 'first_seen_at', 'import_id'] as $field) {
|
||||
$date = cc_test_followup_parse_datetime($order[$field] ?? null);
|
||||
if ($date instanceof DateTimeImmutable) {
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function cc_test_followup_day_info(array $order): ?array
|
||||
{
|
||||
$estado = cc_test_normalize_state((string) ($order['estado'] ?? ''));
|
||||
if (!in_array($estado, cc_test_followup_tracking_states(), true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$startedAt = cc_test_followup_started_at($order);
|
||||
if (!$startedAt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$today = new DateTimeImmutable('today', $startedAt->getTimezone());
|
||||
$startedDay = $startedAt->setTime(0, 0, 0);
|
||||
$daysElapsed = (int) $startedDay->diff($today)->format('%a');
|
||||
if ($startedDay > $today) {
|
||||
$daysElapsed = 0;
|
||||
}
|
||||
|
||||
$dayNumber = $daysElapsed + 1;
|
||||
$badgeClass = 'bg-primary-subtle text-primary-emphasis border';
|
||||
$textClass = 'text-primary-emphasis';
|
||||
$description = 'Seguimiento dentro de la ventana óptima.';
|
||||
$noticeText = '';
|
||||
$rowNoticeClass = '';
|
||||
|
||||
if ($dayNumber === 2) {
|
||||
$badgeClass = 'bg-info-subtle text-info-emphasis border';
|
||||
$textClass = 'text-info-emphasis';
|
||||
$description = 'Aún está dentro de los 3 días de seguimiento.';
|
||||
} elseif ($dayNumber === 3) {
|
||||
$badgeClass = 'bg-warning-subtle text-warning-emphasis border';
|
||||
$textClass = 'text-warning-emphasis';
|
||||
$description = 'Último día de seguimiento antes de Promo Final.';
|
||||
$noticeText = 'Último día de seguimiento. Mañana debe pasar a Promo Final con imagen de sustento.';
|
||||
$rowNoticeClass = 'text-warning-emphasis';
|
||||
} elseif ($dayNumber >= 4) {
|
||||
$badgeClass = 'bg-danger-subtle text-danger-emphasis border';
|
||||
$textClass = 'text-danger-emphasis';
|
||||
$description = 'Ya debe pasar a Promo Final.';
|
||||
$noticeText = 'Este pedido lleva ' . $dayNumber . ' días y debe moverse a Promo Final con imagen de sustento.';
|
||||
$rowNoticeClass = 'text-danger-emphasis';
|
||||
}
|
||||
|
||||
return [
|
||||
'day_number' => $dayNumber,
|
||||
'display_label' => $dayNumber >= 4 ? 'Día 4+' : 'Día ' . $dayNumber,
|
||||
'compact_label' => $dayNumber >= 4 ? '4+' : (string) $dayNumber,
|
||||
'badge_class' => $badgeClass,
|
||||
'text_class' => $textClass,
|
||||
'description' => $description,
|
||||
'notice_text' => $noticeText,
|
||||
'row_notice_class' => $rowNoticeClass,
|
||||
'started_at_label' => $startedAt->format('d/m/Y'),
|
||||
'is_promo_final' => $dayNumber >= 4,
|
||||
];
|
||||
}
|
||||
|
||||
function cc_test_state_label(string $estado): string
|
||||
{
|
||||
return match (cc_test_normalize_state($estado)) {
|
||||
@ -383,6 +568,37 @@ function cc_test_parse_amount(?string $value): ?float
|
||||
return round((float) $value, 2);
|
||||
}
|
||||
|
||||
function cc_test_shipping_agency_options(): array
|
||||
{
|
||||
return ['SHALOM', 'OLVA', 'OTROS'];
|
||||
}
|
||||
|
||||
function cc_test_normalize_shipping_agency(?string $value): ?string
|
||||
{
|
||||
$value = strtoupper(trim((string) $value));
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($value) {
|
||||
'OLVA COURIER', 'OLVA' => 'OLVA',
|
||||
'SHALOM' => 'SHALOM',
|
||||
'OTRO', 'OTROS', 'OTRAS' => 'OTROS',
|
||||
default => $value,
|
||||
};
|
||||
}
|
||||
|
||||
function cc_test_fetch_shalom_sedes(PDO $pdo): array
|
||||
{
|
||||
if (!cc_test_table_exists($pdo, 'sedes_shalom')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$stmt = $pdo->query('SELECT nombre_sede FROM sedes_shalom ORDER BY nombre_sede ASC');
|
||||
$sedes = $stmt ? $stmt->fetchAll(PDO::FETCH_COLUMN) : [];
|
||||
|
||||
return is_array($sedes) ? array_values(array_filter(array_map('strval', $sedes), static fn ($value) => trim($value) !== '')) : [];
|
||||
}
|
||||
|
||||
function cc_test_row_background_style(string $estado): string
|
||||
{
|
||||
|
||||
@ -19,13 +19,15 @@ function drive_test_available_stores(): array
|
||||
'spreadsheet_id' => '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c',
|
||||
'sheet_gid' => null,
|
||||
'startRow' => 5552,
|
||||
'enforce_db_start_row' => false,
|
||||
],
|
||||
// Segunda tienda (nuevo link que pasaste).
|
||||
'otra_tienda' => [
|
||||
'label' => 'TUANI',
|
||||
'spreadsheet_id' => '1QYKeBJIIqYm6yW6Ka-5gKdxhUHwXOAc0No7RjnimxTw',
|
||||
'sheet_gid' => 1523126328,
|
||||
'startRow' => 6710,
|
||||
'startRow' => 6804,
|
||||
'enforce_db_start_row' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
@ -146,16 +148,17 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $
|
||||
if ($startIndex >= $totalDataRows) {
|
||||
$dataRows = [];
|
||||
} elseif ($startIndex > 0) {
|
||||
$dataRows = array_slice($dataRows, $startIndex);
|
||||
$dataRows = array_slice($dataRows, $startIndex, null, true);
|
||||
}
|
||||
|
||||
$previewRows = $limit > 0
|
||||
? array_reverse(array_slice($dataRows, -$limit))
|
||||
: array_reverse($dataRows);
|
||||
? array_slice($dataRows, -$limit, null, true)
|
||||
: $dataRows;
|
||||
$previewRows = array_reverse($previewRows, true);
|
||||
|
||||
$orders = [];
|
||||
|
||||
foreach ($previewRows as $row) {
|
||||
foreach ($previewRows as $rowIndex => $row) {
|
||||
$codigo = trim((string) ($row[0] ?? ''));
|
||||
$importId = drive_test_get_cell($row, $headerIndexes, ['ID']);
|
||||
$nombre = drive_test_get_cell($row, $headerIndexes, ['NOMBRE']);
|
||||
@ -173,6 +176,7 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $
|
||||
$direccion = drive_test_get_cell($row, $headerIndexes, ['DIRECION', 'DIRECCION']);
|
||||
$referencia = drive_test_get_cell($row, $headerIndexes, ['REFERENCIA']);
|
||||
$distrito = drive_test_get_cell($row, $headerIndexes, ['DISTRITO']);
|
||||
$sourceRow = (int) $rowIndex + 2;
|
||||
|
||||
// Importante: para 'flower' mantenemos EXACTAMENTE el algoritmo anterior
|
||||
// para no romper el historial/tracking ya existente.
|
||||
@ -185,8 +189,10 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $
|
||||
|
||||
$orders[] = [
|
||||
'source_key' => $sourceKey,
|
||||
'source_row' => $sourceRow,
|
||||
'codigo' => $codigo,
|
||||
'import_id' => $importId,
|
||||
'drive_imported_at' => drive_test_parse_datetime_mysql($importId),
|
||||
'nombre' => $nombre,
|
||||
'direccion' => $direccion,
|
||||
'referencia' => $referencia,
|
||||
@ -223,7 +229,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, user_id, direccion, referencia, agencia, sede_agencia, sede, ciudad, distrito, dni, observaciones, coordenadas, producto, cantidad, precio, 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, ruta_contraentrega_pedido_id, ruta_contraentrega_subido_at, ruta_contraentrega_subido_por, 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, numero_cuenta_sede_id, numero_cuenta_dni, observaciones, coordenadas, producto, cantidad, precio, monto_adelantado, 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, promo_final_evidencia_path, promo_final_evidencia_subido_at, promo_final_evidencia_subido_por, cancelado_evidencia_path, cancelado_evidencia_subido_at, cancelado_evidencia_subido_por, ruta_contraentrega_pedido_id, ruta_contraentrega_subido_at, ruta_contraentrega_subido_por, pedido_rotulado_pedido_id, pedido_rotulado_subido_at, pedido_rotulado_subido_por, ultima_gestion_at, updated_at FROM callcenter_test_tracking WHERE source_key IN ($placeholders)");
|
||||
$stmt->execute($sourceKeys);
|
||||
|
||||
$tracking = [];
|
||||
@ -236,7 +242,7 @@ function drive_test_fetch_tracking(PDO $pdo, array $sourceKeys): array
|
||||
|
||||
function drive_test_merge_tracking(array $orders, array $tracking): array
|
||||
{
|
||||
$editableFields = ['direccion', 'referencia', 'agencia', 'sede_agencia', 'sede', 'ciudad', 'distrito', 'dni', 'observaciones', 'coordenadas', 'producto', 'cantidad', 'precio', 'confirmacion_producto', 'confirmacion_cantidad', 'confirmacion_precio', 'confirmacion_producto_extra', 'confirmacion_cantidad_extra', 'confirmacion_precio_extra'];
|
||||
$editableFields = ['direccion', 'referencia', 'agencia', 'sede_agencia', 'sede', 'ciudad', 'distrito', 'dni', 'numero_cuenta_sede_id', 'numero_cuenta_dni', 'observaciones', 'coordenadas', 'producto', 'cantidad', 'precio', 'monto_adelantado', 'confirmacion_producto', 'confirmacion_cantidad', 'confirmacion_precio', 'confirmacion_producto_extra', 'confirmacion_cantidad_extra', 'confirmacion_precio_extra'];
|
||||
|
||||
foreach ($orders as &$order) {
|
||||
$current = $tracking[$order['source_key']] ?? null;
|
||||
@ -248,9 +254,18 @@ function drive_test_merge_tracking(array $orders, array $tracking): array
|
||||
$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['promo_final_evidencia_path'] = $current['promo_final_evidencia_path'] ?? null;
|
||||
$order['promo_final_evidencia_subido_at'] = $current['promo_final_evidencia_subido_at'] ?? null;
|
||||
$order['promo_final_evidencia_subido_por'] = isset($current['promo_final_evidencia_subido_por']) && (int) $current['promo_final_evidencia_subido_por'] > 0 ? (int) $current['promo_final_evidencia_subido_por'] : null;
|
||||
$order['cancelado_evidencia_path'] = $current['cancelado_evidencia_path'] ?? null;
|
||||
$order['cancelado_evidencia_subido_at'] = $current['cancelado_evidencia_subido_at'] ?? null;
|
||||
$order['cancelado_evidencia_subido_por'] = isset($current['cancelado_evidencia_subido_por']) && (int) $current['cancelado_evidencia_subido_por'] > 0 ? (int) $current['cancelado_evidencia_subido_por'] : null;
|
||||
$order['ruta_contraentrega_pedido_id'] = isset($current['ruta_contraentrega_pedido_id']) && (int) $current['ruta_contraentrega_pedido_id'] > 0 ? (int) $current['ruta_contraentrega_pedido_id'] : null;
|
||||
$order['ruta_contraentrega_subido_at'] = $current['ruta_contraentrega_subido_at'] ?? null;
|
||||
$order['ruta_contraentrega_subido_por'] = isset($current['ruta_contraentrega_subido_por']) && (int) $current['ruta_contraentrega_subido_por'] > 0 ? (int) $current['ruta_contraentrega_subido_por'] : null;
|
||||
$order['pedido_rotulado_pedido_id'] = isset($current['pedido_rotulado_pedido_id']) && (int) $current['pedido_rotulado_pedido_id'] > 0 ? (int) $current['pedido_rotulado_pedido_id'] : null;
|
||||
$order['pedido_rotulado_subido_at'] = $current['pedido_rotulado_subido_at'] ?? null;
|
||||
$order['pedido_rotulado_subido_por'] = isset($current['pedido_rotulado_subido_por']) && (int) $current['pedido_rotulado_subido_por'] > 0 ? (int) $current['pedido_rotulado_subido_por'] : null;
|
||||
$order['ultima_gestion_at'] = $current['ultima_gestion_at'] ?? ($current['updated_at'] ?? null);
|
||||
|
||||
foreach ($editableFields as $field) {
|
||||
@ -320,6 +335,7 @@ function drive_test_ensure_orders_table(PDO $pdo): void
|
||||
`source_key` CHAR(40) NOT NULL,
|
||||
`codigo` VARCHAR(80) DEFAULT NULL,
|
||||
`import_id` VARCHAR(120) DEFAULT NULL,
|
||||
`source_row` INT NULL,
|
||||
`is_agregado` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`drive_imported_at` DATETIME NULL,
|
||||
`nombre` VARCHAR(255) DEFAULT NULL,
|
||||
@ -346,6 +362,7 @@ function drive_test_ensure_orders_table(PDO $pdo): void
|
||||
|
||||
// In case the existing table was created before we introduced store_key.
|
||||
cc_test_ensure_column($pdo, "callcenter_test_orders", "store_key", "VARCHAR(50) NULL");
|
||||
cc_test_ensure_column($pdo, "callcenter_test_orders", "source_row", "INT NULL AFTER `import_id`");
|
||||
|
||||
// Asegura columna para pedidos cargados manualmente (Agregados)
|
||||
cc_test_ensure_column($pdo, "callcenter_test_orders", "is_agregado", "TINYINT(1) NOT NULL DEFAULT 0");
|
||||
@ -384,11 +401,14 @@ function drive_test_ensure_import_checkpoints_table(PDO $pdo): void
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `drive_test_import_checkpoints` (
|
||||
`store_key` VARCHAR(50) NOT NULL,
|
||||
`last_processed_row` INT NOT NULL DEFAULT 0,
|
||||
`baseline_start_row` INT NOT NULL DEFAULT 0,
|
||||
`last_sync_at` DATETIME NULL,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`store_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
||||
|
||||
cc_test_ensure_column($pdo, 'drive_test_import_checkpoints', 'baseline_start_row', 'INT NOT NULL DEFAULT 0 AFTER `last_processed_row`');
|
||||
|
||||
$checked = true;
|
||||
}
|
||||
|
||||
@ -396,26 +416,33 @@ function drive_test_sync_orders_incremental(PDO $pdo, string $storeKey, int $ove
|
||||
{
|
||||
$storeKey = mb_strtolower(trim($storeKey));
|
||||
$storeConfig = drive_test_get_store_config($storeKey);
|
||||
$baselineStartRow = (int) ($storeConfig["startRow"] ?? 5552);
|
||||
$baselineStartRow = (int) ($storeConfig['startRow'] ?? 5552);
|
||||
|
||||
drive_test_ensure_orders_table($pdo);
|
||||
drive_test_ensure_import_checkpoints_table($pdo);
|
||||
|
||||
$stmtCheckpoint = $pdo->prepare("SELECT last_processed_row FROM drive_test_import_checkpoints WHERE store_key = ? LIMIT 1");
|
||||
$stmtCheckpoint = $pdo->prepare('SELECT last_processed_row, baseline_start_row FROM drive_test_import_checkpoints WHERE store_key = ? LIMIT 1');
|
||||
$stmtCheckpoint->execute([$storeKey]);
|
||||
$checkpointRow = (int) ($stmtCheckpoint->fetchColumn() ?? 0);
|
||||
$checkpointData = $stmtCheckpoint->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||
$checkpointRow = (int) ($checkpointData['last_processed_row'] ?? 0);
|
||||
$storedBaselineStartRow = (int) ($checkpointData['baseline_start_row'] ?? 0);
|
||||
|
||||
$overlapRows = max(0, $overlapRows);
|
||||
$effectiveStartRow = $checkpointRow > 0
|
||||
? max($baselineStartRow, $checkpointRow - $overlapRows + 1)
|
||||
: $baselineStartRow;
|
||||
$mustResetToBaseline = $storedBaselineStartRow !== $baselineStartRow;
|
||||
$effectiveStartRow = $mustResetToBaseline
|
||||
? $baselineStartRow
|
||||
: ($checkpointRow > 0
|
||||
? max($baselineStartRow, $checkpointRow - $overlapRows + 1)
|
||||
: $baselineStartRow);
|
||||
|
||||
$preview = drive_test_fetch_orders(0, $effectiveStartRow, $storeKey);
|
||||
$driveOrders = $preview["orders"] ?? [];
|
||||
$driveTotalRows = (int) ($preview["total_rows"] ?? 0);
|
||||
$driveOrders = $preview['orders'] ?? [];
|
||||
$driveTotalRows = (int) ($preview['total_rows'] ?? 0);
|
||||
|
||||
$sheetLastRow = ($effectiveStartRow - 1) + $driveTotalRows;
|
||||
$newCheckpointRow = max($checkpointRow, (int) $sheetLastRow);
|
||||
$newCheckpointRow = $mustResetToBaseline
|
||||
? (int) $sheetLastRow
|
||||
: max($checkpointRow, (int) $sheetLastRow);
|
||||
|
||||
if (!empty($driveOrders)) {
|
||||
$stmtUpsert = $pdo->prepare(
|
||||
@ -424,6 +451,7 @@ function drive_test_sync_orders_incremental(PDO $pdo, string $storeKey, int $ove
|
||||
source_key,
|
||||
codigo,
|
||||
import_id,
|
||||
source_row,
|
||||
drive_imported_at,
|
||||
nombre,
|
||||
direccion_drive,
|
||||
@ -445,6 +473,7 @@ function drive_test_sync_orders_incremental(PDO $pdo, string $storeKey, int $ove
|
||||
:source_key,
|
||||
:codigo,
|
||||
:import_id,
|
||||
:source_row,
|
||||
:drive_imported_at,
|
||||
:nombre,
|
||||
:direccion_drive,
|
||||
@ -465,6 +494,7 @@ function drive_test_sync_orders_incremental(PDO $pdo, string $storeKey, int $ove
|
||||
store_key = VALUES(store_key),
|
||||
codigo = VALUES(codigo),
|
||||
import_id = VALUES(import_id),
|
||||
source_row = VALUES(source_row),
|
||||
is_agregado = 0,
|
||||
drive_imported_at = VALUES(drive_imported_at),
|
||||
nombre = VALUES(nombre),
|
||||
@ -486,67 +516,78 @@ function drive_test_sync_orders_incremental(PDO $pdo, string $storeKey, int $ove
|
||||
);
|
||||
|
||||
foreach ($driveOrders as $order) {
|
||||
$driveImportedAt = drive_test_parse_datetime_mysql($order["import_id"] ?? null);
|
||||
$driveImportedAt = drive_test_parse_datetime_mysql($order['import_id'] ?? null);
|
||||
$sourceRow = isset($order['source_row']) && (int) $order['source_row'] > 0 ? (int) $order['source_row'] : null;
|
||||
|
||||
$stmtUpsert->execute([
|
||||
":store_key" => $storeKey,
|
||||
":source_key" => $order["source_key"] ?? "",
|
||||
":codigo" => ($order["codigo"] ?? "") !== "" ? ($order["codigo"] ?? null) : null,
|
||||
":import_id" => ($order["import_id"] ?? "") !== "" ? ($order["import_id"] ?? null) : null,
|
||||
":drive_imported_at" => $driveImportedAt,
|
||||
":nombre" => ($order["nombre"] ?? "") !== "" ? ($order["nombre"] ?? null) : null,
|
||||
":direccion_drive" => ($order["direccion"] ?? "") !== "" ? ($order["direccion"] ?? null) : null,
|
||||
":referencia_drive" => ($order["referencia"] ?? "") !== "" ? ($order["referencia"] ?? null) : null,
|
||||
":sede_drive" => ($order["sede"] ?? "") !== "" ? ($order["sede"] ?? null) : null,
|
||||
":ciudad_drive" => ($order["ciudad"] ?? "") !== "" ? ($order["ciudad"] ?? null) : null,
|
||||
":distrito_drive" => ($order["distrito"] ?? "") !== "" ? ($order["distrito"] ?? null) : null,
|
||||
":dni_drive" => ($order["dni"] ?? "") !== "" ? ($order["dni"] ?? null) : null,
|
||||
":observaciones_drive" => ($order["observaciones"] ?? "") !== "" ? ($order["observaciones"] ?? null) : null,
|
||||
":celular" => ($order["celular"] ?? "") !== "" ? ($order["celular"] ?? null) : null,
|
||||
":producto" => ($order["producto"] ?? "") !== "" ? ($order["producto"] ?? null) : null,
|
||||
":cantidad" => ($order["cantidad"] ?? "") !== "" ? ($order["cantidad"] ?? null) : null,
|
||||
":precio" => ($order["precio"] ?? "") !== "" ? ($order["precio"] ?? null) : null,
|
||||
":pais" => ($order["pais"] ?? "") !== "" ? ($order["pais"] ?? null) : null,
|
||||
":coordenadas" => ($order["coordenadas"] ?? "") !== "" ? ($order["coordenadas"] ?? null) : null,
|
||||
":metodo" => ($order["metodo"] ?? "") !== "" ? ($order["metodo"] ?? null) : null,
|
||||
':store_key' => $storeKey,
|
||||
':source_key' => $order['source_key'] ?? '',
|
||||
':codigo' => ($order['codigo'] ?? '') !== '' ? ($order['codigo'] ?? null) : null,
|
||||
':import_id' => ($order['import_id'] ?? '') !== '' ? ($order['import_id'] ?? null) : null,
|
||||
':source_row' => $sourceRow,
|
||||
':drive_imported_at' => $driveImportedAt,
|
||||
':nombre' => ($order['nombre'] ?? '') !== '' ? ($order['nombre'] ?? null) : null,
|
||||
':direccion_drive' => ($order['direccion'] ?? '') !== '' ? ($order['direccion'] ?? null) : null,
|
||||
':referencia_drive' => ($order['referencia'] ?? '') !== '' ? ($order['referencia'] ?? null) : null,
|
||||
':sede_drive' => ($order['sede'] ?? '') !== '' ? ($order['sede'] ?? null) : null,
|
||||
':ciudad_drive' => ($order['ciudad'] ?? '') !== '' ? ($order['ciudad'] ?? null) : null,
|
||||
':distrito_drive' => ($order['distrito'] ?? '') !== '' ? ($order['distrito'] ?? null) : null,
|
||||
':dni_drive' => ($order['dni'] ?? '') !== '' ? ($order['dni'] ?? null) : null,
|
||||
':observaciones_drive' => ($order['observaciones'] ?? '') !== '' ? ($order['observaciones'] ?? null) : null,
|
||||
':celular' => ($order['celular'] ?? '') !== '' ? ($order['celular'] ?? null) : null,
|
||||
':producto' => ($order['producto'] ?? '') !== '' ? ($order['producto'] ?? null) : null,
|
||||
':cantidad' => ($order['cantidad'] ?? '') !== '' ? ($order['cantidad'] ?? null) : null,
|
||||
':precio' => ($order['precio'] ?? '') !== '' ? ($order['precio'] ?? null) : null,
|
||||
':pais' => ($order['pais'] ?? '') !== '' ? ($order['pais'] ?? null) : null,
|
||||
':coordenadas' => ($order['coordenadas'] ?? '') !== '' ? ($order['coordenadas'] ?? null) : null,
|
||||
':metodo' => ($order['metodo'] ?? '') !== '' ? ($order['metodo'] ?? null) : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$stmtCheckpointUpsert = $pdo->prepare(
|
||||
"INSERT INTO drive_test_import_checkpoints (store_key, last_processed_row, last_sync_at)
|
||||
VALUES (:store_key, :last_processed_row, CURRENT_TIMESTAMP)
|
||||
"INSERT INTO drive_test_import_checkpoints (store_key, last_processed_row, baseline_start_row, last_sync_at)
|
||||
VALUES (:store_key, :last_processed_row, :baseline_start_row, CURRENT_TIMESTAMP)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
last_processed_row = VALUES(last_processed_row),
|
||||
baseline_start_row = VALUES(baseline_start_row),
|
||||
last_sync_at = CURRENT_TIMESTAMP"
|
||||
);
|
||||
$stmtCheckpointUpsert->execute([
|
||||
":store_key" => $storeKey,
|
||||
":last_processed_row" => $newCheckpointRow,
|
||||
':store_key' => $storeKey,
|
||||
':last_processed_row' => $newCheckpointRow,
|
||||
':baseline_start_row' => $baselineStartRow,
|
||||
]);
|
||||
|
||||
return [
|
||||
"checkpoint_was" => $checkpointRow,
|
||||
"last_processed_row" => $newCheckpointRow,
|
||||
"next_start_row" => $newCheckpointRow + 1,
|
||||
"effective_start_row" => $effectiveStartRow,
|
||||
"drive_rows_total" => $driveTotalRows,
|
||||
"orders_synced_count" => count($driveOrders),
|
||||
'checkpoint_was' => $checkpointRow,
|
||||
'last_processed_row' => $newCheckpointRow,
|
||||
'next_start_row' => $newCheckpointRow + 1,
|
||||
'effective_start_row' => $effectiveStartRow,
|
||||
'drive_rows_total' => $driveTotalRows,
|
||||
'orders_synced_count' => count($driveOrders),
|
||||
];
|
||||
}
|
||||
|
||||
function drive_test_fetch_orders_from_db(PDO $pdo, string $storeKey, ?bool $onlyAgregados = null): array
|
||||
{
|
||||
$storeKey = mb_strtolower(trim($storeKey));
|
||||
$storeConfig = drive_test_get_store_config($storeKey);
|
||||
drive_test_ensure_orders_table($pdo);
|
||||
|
||||
$where = "WHERE store_key = ?";
|
||||
$where = 'WHERE store_key = ?';
|
||||
$params = [$storeKey];
|
||||
|
||||
if ($onlyAgregados === true) {
|
||||
$where .= " AND is_agregado = 1";
|
||||
$where .= ' AND is_agregado = 1';
|
||||
} elseif ($onlyAgregados === false) {
|
||||
$where .= " AND (is_agregado = 0 OR is_agregado IS NULL)";
|
||||
$where .= ' AND (is_agregado = 0 OR is_agregado IS NULL)';
|
||||
}
|
||||
|
||||
$minVisibleRow = !empty($storeConfig['enforce_db_start_row']) ? (int) ($storeConfig['startRow'] ?? 0) : 0;
|
||||
if ($minVisibleRow > 0) {
|
||||
$where .= ' AND (is_agregado = 1 OR (source_row IS NOT NULL AND source_row >= ?))';
|
||||
$params[] = $minVisibleRow;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
@ -556,6 +597,9 @@ function drive_test_fetch_orders_from_db(PDO $pdo, string $storeKey, ?bool $only
|
||||
source_key,
|
||||
codigo,
|
||||
import_id,
|
||||
source_row,
|
||||
drive_imported_at,
|
||||
first_seen_at,
|
||||
nombre,
|
||||
celular,
|
||||
producto,
|
||||
@ -573,35 +617,42 @@ function drive_test_fetch_orders_from_db(PDO $pdo, string $storeKey, ?bool $only
|
||||
observaciones_drive
|
||||
FROM callcenter_test_orders
|
||||
$where
|
||||
ORDER BY id DESC"
|
||||
ORDER BY
|
||||
CASE WHEN source_row IS NULL THEN 1 ELSE 0 END ASC,
|
||||
source_row ASC,
|
||||
id DESC"
|
||||
);
|
||||
$stmt->execute($params);
|
||||
|
||||
$orders = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$orders[] = [
|
||||
"id" => (int) ($row["id"] ?? 0),
|
||||
"is_agregado" => (int) ($row["is_agregado"] ?? 0),
|
||||
"source_key" => (string) ($row["source_key"] ?? ""),
|
||||
"codigo" => (string) ($row["codigo"] ?? ""),
|
||||
"import_id" => (string) ($row["import_id"] ?? ""),
|
||||
"nombre" => (string) ($row["nombre"] ?? ""),
|
||||
"direccion" => (string) ($row["direccion_drive"] ?? ""),
|
||||
"referencia" => (string) ($row["referencia_drive"] ?? ""),
|
||||
"agencia" => "",
|
||||
"sede_agencia" => "",
|
||||
"distrito" => (string) ($row["distrito_drive"] ?? ""),
|
||||
"celular" => (string) ($row["celular"] ?? ""),
|
||||
"producto" => (string) ($row["producto"] ?? ""),
|
||||
"cantidad" => (string) ($row["cantidad"] ?? ""),
|
||||
"precio" => (string) ($row["precio"] ?? ""),
|
||||
"pais" => (string) ($row["pais"] ?? ""),
|
||||
"coordenadas" => (string) ($row["coordenadas"] ?? ""),
|
||||
"ciudad" => (string) ($row["ciudad_drive"] ?? ""),
|
||||
"metodo" => (string) ($row["metodo"] ?? ""),
|
||||
"sede" => (string) ($row["sede_drive"] ?? ""),
|
||||
"dni" => (string) ($row["dni_drive"] ?? ""),
|
||||
"observaciones" => (string) ($row["observaciones_drive"] ?? ""),
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'is_agregado' => (int) ($row['is_agregado'] ?? 0),
|
||||
'source_key' => (string) ($row['source_key'] ?? ''),
|
||||
'codigo' => (string) ($row['codigo'] ?? ''),
|
||||
'import_id' => (string) ($row['import_id'] ?? ''),
|
||||
'source_row' => isset($row['source_row']) && (int) $row['source_row'] > 0 ? (int) $row['source_row'] : null,
|
||||
'drive_imported_at' => (string) ($row['drive_imported_at'] ?? ''),
|
||||
'first_seen_at' => (string) ($row['first_seen_at'] ?? ''),
|
||||
'nombre' => (string) ($row['nombre'] ?? ''),
|
||||
'direccion' => (string) ($row['direccion_drive'] ?? ''),
|
||||
'referencia' => (string) ($row['referencia_drive'] ?? ''),
|
||||
'agencia' => '',
|
||||
'sede_agencia' => '',
|
||||
'distrito' => (string) ($row['distrito_drive'] ?? ''),
|
||||
'celular' => (string) ($row['celular'] ?? ''),
|
||||
'producto' => (string) ($row['producto'] ?? ''),
|
||||
'cantidad' => (string) ($row['cantidad'] ?? ''),
|
||||
'precio' => (string) ($row['precio'] ?? ''),
|
||||
'monto_adelantado' => '',
|
||||
'pais' => (string) ($row['pais'] ?? ''),
|
||||
'coordenadas' => (string) ($row['coordenadas'] ?? ''),
|
||||
'ciudad' => (string) ($row['ciudad_drive'] ?? ''),
|
||||
'metodo' => (string) ($row['metodo'] ?? ''),
|
||||
'sede' => (string) ($row['sede_drive'] ?? ''),
|
||||
'dni' => (string) ($row['dni_drive'] ?? ''),
|
||||
'observaciones' => (string) ($row['observaciones_drive'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -32,6 +32,84 @@ function cc_test_normalize_nullable_text(string $key, int $maxLen = 3000): ?stri
|
||||
return $value;
|
||||
}
|
||||
|
||||
function cc_test_normalize_optional_image_upload(string $key): ?array
|
||||
{
|
||||
if (!isset($_FILES[$key]) || !is_array($_FILES[$key])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file = $_FILES[$key];
|
||||
$error = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE);
|
||||
if ($error === UPLOAD_ERR_NO_FILE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($error !== UPLOAD_ERR_OK) {
|
||||
throw new RuntimeException('No se pudo subir la imagen de sustento.');
|
||||
}
|
||||
|
||||
$tmpName = (string) ($file['tmp_name'] ?? '');
|
||||
if ($tmpName === '' || !is_uploaded_file($tmpName)) {
|
||||
throw new RuntimeException('La imagen de sustento no es válida.');
|
||||
}
|
||||
|
||||
$size = (int) ($file['size'] ?? 0);
|
||||
if ($size <= 0) {
|
||||
throw new RuntimeException('La imagen de sustento está vacía.');
|
||||
}
|
||||
|
||||
if ($size > 8 * 1024 * 1024) {
|
||||
throw new RuntimeException('La imagen de sustento no puede pesar más de 8 MB.');
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
function cc_test_store_tracking_evidence_image(array $file, string $prefix, string $sourceKey, int $userId): array
|
||||
{
|
||||
$tmpName = (string) ($file['tmp_name'] ?? '');
|
||||
$imgInfo = @getimagesize($tmpName);
|
||||
if ($imgInfo === false || empty($imgInfo['mime'])) {
|
||||
throw new RuntimeException('El archivo no parece ser una imagen válida.');
|
||||
}
|
||||
|
||||
$mime = (string) $imgInfo['mime'];
|
||||
$allowed = [
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/webp' => 'webp',
|
||||
];
|
||||
|
||||
if (!isset($allowed[$mime])) {
|
||||
throw new RuntimeException('Formato no permitido. Use JPG, PNG o WEBP.');
|
||||
}
|
||||
|
||||
$uploadDir = __DIR__ . '/assets/uploads/callcenter_tracking';
|
||||
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0775, true) && !is_dir($uploadDir)) {
|
||||
throw new RuntimeException('No se pudo preparar la carpeta de imágenes.');
|
||||
}
|
||||
|
||||
$filename = sprintf(
|
||||
'%s_%s_%d_%d_%s.%s',
|
||||
$prefix,
|
||||
substr($sourceKey, 0, 12),
|
||||
max(0, $userId),
|
||||
time(),
|
||||
bin2hex(random_bytes(4)),
|
||||
$allowed[$mime]
|
||||
);
|
||||
|
||||
$targetPath = $uploadDir . '/' . $filename;
|
||||
if (!move_uploaded_file($tmpName, $targetPath)) {
|
||||
throw new RuntimeException('No se pudo guardar la imagen en el servidor.');
|
||||
}
|
||||
|
||||
return [
|
||||
'absolute_path' => $targetPath,
|
||||
'relative_path' => 'assets/uploads/callcenter_tracking/' . $filename,
|
||||
];
|
||||
}
|
||||
|
||||
function cc_test_fetch_source_order(PDO $pdo, string $sourceKey): ?array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
@ -40,6 +118,8 @@ function cc_test_fetch_source_order(PDO $pdo, string $sourceKey): ?array
|
||||
store_key,
|
||||
codigo,
|
||||
import_id,
|
||||
drive_imported_at,
|
||||
first_seen_at,
|
||||
nombre,
|
||||
celular,
|
||||
producto,
|
||||
@ -407,10 +487,296 @@ function cc_test_sync_route_order(PDO $pdo, ?int $existingPedidoId, array $paylo
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
function cc_test_prepare_rotulado_order_payload(array $sourceOrder, array $formData, int $fallbackUserId): array
|
||||
{
|
||||
if (($sourceOrder['store_key'] ?? '') !== 'otra_tienda') {
|
||||
throw new RuntimeException('En este primer paso la subida automática a Pedidos Rotulados está activa solo para TUANI.');
|
||||
}
|
||||
|
||||
$nombreCompleto = trim((string) ($sourceOrder['nombre'] ?? ''));
|
||||
$celular = trim((string) ($sourceOrder['celular'] ?? ''));
|
||||
$dni = trim((string) (($formData['dni'] ?? '') !== '' ? ($formData['dni'] ?? '') : ($sourceOrder['dni_drive'] ?? '')));
|
||||
$agencia = cc_test_normalize_shipping_agency($formData['agencia'] ?? '');
|
||||
$sedeEnvio = trim((string) ($formData['sede_agencia'] ?? ''));
|
||||
$montoAdelantado = cc_test_parse_amount((string) ($formData['monto_adelantado'] ?? ''));
|
||||
$observacionesPedido = trim((string) (($formData['observaciones'] ?? '') !== '' ? ($formData['observaciones'] ?? '') : ($sourceOrder['observaciones_drive'] ?? '')));
|
||||
$notaSeguimiento = trim((string) ($formData['nota_seguimiento'] ?? ''));
|
||||
|
||||
if ($nombreCompleto === '' || $celular === '') {
|
||||
throw new RuntimeException('El pedido no tiene nombre o celular suficientes para enviarlo a Pedidos Rotulados.');
|
||||
}
|
||||
|
||||
if ($dni === '') {
|
||||
throw new RuntimeException('Debes completar el DNI antes de subir el pedido a Pedidos Rotulados.');
|
||||
}
|
||||
|
||||
if ($agencia === null || !in_array($agencia, cc_test_shipping_agency_options(), true)) {
|
||||
throw new RuntimeException('Selecciona una agencia válida para Pedidos Rotulados.');
|
||||
}
|
||||
|
||||
if ($sedeEnvio === '') {
|
||||
throw new RuntimeException('Debes completar la sede de envío antes de subir el pedido a Pedidos Rotulados.');
|
||||
}
|
||||
|
||||
if ($montoAdelantado === null) {
|
||||
throw new RuntimeException('Debes ingresar un monto de adelanto válido antes de subir el pedido a Pedidos Rotulados.');
|
||||
}
|
||||
|
||||
$productoBase = trim((string) ($formData['producto'] ?? ''));
|
||||
if ($productoBase === '') {
|
||||
$productoBase = trim((string) ($sourceOrder['producto'] ?? ''));
|
||||
}
|
||||
$cantidadBase = trim((string) ($formData['cantidad'] ?? ''));
|
||||
if ($cantidadBase === '') {
|
||||
$cantidadBase = trim((string) ($sourceOrder['cantidad'] ?? ''));
|
||||
}
|
||||
$precioBase = trim((string) ($formData['precio'] ?? ''));
|
||||
if ($precioBase === '') {
|
||||
$precioBase = trim((string) ($sourceOrder['precio'] ?? ''));
|
||||
}
|
||||
|
||||
$productoPrincipal = trim((string) ($formData['confirmacion_producto'] ?? ''));
|
||||
if ($productoPrincipal === '') {
|
||||
$productoPrincipal = $productoBase;
|
||||
}
|
||||
|
||||
$cantidadPrincipalRaw = trim((string) ($formData['confirmacion_cantidad'] ?? ''));
|
||||
if ($cantidadPrincipalRaw === '') {
|
||||
$cantidadPrincipalRaw = $cantidadBase;
|
||||
}
|
||||
|
||||
$precioPrincipalRaw = trim((string) ($formData['confirmacion_precio'] ?? ''));
|
||||
if ($precioPrincipalRaw === '') {
|
||||
$precioPrincipalRaw = $precioBase;
|
||||
}
|
||||
|
||||
if ($productoPrincipal === '') {
|
||||
throw new RuntimeException('El pedido no tiene producto principal para subir a Pedidos Rotulados.');
|
||||
}
|
||||
|
||||
$cantidadPrincipal = cc_test_parse_quantity($cantidadPrincipalRaw);
|
||||
if ($cantidadPrincipal === null) {
|
||||
throw new RuntimeException('La cantidad principal del pedido no es válida.');
|
||||
}
|
||||
|
||||
$precioPrincipal = cc_test_parse_amount($precioPrincipalRaw);
|
||||
if ($precioPrincipal === null) {
|
||||
throw new RuntimeException('El precio principal del pedido no es válido.');
|
||||
}
|
||||
|
||||
$productoExtra = trim((string) ($formData['confirmacion_producto_extra'] ?? ''));
|
||||
$cantidadExtraRaw = trim((string) ($formData['confirmacion_cantidad_extra'] ?? ''));
|
||||
$precioExtraRaw = trim((string) ($formData['confirmacion_precio_extra'] ?? ''));
|
||||
$hayExtra = $productoExtra !== '' || $cantidadExtraRaw !== '' || $precioExtraRaw !== '';
|
||||
|
||||
$productos = [$productoPrincipal];
|
||||
$detalleProductos = [$productoPrincipal . ' (x' . $cantidadPrincipal . ')'];
|
||||
$cantidadTotal = $cantidadPrincipal;
|
||||
$montoTotal = $precioPrincipal;
|
||||
|
||||
if ($hayExtra) {
|
||||
if ($productoExtra === '' || $cantidadExtraRaw === '' || $precioExtraRaw === '') {
|
||||
throw new RuntimeException('Si usas producto adicional, completa producto, cantidad y precio adicional.');
|
||||
}
|
||||
|
||||
$cantidadExtra = cc_test_parse_quantity($cantidadExtraRaw);
|
||||
if ($cantidadExtra === null) {
|
||||
throw new RuntimeException('La cantidad del producto adicional no es válida.');
|
||||
}
|
||||
|
||||
$precioExtra = cc_test_parse_amount($precioExtraRaw);
|
||||
if ($precioExtra === null) {
|
||||
throw new RuntimeException('El precio del producto adicional no es válido.');
|
||||
}
|
||||
|
||||
$productos[] = $productoExtra;
|
||||
$detalleProductos[] = $productoExtra . ' (x' . $cantidadExtra . ')';
|
||||
$cantidadTotal += $cantidadExtra;
|
||||
$montoTotal += $precioExtra;
|
||||
}
|
||||
|
||||
$identificadorPedido = trim((string) ($sourceOrder['codigo'] ?? ''));
|
||||
if ($identificadorPedido === '') {
|
||||
$identificadorPedido = trim((string) ($sourceOrder['import_id'] ?? ''));
|
||||
}
|
||||
if ($identificadorPedido === '') {
|
||||
$identificadorPedido = 'source:' . substr((string) ($sourceOrder['source_key'] ?? ''), 0, 12);
|
||||
}
|
||||
|
||||
$notas = cc_test_build_contraentrega_notes([
|
||||
'Origen: Call Center TUANI',
|
||||
'Pedido base: ' . $identificadorPedido,
|
||||
'Detalle confirmado: ' . implode(', ', $detalleProductos),
|
||||
'Agencia: ' . $agencia,
|
||||
'Sede de envío: ' . $sedeEnvio,
|
||||
'Monto de adelanto: S/ ' . number_format($montoAdelantado, 2, '.', ''),
|
||||
$observacionesPedido !== '' ? 'Observaciones del pedido: ' . $observacionesPedido : '',
|
||||
$notaSeguimiento !== '' ? 'Nota interna Call Center: ' . $notaSeguimiento : '',
|
||||
]);
|
||||
|
||||
$codigoTracking = trim((string) ($sourceOrder['codigo'] ?? ''));
|
||||
$montoTotal = round($montoTotal, 2);
|
||||
$montoAdelantado = round($montoAdelantado, 2);
|
||||
$montoDebe = round(max(0, $montoTotal - $montoAdelantado), 2);
|
||||
|
||||
return [
|
||||
'source_key' => (string) ($sourceOrder['source_key'] ?? ''),
|
||||
'dni_cliente' => $dni,
|
||||
'nombre_completo' => $nombreCompleto,
|
||||
'celular' => $celular,
|
||||
'agencia' => $agencia,
|
||||
'sede_envio' => $sedeEnvio,
|
||||
'codigo_tracking' => $codigoTracking !== '' ? $codigoTracking : null,
|
||||
'producto' => implode(', ', $productos),
|
||||
'cantidad' => $cantidadTotal,
|
||||
'monto_total' => $montoTotal,
|
||||
'monto_adelantado' => $montoAdelantado,
|
||||
'monto_debe' => $montoDebe,
|
||||
'asesor_id' => $fallbackUserId > 0 ? $fallbackUserId : null,
|
||||
'notas' => $notas,
|
||||
'nota_adicional' => $notaSeguimiento !== '' ? $notaSeguimiento : null,
|
||||
'observacion' => $observacionesPedido !== '' ? $observacionesPedido : null,
|
||||
'descargo' => $observacionesPedido !== '' ? $observacionesPedido : null,
|
||||
'seguimiento' => 'Subido desde Call Center TUANI',
|
||||
'estado' => 'ROTULADO 📦',
|
||||
];
|
||||
}
|
||||
|
||||
function cc_test_sync_rotulado_order(PDO $pdo, ?int $existingPedidoId, array $payload): array
|
||||
{
|
||||
$existing = null;
|
||||
if ($existingPedidoId !== null && $existingPedidoId > 0) {
|
||||
$stmtExisting = $pdo->prepare('SELECT * FROM pedidos WHERE id = ? LIMIT 1');
|
||||
$stmtExisting->execute([$existingPedidoId]);
|
||||
$existing = $stmtExisting->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
$stmtUpdate = $pdo->prepare(
|
||||
'UPDATE pedidos SET
|
||||
dni_cliente = :dni_cliente,
|
||||
nombre_completo = :nombre_completo,
|
||||
celular = :celular,
|
||||
agencia = :agencia,
|
||||
sede_envio = :sede_envio,
|
||||
codigo_tracking = COALESCE(NULLIF(:codigo_tracking, \'\'), codigo_tracking),
|
||||
producto = :producto,
|
||||
cantidad = :cantidad,
|
||||
monto_total = :monto_total,
|
||||
monto_adelantado = :monto_adelantado,
|
||||
monto_debe = :monto_debe,
|
||||
asesor_id = :asesor_id,
|
||||
notas = :notas,
|
||||
nota_adicional = :nota_adicional,
|
||||
observacion = :observacion,
|
||||
descargo = :descargo,
|
||||
seguimiento = :seguimiento,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = :id'
|
||||
);
|
||||
|
||||
$stmtUpdate->execute([
|
||||
':dni_cliente' => $payload['dni_cliente'],
|
||||
':nombre_completo' => $payload['nombre_completo'],
|
||||
':celular' => $payload['celular'],
|
||||
':agencia' => $payload['agencia'],
|
||||
':sede_envio' => $payload['sede_envio'],
|
||||
':codigo_tracking' => $payload['codigo_tracking'] ?? '',
|
||||
':producto' => $payload['producto'],
|
||||
':cantidad' => $payload['cantidad'],
|
||||
':monto_total' => $payload['monto_total'],
|
||||
':monto_adelantado' => $payload['monto_adelantado'],
|
||||
':monto_debe' => $payload['monto_debe'],
|
||||
':asesor_id' => $payload['asesor_id'],
|
||||
':notas' => !empty(trim((string) ($existing['notas'] ?? ''))) ? $existing['notas'] : $payload['notas'],
|
||||
':nota_adicional' => !empty(trim((string) ($existing['nota_adicional'] ?? ''))) ? $existing['nota_adicional'] : $payload['nota_adicional'],
|
||||
':observacion' => !empty(trim((string) ($existing['observacion'] ?? ''))) ? $existing['observacion'] : $payload['observacion'],
|
||||
':descargo' => !empty(trim((string) ($existing['descargo'] ?? ''))) ? $existing['descargo'] : $payload['descargo'],
|
||||
':seguimiento' => !empty(trim((string) ($existing['seguimiento'] ?? ''))) ? $existing['seguimiento'] : $payload['seguimiento'],
|
||||
':id' => (int) $existing['id'],
|
||||
]);
|
||||
|
||||
return [
|
||||
'pedido_id' => (int) $existing['id'],
|
||||
'action' => 'updated',
|
||||
];
|
||||
}
|
||||
|
||||
$stmtInsert = $pdo->prepare(
|
||||
'INSERT INTO pedidos (
|
||||
dni_cliente,
|
||||
nombre_completo,
|
||||
celular,
|
||||
agencia,
|
||||
sede_envio,
|
||||
codigo_tracking,
|
||||
producto,
|
||||
cantidad,
|
||||
monto_total,
|
||||
monto_adelantado,
|
||||
monto_debe,
|
||||
estado,
|
||||
asesor_id,
|
||||
notas,
|
||||
nota_adicional,
|
||||
observacion,
|
||||
descargo,
|
||||
seguimiento
|
||||
) VALUES (
|
||||
:dni_cliente,
|
||||
:nombre_completo,
|
||||
:celular,
|
||||
:agencia,
|
||||
:sede_envio,
|
||||
:codigo_tracking,
|
||||
:producto,
|
||||
:cantidad,
|
||||
:monto_total,
|
||||
:monto_adelantado,
|
||||
:monto_debe,
|
||||
:estado,
|
||||
:asesor_id,
|
||||
:notas,
|
||||
:nota_adicional,
|
||||
:observacion,
|
||||
:descargo,
|
||||
:seguimiento
|
||||
)'
|
||||
);
|
||||
|
||||
$stmtInsert->execute([
|
||||
':dni_cliente' => $payload['dni_cliente'],
|
||||
':nombre_completo' => $payload['nombre_completo'],
|
||||
':celular' => $payload['celular'],
|
||||
':agencia' => $payload['agencia'],
|
||||
':sede_envio' => $payload['sede_envio'],
|
||||
':codigo_tracking' => $payload['codigo_tracking'],
|
||||
':producto' => $payload['producto'],
|
||||
':cantidad' => $payload['cantidad'],
|
||||
':monto_total' => $payload['monto_total'],
|
||||
':monto_adelantado' => $payload['monto_adelantado'],
|
||||
':monto_debe' => $payload['monto_debe'],
|
||||
':estado' => $payload['estado'],
|
||||
':asesor_id' => $payload['asesor_id'],
|
||||
':notas' => $payload['notas'],
|
||||
':nota_adicional' => $payload['nota_adicional'],
|
||||
':observacion' => $payload['observacion'],
|
||||
':descargo' => $payload['descargo'],
|
||||
':seguimiento' => $payload['seguimiento'],
|
||||
]);
|
||||
|
||||
return [
|
||||
'pedido_id' => (int) $pdo->lastInsertId(),
|
||||
'action' => 'created',
|
||||
];
|
||||
}
|
||||
|
||||
$sourceKey = trim((string) ($_POST['source_key'] ?? ''));
|
||||
$estado = cc_test_normalize_state(trim((string) ($_POST['estado'] ?? 'POR LLAMAR')));
|
||||
$validStates = cc_test_valid_states();
|
||||
$subirARuta = (string) ($_POST['subir_a_ruta'] ?? '') === '1';
|
||||
$subirALogistica = (string) ($_POST['subir_a_logistica'] ?? ($_POST['subir_a_ruta'] ?? '')) === '1';
|
||||
$moverAPromoFinal = (string) ($_POST['mover_a_promo_final'] ?? '') === '1';
|
||||
|
||||
if ($sourceKey === '' || !preg_match('/^[a-f0-9]{40}$/', $sourceKey)) {
|
||||
http_response_code(400);
|
||||
@ -424,6 +790,8 @@ if (!in_array($estado, $validStates, true)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$createdFiles = [];
|
||||
|
||||
try {
|
||||
$nota = cc_test_normalize_nullable_text('nota_seguimiento', 3000);
|
||||
$direccion = cc_test_normalize_nullable_text('direccion', 1000);
|
||||
@ -435,24 +803,66 @@ try {
|
||||
$distrito = cc_test_normalize_nullable_text('distrito', 120);
|
||||
$coordenadas = cc_test_normalize_nullable_text('coordenadas', 255);
|
||||
$dni = cc_test_normalize_nullable_text('dni', 40);
|
||||
$numeroCuentaSedeId = array_key_exists('numero_cuenta_sede_id', $_POST)
|
||||
? cc_test_normalize_nullable_text('numero_cuenta_sede_id', 120)
|
||||
: null;
|
||||
$numeroCuentaDni = array_key_exists('numero_cuenta_dni', $_POST)
|
||||
? cc_test_normalize_nullable_text('numero_cuenta_dni', 40)
|
||||
: null;
|
||||
$observaciones = cc_test_normalize_nullable_text('observaciones', 3000);
|
||||
$producto = cc_test_normalize_nullable_text('producto', 255);
|
||||
$cantidad = cc_test_normalize_nullable_text('cantidad', 50);
|
||||
$precio = cc_test_normalize_nullable_text('precio', 80);
|
||||
$montoAdelantadoRaw = cc_test_normalize_nullable_text('monto_adelantado', 80);
|
||||
$montoAdelantado = null;
|
||||
if ($montoAdelantadoRaw !== null) {
|
||||
$montoAdelantado = cc_test_parse_amount($montoAdelantadoRaw);
|
||||
if ($montoAdelantado === null) {
|
||||
throw new RuntimeException('Ingresa un monto de adelanto válido.');
|
||||
}
|
||||
}
|
||||
if ($agencia !== null) {
|
||||
$agencia = cc_test_normalize_shipping_agency($agencia) ?? $agencia;
|
||||
}
|
||||
$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);
|
||||
$promoFinalEvidenceFile = cc_test_normalize_optional_image_upload('promo_final_evidencia');
|
||||
$canceladoEvidenceFile = cc_test_normalize_optional_image_upload('cancelado_evidencia');
|
||||
|
||||
$pdo = db();
|
||||
cc_test_ensure_tracking_table($pdo);
|
||||
|
||||
$stmtCurrent = $pdo->prepare('SELECT estado, numero_cuenta_enviado_at, user_id, ruta_contraentrega_pedido_id FROM callcenter_test_tracking WHERE source_key = ? LIMIT 1');
|
||||
$stmtCurrent = $pdo->prepare('SELECT estado, numero_cuenta_enviado_at, numero_cuenta_sede_id, numero_cuenta_dni, user_id, promo_final_evidencia_path, promo_final_evidencia_subido_at, promo_final_evidencia_subido_por, cancelado_evidencia_path, cancelado_evidencia_subido_at, cancelado_evidencia_subido_por, ruta_contraentrega_pedido_id, pedido_rotulado_pedido_id FROM callcenter_test_tracking WHERE source_key = ? LIMIT 1');
|
||||
$stmtCurrent->execute([$sourceKey]);
|
||||
$currentTracking = $stmtCurrent->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
|
||||
if (!array_key_exists('numero_cuenta_sede_id', $_POST)) {
|
||||
$numeroCuentaSedeIdCurrent = trim((string) ($currentTracking['numero_cuenta_sede_id'] ?? ''));
|
||||
$numeroCuentaSedeId = $numeroCuentaSedeIdCurrent !== '' ? $numeroCuentaSedeIdCurrent : null;
|
||||
}
|
||||
|
||||
if (!array_key_exists('numero_cuenta_dni', $_POST)) {
|
||||
$numeroCuentaDniCurrent = trim((string) ($currentTracking['numero_cuenta_dni'] ?? ''));
|
||||
$numeroCuentaDni = $numeroCuentaDniCurrent !== '' ? $numeroCuentaDniCurrent : null;
|
||||
}
|
||||
|
||||
$promoFinalEvidencePath = trim((string) ($currentTracking['promo_final_evidencia_path'] ?? ''));
|
||||
$promoFinalEvidencePath = $promoFinalEvidencePath !== '' ? $promoFinalEvidencePath : null;
|
||||
$promoFinalEvidenceUploadedAt = $currentTracking['promo_final_evidencia_subido_at'] ?? null;
|
||||
$promoFinalEvidenceUploadedBy = isset($currentTracking['promo_final_evidencia_subido_por']) && (int) $currentTracking['promo_final_evidencia_subido_por'] > 0
|
||||
? (int) $currentTracking['promo_final_evidencia_subido_por']
|
||||
: null;
|
||||
$canceladoEvidencePath = trim((string) ($currentTracking['cancelado_evidencia_path'] ?? ''));
|
||||
$canceladoEvidencePath = $canceladoEvidencePath !== '' ? $canceladoEvidencePath : null;
|
||||
$canceladoEvidenceUploadedAt = $currentTracking['cancelado_evidencia_subido_at'] ?? null;
|
||||
$canceladoEvidenceUploadedBy = isset($currentTracking['cancelado_evidencia_subido_por']) && (int) $currentTracking['cancelado_evidencia_subido_por'] > 0
|
||||
? (int) $currentTracking['cancelado_evidencia_subido_por']
|
||||
: null;
|
||||
|
||||
$role = (string) ($_SESSION['user_role'] ?? '');
|
||||
$isAdmin = in_array($role, ['Administrador', 'admin'], true);
|
||||
if (!$isAdmin) {
|
||||
@ -509,8 +919,72 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
if ($subirARuta && $estado !== 'CONFIRMADO CONTRAENTREGA') {
|
||||
throw new RuntimeException('Para subir el pedido a ruta, el estado debe ser CONFIRMADO CONTRAENTREGA.');
|
||||
if (cc_test_requires_shipping_details($estado)) {
|
||||
if ($dni === null || trim($dni) === '') {
|
||||
throw new RuntimeException('Debes completar el DNI para CONFIRMADO ENVIO.');
|
||||
}
|
||||
|
||||
if ($agencia === null || !in_array($agencia, cc_test_shipping_agency_options(), true)) {
|
||||
throw new RuntimeException('Selecciona una agencia válida para CONFIRMADO ENVIO.');
|
||||
}
|
||||
|
||||
if ($sedeAgencia === null || trim($sedeAgencia) === '') {
|
||||
throw new RuntimeException('Debes completar la sede de envío para CONFIRMADO ENVIO.');
|
||||
}
|
||||
|
||||
if ($montoAdelantado === null) {
|
||||
throw new RuntimeException('Debes ingresar el monto de adelanto para CONFIRMADO ENVIO.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($subirALogistica && !in_array($estado, ['CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO'], true)) {
|
||||
throw new RuntimeException('Para subir el pedido, el estado debe ser CONFIRMADO CONTRAENTREGA o CONFIRMADO ENVIO.');
|
||||
}
|
||||
|
||||
if ($moverAPromoFinal) {
|
||||
if (!in_array($estado, cc_test_followup_tracking_states(), true)) {
|
||||
throw new RuntimeException('Solo puedes mover a Promo Final pedidos que sigan en seguimiento.');
|
||||
}
|
||||
|
||||
$sourceOrderForPromo = cc_test_fetch_source_order($pdo, $sourceKey);
|
||||
if (!$sourceOrderForPromo) {
|
||||
throw new RuntimeException('No encontré el pedido base para moverlo a Promo Final.');
|
||||
}
|
||||
|
||||
$promoFinalDayInfo = cc_test_followup_day_info([
|
||||
'estado' => $estado,
|
||||
'drive_imported_at' => $sourceOrderForPromo['drive_imported_at'] ?? null,
|
||||
'first_seen_at' => $sourceOrderForPromo['first_seen_at'] ?? null,
|
||||
'import_id' => $sourceOrderForPromo['import_id'] ?? null,
|
||||
]);
|
||||
if (empty($promoFinalDayInfo['is_promo_final'])) {
|
||||
throw new RuntimeException('Este pedido aún no puede pasar a Promo Final. Recién se habilita desde el Día 4.');
|
||||
}
|
||||
|
||||
if ($promoFinalEvidenceFile === null && $promoFinalEvidencePath === null) {
|
||||
throw new RuntimeException('Debes subir una imagen de sustento para mover el pedido a Promo Final.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($estado === 'CANCELADO' && $canceladoEvidenceFile === null && $canceladoEvidencePath === null) {
|
||||
throw new RuntimeException('Debes subir una imagen de sustento para guardar el pedido en estado Cancelado.');
|
||||
}
|
||||
|
||||
$actorUserId = (int) ($_SESSION['user_id'] ?? 0);
|
||||
if ($moverAPromoFinal && $promoFinalEvidenceFile !== null) {
|
||||
$storedPromoFinalEvidence = cc_test_store_tracking_evidence_image($promoFinalEvidenceFile, 'promo_final', $sourceKey, $actorUserId);
|
||||
$createdFiles[] = $storedPromoFinalEvidence['absolute_path'];
|
||||
$promoFinalEvidencePath = $storedPromoFinalEvidence['relative_path'];
|
||||
$promoFinalEvidenceUploadedAt = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
|
||||
$promoFinalEvidenceUploadedBy = $actorUserId > 0 ? $actorUserId : null;
|
||||
}
|
||||
|
||||
if ($estado === 'CANCELADO' && $canceladoEvidenceFile !== null) {
|
||||
$storedCanceladoEvidence = cc_test_store_tracking_evidence_image($canceladoEvidenceFile, 'cancelado', $sourceKey, $actorUserId);
|
||||
$createdFiles[] = $storedCanceladoEvidence['absolute_path'];
|
||||
$canceladoEvidencePath = $storedCanceladoEvidence['relative_path'];
|
||||
$canceladoEvidenceUploadedAt = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
|
||||
$canceladoEvidenceUploadedBy = $actorUserId > 0 ? $actorUserId : null;
|
||||
}
|
||||
|
||||
$formData = [
|
||||
@ -524,10 +998,13 @@ try {
|
||||
'distrito' => $distrito,
|
||||
'coordenadas' => $coordenadas,
|
||||
'dni' => $dni,
|
||||
'numero_cuenta_sede_id' => $numeroCuentaSedeId,
|
||||
'numero_cuenta_dni' => $numeroCuentaDni,
|
||||
'observaciones' => $observaciones,
|
||||
'producto' => $producto,
|
||||
'cantidad' => $cantidad,
|
||||
'precio' => $precio,
|
||||
'monto_adelantado' => $montoAdelantado,
|
||||
'confirmacion_producto' => $confirmacionProducto,
|
||||
'confirmacion_cantidad' => $confirmacionCantidad,
|
||||
'confirmacion_precio' => $confirmacionPrecio,
|
||||
@ -554,10 +1031,13 @@ try {
|
||||
distrito,
|
||||
coordenadas,
|
||||
dni,
|
||||
numero_cuenta_sede_id,
|
||||
numero_cuenta_dni,
|
||||
observaciones,
|
||||
producto,
|
||||
cantidad,
|
||||
precio,
|
||||
monto_adelantado,
|
||||
confirmacion_producto,
|
||||
confirmacion_cantidad,
|
||||
confirmacion_precio,
|
||||
@ -567,6 +1047,12 @@ try {
|
||||
proxima_llamada_at,
|
||||
fecha_entrega_programada,
|
||||
numero_cuenta_enviado_at,
|
||||
promo_final_evidencia_path,
|
||||
promo_final_evidencia_subido_at,
|
||||
promo_final_evidencia_subido_por,
|
||||
cancelado_evidencia_path,
|
||||
cancelado_evidencia_subido_at,
|
||||
cancelado_evidencia_subido_por,
|
||||
ultima_gestion_at
|
||||
) VALUES (
|
||||
:source_key,
|
||||
@ -582,10 +1068,13 @@ try {
|
||||
:distrito,
|
||||
:coordenadas,
|
||||
:dni,
|
||||
:numero_cuenta_sede_id,
|
||||
:numero_cuenta_dni,
|
||||
:observaciones,
|
||||
:producto,
|
||||
:cantidad,
|
||||
:precio,
|
||||
:monto_adelantado,
|
||||
:confirmacion_producto,
|
||||
:confirmacion_cantidad,
|
||||
:confirmacion_precio,
|
||||
@ -595,6 +1084,12 @@ try {
|
||||
:proxima_llamada_at,
|
||||
:fecha_entrega_programada,
|
||||
:numero_cuenta_enviado_at,
|
||||
:promo_final_evidencia_path,
|
||||
:promo_final_evidencia_subido_at,
|
||||
:promo_final_evidencia_subido_por,
|
||||
:cancelado_evidencia_path,
|
||||
:cancelado_evidencia_subido_at,
|
||||
:cancelado_evidencia_subido_por,
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
@ -610,10 +1105,13 @@ try {
|
||||
distrito = VALUES(distrito),
|
||||
coordenadas = VALUES(coordenadas),
|
||||
dni = VALUES(dni),
|
||||
numero_cuenta_sede_id = VALUES(numero_cuenta_sede_id),
|
||||
numero_cuenta_dni = VALUES(numero_cuenta_dni),
|
||||
observaciones = VALUES(observaciones),
|
||||
producto = VALUES(producto),
|
||||
cantidad = VALUES(cantidad),
|
||||
precio = VALUES(precio),
|
||||
monto_adelantado = VALUES(monto_adelantado),
|
||||
confirmacion_producto = VALUES(confirmacion_producto),
|
||||
confirmacion_cantidad = VALUES(confirmacion_cantidad),
|
||||
confirmacion_precio = VALUES(confirmacion_precio),
|
||||
@ -623,6 +1121,12 @@ try {
|
||||
proxima_llamada_at = VALUES(proxima_llamada_at),
|
||||
fecha_entrega_programada = VALUES(fecha_entrega_programada),
|
||||
numero_cuenta_enviado_at = VALUES(numero_cuenta_enviado_at),
|
||||
promo_final_evidencia_path = VALUES(promo_final_evidencia_path),
|
||||
promo_final_evidencia_subido_at = VALUES(promo_final_evidencia_subido_at),
|
||||
promo_final_evidencia_subido_por = VALUES(promo_final_evidencia_subido_por),
|
||||
cancelado_evidencia_path = VALUES(cancelado_evidencia_path),
|
||||
cancelado_evidencia_subido_at = VALUES(cancelado_evidencia_subido_at),
|
||||
cancelado_evidencia_subido_por = VALUES(cancelado_evidencia_subido_por),
|
||||
ultima_gestion_at = CURRENT_TIMESTAMP,
|
||||
updated_at = CURRENT_TIMESTAMP'
|
||||
);
|
||||
@ -641,10 +1145,13 @@ try {
|
||||
':distrito' => $distrito,
|
||||
':coordenadas' => $coordenadas,
|
||||
':dni' => $dni,
|
||||
':numero_cuenta_sede_id' => $numeroCuentaSedeId,
|
||||
':numero_cuenta_dni' => $numeroCuentaDni,
|
||||
':observaciones' => $observaciones,
|
||||
':producto' => $producto,
|
||||
':cantidad' => $cantidad,
|
||||
':precio' => $precio,
|
||||
':monto_adelantado' => $montoAdelantado,
|
||||
':confirmacion_producto' => $confirmacionProducto,
|
||||
':confirmacion_cantidad' => $confirmacionCantidad,
|
||||
':confirmacion_precio' => $confirmacionPrecio,
|
||||
@ -654,6 +1161,12 @@ try {
|
||||
':proxima_llamada_at' => $proximaLlamada,
|
||||
':fecha_entrega_programada' => $fechaEntrega,
|
||||
':numero_cuenta_enviado_at' => $numeroCuentaEnviadoAt,
|
||||
':promo_final_evidencia_path' => $promoFinalEvidencePath,
|
||||
':promo_final_evidencia_subido_at' => $promoFinalEvidenceUploadedAt,
|
||||
':promo_final_evidencia_subido_por' => $promoFinalEvidenceUploadedBy,
|
||||
':cancelado_evidencia_path' => $canceladoEvidencePath,
|
||||
':cancelado_evidencia_subido_at' => $canceladoEvidenceUploadedAt,
|
||||
':cancelado_evidencia_subido_por' => $canceladoEvidenceUploadedBy,
|
||||
]);
|
||||
|
||||
$rutaPedidoId = isset($currentTracking['ruta_contraentrega_pedido_id']) && (int) $currentTracking['ruta_contraentrega_pedido_id'] > 0
|
||||
@ -661,48 +1174,91 @@ try {
|
||||
: null;
|
||||
$rutaSubidoAt = null;
|
||||
$rutaAction = null;
|
||||
$pedidoRotuladoId = isset($currentTracking['pedido_rotulado_pedido_id']) && (int) $currentTracking['pedido_rotulado_pedido_id'] > 0
|
||||
? (int) $currentTracking['pedido_rotulado_pedido_id']
|
||||
: null;
|
||||
$pedidoRotuladoSubidoAt = null;
|
||||
$pedidoRotuladoAction = null;
|
||||
|
||||
if ($subirARuta) {
|
||||
if ($subirALogistica) {
|
||||
$sourceOrder = cc_test_fetch_source_order($pdo, $sourceKey);
|
||||
if (!$sourceOrder) {
|
||||
throw new RuntimeException('No encontré el pedido base para enviarlo a Ruta Contraentrega.');
|
||||
throw new RuntimeException('No encontré el pedido base para enviarlo a logística.');
|
||||
}
|
||||
|
||||
$routePayload = cc_test_prepare_route_order_payload(
|
||||
$sourceOrder,
|
||||
$formData,
|
||||
(int) ($userIdToSet ?: ($_SESSION['user_id'] ?? 0))
|
||||
);
|
||||
if ($estado === 'CONFIRMADO CONTRAENTREGA') {
|
||||
$routePayload = cc_test_prepare_route_order_payload(
|
||||
$sourceOrder,
|
||||
$formData,
|
||||
(int) ($userIdToSet ?: ($_SESSION['user_id'] ?? 0))
|
||||
);
|
||||
|
||||
$routeSync = cc_test_sync_route_order($pdo, $rutaPedidoId, $routePayload);
|
||||
$rutaPedidoId = (int) $routeSync['pedido_id'];
|
||||
$rutaAction = $routeSync['action'];
|
||||
$rutaSubidoAt = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
|
||||
$routeSync = cc_test_sync_route_order($pdo, $rutaPedidoId, $routePayload);
|
||||
$rutaPedidoId = (int) $routeSync['pedido_id'];
|
||||
$rutaAction = $routeSync['action'];
|
||||
$rutaSubidoAt = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
|
||||
|
||||
$stmtRuta = $pdo->prepare(
|
||||
'UPDATE callcenter_test_tracking
|
||||
SET ruta_contraentrega_pedido_id = :pedido_id,
|
||||
ruta_contraentrega_subido_at = :subido_at,
|
||||
ruta_contraentrega_subido_por = :subido_por,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE source_key = :source_key'
|
||||
);
|
||||
$stmtRuta->execute([
|
||||
':pedido_id' => $rutaPedidoId,
|
||||
':subido_at' => $rutaSubidoAt,
|
||||
':subido_por' => (int) ($_SESSION['user_id'] ?? 0),
|
||||
':source_key' => $sourceKey,
|
||||
]);
|
||||
$stmtRuta = $pdo->prepare(
|
||||
'UPDATE callcenter_test_tracking
|
||||
SET ruta_contraentrega_pedido_id = :pedido_id,
|
||||
ruta_contraentrega_subido_at = :subido_at,
|
||||
ruta_contraentrega_subido_por = :subido_por,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE source_key = :source_key'
|
||||
);
|
||||
$stmtRuta->execute([
|
||||
':pedido_id' => $rutaPedidoId,
|
||||
':subido_at' => $rutaSubidoAt,
|
||||
':subido_por' => (int) ($_SESSION['user_id'] ?? 0),
|
||||
':source_key' => $sourceKey,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($estado === 'CONFIRMADO ENVIO') {
|
||||
$rotuladoPayload = cc_test_prepare_rotulado_order_payload(
|
||||
$sourceOrder,
|
||||
$formData,
|
||||
(int) ($userIdToSet ?: ($_SESSION['user_id'] ?? 0))
|
||||
);
|
||||
|
||||
$rotuladoSync = cc_test_sync_rotulado_order($pdo, $pedidoRotuladoId, $rotuladoPayload);
|
||||
$pedidoRotuladoId = (int) $rotuladoSync['pedido_id'];
|
||||
$pedidoRotuladoAction = $rotuladoSync['action'];
|
||||
$pedidoRotuladoSubidoAt = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
|
||||
|
||||
$stmtRotulado = $pdo->prepare(
|
||||
'UPDATE callcenter_test_tracking
|
||||
SET pedido_rotulado_pedido_id = :pedido_id,
|
||||
pedido_rotulado_subido_at = :subido_at,
|
||||
pedido_rotulado_subido_por = :subido_por,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE source_key = :source_key'
|
||||
);
|
||||
$stmtRotulado->execute([
|
||||
':pedido_id' => $pedidoRotuladoId,
|
||||
':subido_at' => $pedidoRotuladoSubidoAt,
|
||||
':subido_por' => (int) ($_SESSION['user_id'] ?? 0),
|
||||
':source_key' => $sourceKey,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
$message = 'Gestión actualizada correctamente.';
|
||||
if ($subirARuta && $rutaPedidoId !== null) {
|
||||
if ($moverAPromoFinal && $promoFinalEvidencePath !== null) {
|
||||
$message = 'Pedido movido a Promo Final correctamente.';
|
||||
}
|
||||
if ($subirALogistica && $estado === 'CONFIRMADO CONTRAENTREGA' && $rutaPedidoId !== null) {
|
||||
$message = $rutaAction === 'updated'
|
||||
? 'Pedido actualizado en Ruta Contraentrega #' . $rutaPedidoId . '.'
|
||||
: 'Pedido subido a Ruta Contraentrega #' . $rutaPedidoId . '.';
|
||||
}
|
||||
if ($subirALogistica && $estado === 'CONFIRMADO ENVIO' && $pedidoRotuladoId !== null) {
|
||||
$message = $pedidoRotuladoAction === 'updated'
|
||||
? 'Pedido actualizado en Pedidos Rotulados #' . $pedidoRotuladoId . '.'
|
||||
: 'Pedido subido a Pedidos Rotulados #' . $pedidoRotuladoId . '.';
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
@ -711,14 +1267,28 @@ try {
|
||||
'proxima_llamada_at' => $proximaLlamada,
|
||||
'fecha_entrega_programada' => $fechaEntrega,
|
||||
'numero_cuenta_enviado_at' => $numeroCuentaEnviadoAt,
|
||||
'numero_cuenta_sede_id' => $numeroCuentaSedeId,
|
||||
'numero_cuenta_dni' => $numeroCuentaDni,
|
||||
'promo_final_evidencia_path' => $promoFinalEvidencePath,
|
||||
'promo_final_evidencia_subido_at' => $promoFinalEvidenceUploadedAt,
|
||||
'cancelado_evidencia_path' => $canceladoEvidencePath,
|
||||
'cancelado_evidencia_subido_at' => $canceladoEvidenceUploadedAt,
|
||||
'ruta_contraentrega_pedido_id' => $rutaPedidoId,
|
||||
'ruta_contraentrega_subido_at' => $rutaSubidoAt,
|
||||
'pedido_rotulado_pedido_id' => $pedidoRotuladoId,
|
||||
'pedido_rotulado_subido_at' => $pedidoRotuladoSubidoAt,
|
||||
]);
|
||||
} catch (Throwable $exception) {
|
||||
if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
foreach ($createdFiles as $createdFile) {
|
||||
if (is_string($createdFile) && $createdFile !== '' && file_exists($createdFile)) {
|
||||
@unlink($createdFile);
|
||||
}
|
||||
}
|
||||
|
||||
http_response_code(500);
|
||||
error_log('update_callcenter_test_tracking.php: ' . $exception->getMessage());
|
||||
echo json_encode([
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user