34849-vm/includes/callcenter_test_helpers.php
2026-07-15 05:59:10 +00:00

790 lines
28 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
function cc_test_ensure_column(PDO $pdo, string $table, string $column, string $definition): void
{
static $cache = [];
$key = $table . '.' . $column;
if (isset($cache[$key])) {
return;
}
$stmt = $pdo->prepare("SHOW COLUMNS FROM `{$table}` LIKE ?");
$stmt->execute([$column]);
if (!$stmt->fetch()) {
$pdo->exec("ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition}");
}
$cache[$key] = true;
}
function cc_test_column_exists(PDO $pdo, string $table, string $column): bool
{
static $cache = [];
$key = $table . '.' . $column;
if (array_key_exists($key, $cache)) {
return $cache[$key];
}
try {
$stmt = $pdo->prepare("SHOW COLUMNS FROM `{$table}` LIKE ?");
$stmt->execute([$column]);
$cache[$key] = (bool) $stmt->fetch(PDO::FETCH_ASSOC);
} catch (Throwable $exception) {
$cache[$key] = false;
}
return $cache[$key];
}
function cc_test_normalize_hex_color(?string $value): ?string
{
$value = strtoupper(trim((string) $value));
if ($value === '') {
return null;
}
if ($value[0] !== '#') {
$value = '#' . $value;
}
return preg_match('/^#[0-9A-F]{6}$/', $value) ? $value : null;
}
function cc_test_hex_to_rgb_triplet(string $hex): ?array
{
$normalized = cc_test_normalize_hex_color($hex);
if ($normalized === null) {
return null;
}
$normalized = ltrim($normalized, '#');
return [
hexdec(substr($normalized, 0, 2)),
hexdec(substr($normalized, 2, 2)),
hexdec(substr($normalized, 4, 2)),
];
}
function cc_test_assessor_default_color_hex(string $assessorKey): string
{
return match (cc_test_normalize_user_key($assessorKey)) {
'KARINA' => '#0D6EFD',
'ESTEFANYA' => '#198754',
'CARMEN' => '#FF00FF',
default => '#6C757D',
};
}
function cc_test_assessor_effective_color_hex(string $assessorKey, ?string $storedColorHex = null): string
{
return cc_test_normalize_hex_color($storedColorHex) ?? cc_test_assessor_default_color_hex($assessorKey);
}
function cc_test_hex_relative_luminance(string $hex): float
{
$rgb = cc_test_hex_to_rgb_triplet($hex) ?? [108, 117, 125];
$linear = array_map(
static function (int $channel): float {
$value = $channel / 255;
return $value <= 0.03928
? $value / 12.92
: pow(($value + 0.055) / 1.055, 2.4);
},
$rgb
);
return (0.2126 * $linear[0]) + (0.7152 * $linear[1]) + (0.0722 * $linear[2]);
}
function cc_test_assessor_contrast_text_hex(string $hex): string
{
static $darkText = '#212529';
static $darkLuminance = null;
if ($darkLuminance === null) {
$darkLuminance = cc_test_hex_relative_luminance($darkText);
}
$luminance = cc_test_hex_relative_luminance($hex);
$contrastWithWhite = 1.05 / ($luminance + 0.05);
$contrastWithDark = ($luminance + 0.05) / ($darkLuminance + 0.05);
return $contrastWithDark >= $contrastWithWhite ? $darkText : '#FFFFFF';
}
function cc_test_assessor_css_vars(string $assessorKey, ?string $storedColorHex = null): string
{
$hex = cc_test_assessor_effective_color_hex($assessorKey, $storedColorHex);
$rgb = cc_test_hex_to_rgb_triplet($hex) ?? [108, 117, 125];
return '--cc-assessor-accent:' . $hex . '; --cc-assessor-accent-rgb:' . implode(', ', $rgb) . '; --cc-assessor-accent-contrast:' . cc_test_assessor_contrast_text_hex($hex) . ';';
}
function cc_test_ensure_tracking_table(PDO $pdo): void
{
static $checked = false;
if ($checked) {
return;
}
$pdo->exec("CREATE TABLE IF NOT EXISTS `callcenter_test_tracking` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`source_key` CHAR(40) NOT NULL,
`estado` VARCHAR(40) NOT NULL DEFAULT 'POR LLAMAR',
`nota_seguimiento` TEXT NULL,
`user_id` INT NULL,
`assigned_at` DATETIME NULL,
`direccion` TEXT NULL,
`referencia` TEXT NULL,
`agencia` VARCHAR(80) NULL,
`sede_agencia` VARCHAR(120) NULL,
`sede` VARCHAR(120) NULL,
`ciudad` VARCHAR(120) NULL,
`distrito` VARCHAR(120) NULL,
`dni` VARCHAR(40) NULL,
`observaciones` TEXT NULL,
`coordenadas` VARCHAR(255) NULL,
`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,
`confirmacion_producto_extra` VARCHAR(255) NULL,
`confirmacion_cantidad_extra` VARCHAR(50) NULL,
`confirmacion_precio_extra` VARCHAR(80) NULL,
`proxima_llamada_at` DATETIME NULL,
`fecha_entrega_programada` DATE NULL,
`numero_cuenta_enviado_at` DATETIME NULL,
`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,
`eliminado_at` DATETIME NULL,
`eliminado_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,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_callcenter_test_tracking_source_key` (`source_key`),
KEY `idx_callcenter_test_tracking_estado` (`estado`),
KEY `idx_callcenter_test_tracking_proxima` (`proxima_llamada_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'assigned_at', 'DATETIME NULL AFTER `user_id`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'direccion', 'TEXT NULL AFTER `user_id`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'referencia', 'TEXT NULL AFTER `direccion`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'agencia', 'VARCHAR(80) NULL AFTER `referencia`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'sede_agencia', 'VARCHAR(120) NULL AFTER `agencia`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'sede', 'VARCHAR(120) NULL AFTER `sede_agencia`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ciudad', 'VARCHAR(120) NULL AFTER `sede`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'distrito', 'VARCHAR(120) NULL AFTER `ciudad`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'dni', 'VARCHAR(40) NULL AFTER `distrito`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'observaciones', 'TEXT NULL AFTER `dni`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'coordenadas', 'VARCHAR(255) NULL AFTER `observaciones`');
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`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_producto_extra', 'VARCHAR(255) NULL AFTER `confirmacion_precio`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_cantidad_extra', 'VARCHAR(50) NULL AFTER `confirmacion_producto_extra`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'confirmacion_precio_extra', 'VARCHAR(80) NULL AFTER `confirmacion_cantidad_extra`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'proxima_llamada_at', 'DATETIME NULL AFTER `confirmacion_precio`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'fecha_entrega_programada', 'DATE NULL AFTER `proxima_llamada_at`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'numero_cuenta_enviado_at', 'DATETIME NULL AFTER `fecha_entrega_programada`');
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', 'eliminado_at', 'DATETIME NULL AFTER `cancelado_evidencia_subido_por`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'eliminado_por', 'INT NULL AFTER `eliminado_at`');
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ruta_contraentrega_pedido_id', 'INT NULL AFTER `eliminado_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`');
$stmtAssignedBackfill = $pdo->query("SELECT id FROM callcenter_test_tracking WHERE assigned_at IS NULL AND user_id IS NOT NULL LIMIT 1");
if ($stmtAssignedBackfill && $stmtAssignedBackfill->fetchColumn()) {
$pdo->exec("UPDATE callcenter_test_tracking SET assigned_at = created_at, updated_at = updated_at WHERE assigned_at IS NULL AND user_id IS NOT NULL");
}
$checked = true;
}
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();
}
$hasColorColumn = cc_test_column_exists($pdo, 'users', 'color_hex');
$selectColorColumn = $hasColorColumn ? ', color_hex' : ', NULL AS color_hex';
$stmt = $pdo->prepare("SELECT id, username, nombre_asesor{$selectColorColumn} FROM users WHERE role = 'Asesor'");
$stmt->execute();
$allowedLookup = array_fill_keys(array_map('strtoupper', $allowedNames), true);
$byKey = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$key = trim(mb_strtoupper((string) ($row['nombre_asesor'] ?: $row['username'] ?: '')));
$key = preg_replace('/\s+/', ' ', $key) ?? $key;
if ($key === '' || (!empty($allowedLookup) && !isset($allowedLookup[$key]))) {
continue;
}
$byKey[$key] = [
'id' => (int) $row['id'],
'label' => trim((string) ($row['nombre_asesor'] ?: $row['username'] ?: ('Asesor #' . (int) $row['id']))),
'color_hex' => cc_test_normalize_hex_color($row['color_hex'] ?? null),
];
}
return $byKey;
}
function cc_test_normalize_user_key(?string $value): string
{
$value = trim((string) $value);
if ($value === '') {
return '';
}
$value = preg_replace('/\s+/', ' ', $value) ?? $value;
return mb_strtoupper($value);
}
function cc_test_allowed_module_user_keys(): array
{
return cc_test_default_assessor_keys();
}
function cc_test_is_allowed_module_user(?string $role = null, ?string $username = null, ?string $nombreAsesor = null): bool
{
$role = trim((string) $role);
if (in_array($role, ['Administrador', 'admin'], true)) {
return true;
}
$allowedLookup = array_fill_keys(cc_test_allowed_module_user_keys(), true);
foreach ([$username, $nombreAsesor] as $candidate) {
$key = cc_test_normalize_user_key($candidate);
if ($key !== '' && isset($allowedLookup[$key])) {
return true;
}
}
return false;
}
function cc_test_current_user_can_access_module(?PDO $pdo = null): bool
{
$role = $_SESSION['user_role'] ?? '';
$username = $_SESSION['username'] ?? '';
$nombreAsesor = null;
if ($pdo instanceof PDO && !empty($_SESSION['user_id']) && !in_array($role, ['Administrador', 'admin'], true)) {
try {
$stmt = $pdo->prepare('SELECT username, nombre_asesor FROM users WHERE id = ? LIMIT 1');
$stmt->execute([$_SESSION['user_id']]);
if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$username = $row['username'] ?? $username;
$nombreAsesor = $row['nombre_asesor'] ?? null;
}
} catch (Throwable $exception) {
// Fall back to the session values if the lookup fails.
}
}
return cc_test_is_allowed_module_user($role, $username, $nombreAsesor);
}
function cc_test_upsert_assignee(PDO $pdo, string $sourceKey, ?int $userId): void
{
cc_test_ensure_tracking_table($pdo);
$assignedAt = $userId === null ? null : (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
$stmt = $pdo->prepare("INSERT INTO callcenter_test_tracking (source_key, user_id, assigned_at, ultima_gestion_at)
VALUES (:source_key, :user_id, :assigned_at, CURRENT_TIMESTAMP)
ON DUPLICATE KEY UPDATE
user_id = VALUES(user_id),
assigned_at = CASE
WHEN VALUES(user_id) IS NULL THEN NULL
WHEN assigned_at IS NULL OR user_id IS NULL OR user_id <> VALUES(user_id) THEN VALUES(assigned_at)
ELSE assigned_at
END,
ultima_gestion_at = VALUES(ultima_gestion_at),
updated_at = CURRENT_TIMESTAMP");
$stmt->bindValue(':source_key', $sourceKey, PDO::PARAM_STR);
if ($userId === null) {
$stmt->bindValue(':user_id', null, PDO::PARAM_NULL);
$stmt->bindValue(':assigned_at', null, PDO::PARAM_NULL);
} else {
$stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
$stmt->bindValue(':assigned_at', $assignedAt, PDO::PARAM_STR);
}
$stmt->execute();
}
function cc_test_ensure_historial_llamadas_table(PDO $pdo): void
{
static $checked = false;
if ($checked) {
return;
}
$pdo->exec("CREATE TABLE IF NOT EXISTS `historial_llamadas` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`pedido_id` VARCHAR(80) NOT NULL,
`asesor_id` INT UNSIGNED NOT NULL,
`resultado` VARCHAR(120) NOT NULL,
`observacion` TEXT NULL,
`fecha_llamada` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_historial_llamadas_pedido` (`pedido_id`),
INDEX `idx_historial_llamadas_asesor` (`asesor_id`),
INDEX `idx_historial_llamadas_fecha` (`fecha_llamada`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
$checked = true;
}
function cc_test_normalize_state(string $estado): string
{
$estado = trim($estado);
return match ($estado) {
'CONFIRMADO CONTRAENTREGA FECHA', 'CONFIRMADO FECHA', 'CONTRAENTREGA CONFIRMADO' => 'CONFIRMADO CONTRAENTREGA',
'ENVIO REPETIDO' => 'REPETIDO',
default => $estado,
};
}
function cc_test_valid_states(): array
{
return [
'POR LLAMAR',
'DEVOLVER LLAMADA',
'OBSERVADO',
'SE ENVIO NUMERO DE CUENTA',
'CONFIRMADO CONTRAENTREGA',
'CONFIRMADO ENVIO',
'CANCELADO',
'REPETIDO',
];
}
if (!function_exists('cc_test_open_states')) {
function cc_test_open_states(): array
{
return ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'];
}
}
function cc_test_confirmed_states(): array
{
return ['CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO'];
}
if (!function_exists('cc_test_closed_states')) {
function cc_test_closed_states(): array
{
return ['CANCELADO', 'REPETIDO'];
}
}
function cc_test_requires_delivery_date(string $estado): bool
{
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);
if (!$date) {
return null;
}
$now = new DateTimeImmutable('now');
$days = (int) $date->diff($now)->format('%a');
if ($date > $now) {
$days = 0;
}
if ($days <= 1) {
return [
'class' => 'bg-success-subtle text-success-emphasis',
'label' => 'Verde',
'range' => '01 días',
'days' => $days,
'description' => 'Aún está en ventana de seguimiento amable.',
];
}
if ($days === 2) {
return [
'class' => 'bg-warning-subtle text-warning-emphasis',
'label' => 'Amarillo',
'range' => '2 días',
'days' => $days,
'description' => 'Ya conviene insistir con llamada o WhatsApp.',
];
}
return [
'class' => 'bg-danger-subtle text-danger-emphasis',
'label' => 'Rojo',
'range' => '3+ días',
'days' => $days,
'description' => 'Necesita seguimiento urgente para no perder el pedido.',
];
}
function cc_test_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)) {
'SE ENVIO NUMERO DE CUENTA' => 'SE ENVIÓ NÚMERO DE CUENTA',
'CONFIRMADO CONTRAENTREGA' => 'CONFIRMADO CONTRAENTREGA',
'CONFIRMADO ENVIO' => 'CONFIRMADO ENVIO',
'REPETIDO' => 'REPETIDO',
default => $estado,
};
}
function cc_test_normalize_contraentrega_coordinates(string $value): ?string
{
$value = trim($value);
if ($value === '') {
return null;
}
if (!preg_match('/^\s*(-?\d{1,2}\.\d{6,})\s*,\s*(-?\d{1,3}\.\d{6,})\s*$/', $value, $matches)) {
return null;
}
$latitud = (float) $matches[1];
$longitud = (float) $matches[2];
if ($latitud < -90 || $latitud > 90 || $longitud < -180 || $longitud > 180) {
return null;
}
return $matches[1] . ', ' . $matches[2];
}
function cc_test_parse_quantity(?string $value): ?int
{
$value = trim((string) $value);
if ($value === '') {
return null;
}
if (!preg_match('/-?\d+/', $value, $matches)) {
return null;
}
$cantidad = (int) $matches[0];
if ($cantidad <= 0) {
return null;
}
return $cantidad;
}
function cc_test_parse_amount(?string $value): ?float
{
$value = trim((string) $value);
if ($value === '') {
return null;
}
$value = str_ireplace(['S/', 'USD', '$'], '', $value);
$value = preg_replace('/\s+/', '', $value) ?? $value;
if (strpos($value, ',') !== false && strpos($value, '.') === false) {
$value = str_replace(',', '.', $value);
} else {
$value = str_replace(',', '', $value);
}
$value = preg_replace('/[^0-9.\-]/', '', $value) ?? $value;
if ($value === '' || !is_numeric($value)) {
return null;
}
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
{
$color = match (cc_test_normalize_state($estado)) {
// Blanco
'POR LLAMAR', 'DEVOLVER LLAMADA' => '#ffffff',
// Amarillo
'OBSERVADO' => '#FFFF00',
// Verde brillante
'SE ENVIO NUMERO DE CUENTA' => '#00FF00',
// Naranja
'CONFIRMADO CONTRAENTREGA' => '#ffb74d',
// Verde Google
'CONFIRMADO ENVIO' => '#34A853',
// Rojo
'CANCELADO' => '#FF0000',
// Plomo
'REPETIDO' => '#CCCCCC',
default => '#ffffff',
};
// We set a CSS variable so we can paint the entire row (all <td> cells).
// Bootstrap/table styles often apply opaque backgrounds to <td>, which can hide
// the <tr> background. A CSS rule below will apply this variable to every cell.
return '--cc-row-bg:' . $color . ';';
}
if (!function_exists('cc_test_table_exists')) {
function cc_test_table_exists(PDO $pdo, string $tableName): bool
{
static $cache = [];
if (array_key_exists($tableName, $cache)) {
return $cache[$tableName];
}
$stmt = $pdo->prepare('SHOW TABLES LIKE ?');
$stmt->execute([$tableName]);
$cache[$tableName] = (bool) $stmt->fetchColumn();
return $cache[$tableName];
}
}
function cc_test_fetch_historial(PDO $pdo, string $pedidoId): array
{
if (cc_test_table_exists($pdo, 'usuarios')) {
$stmt = $pdo->prepare("SELECT h.*, u.nombre as asesor FROM historial_llamadas h LEFT JOIN usuarios u ON h.asesor_id = u.id WHERE h.pedido_id = ? ORDER BY h.fecha_llamada DESC");
$stmt->execute([$pedidoId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
$stmt = $pdo->prepare("SELECT h.*, NULL as asesor FROM historial_llamadas h WHERE h.pedido_id = ? ORDER BY h.fecha_llamada DESC");
$stmt->execute([$pedidoId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}