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_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, `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, `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', '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', '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_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(); $allowedLookup = array_fill_keys(array_map('strtoupper', $allowedNames), true); $byKey = []; foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { $key = trim(mb_strtoupper((string) ($row['nombre_asesor'] ?: $row['username'] ?: ''))); $key = preg_replace('/\s+/', ' ', $key) ?? $key; if ($key === '' || (!empty($allowedLookup) && !isset($allowedLookup[$key]))) { continue; } $byKey[$key] = [ 'id' => (int) $row['id'], 'label' => trim((string) ($row['nombre_asesor'] ?: $row['username'] ?: ('Asesor #' . (int) $row['id']))), ]; } return $byKey; } function cc_test_normalize_user_key(?string $value): string { $value = trim((string) $value); if ($value === '') { return ''; } $value = preg_replace('/\s+/', ' ', $value) ?? $value; return mb_strtoupper($value); } function cc_test_allowed_module_user_keys(): array { return 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); $stmt = $pdo->prepare("INSERT INTO callcenter_test_tracking (source_key, user_id, ultima_gestion_at) VALUES (:source_key, :user_id, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE user_id = VALUES(user_id), ultima_gestion_at = VALUES(ultima_gestion_at), updated_at = CURRENT_TIMESTAMP"); $stmt->bindValue(':source_key', $sourceKey, PDO::PARAM_STR); if ($userId === null) { $stmt->bindValue(':user_id', null, PDO::PARAM_NULL); } else { $stmt->bindValue(':user_id', $userId, PDO::PARAM_INT); } $stmt->execute(); } function cc_test_ensure_historial_llamadas_table(PDO $pdo): void { static $checked = false; 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' => '0–1 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' => '#fff3cd', // Verde claro 'SE ENVIO NUMERO DE CUENTA' => '#d1e7dd', // Naranja 'CONFIRMADO CONTRAENTREGA' => '#ffb74d', // Verde 'CONFIRMADO ENVIO' => '#b6f1c7', // Rojo 'CANCELADO' => '#f8d7da', // Plomo 'REPETIDO' => '#d6d8db', default => '#ffffff', }; // We set a CSS variable so we can paint the entire row (all cells). // Bootstrap/table styles often apply opaque backgrounds to , which can hide // the 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); }