'bg-success-subtle text-success-emphasis', 'DEVOLVER LLAMADA', 'REPROGRAMADO' => 'bg-info-subtle text-info-emphasis', 'NO CONTESTA' => 'bg-secondary-subtle text-secondary-emphasis', 'NO INTERESADO', 'NUMERO EQUIVOCADO', 'DUPLICADO' => 'bg-danger-subtle text-danger-emphasis', default => 'bg-warning-subtle text-warning-emphasis', }; } function cc_test_display_value(?string $value, string $fallback = 'No registrado'): string { $value = trim((string) $value); return $value !== '' ? $value : $fallback; } function cc_test_format_price(?string $value): string { $value = trim((string) $value); return $value !== '' ? 'S/ ' . $value : 'No registrado'; } function cc_test_parse_datetime(?string $value): ?string { $value = trim((string) $value); if ($value === '') { return null; } $formats = [ 'Y-m-d\\TH:i', 'Y-m-d H:i:s', 'Y-m-d H:i', DateTimeInterface::ATOM, 'Y-m-d', ]; foreach ($formats as $format) { $date = DateTimeImmutable::createFromFormat($format, $value); if ($date instanceof DateTimeImmutable) { return $date->format('Y-m-d H:i:s'); } } try { return (new DateTimeImmutable($value))->format('Y-m-d H:i:s'); } catch (Throwable $exception) { return null; } } function cc_test_format_datetime(?string $value, string $fallback = 'Sin programar'): string { $parsed = cc_test_parse_datetime($value); if ($parsed === null) { return $fallback; } try { return (new DateTimeImmutable($parsed))->format('d/m/Y h:i A'); } catch (Throwable $exception) { return $fallback; } } function cc_test_format_datetime_local(?string $value): string { $parsed = cc_test_parse_datetime($value); if ($parsed === null) { return ''; } try { return (new DateTimeImmutable($parsed))->format('Y-m-d\\TH:i'); } catch (Throwable $exception) { return ''; } } 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_column_exists(PDO $pdo, string $tableName, string $columnName): bool { static $cache = []; $cacheKey = $tableName . '.' . $columnName; if (array_key_exists($cacheKey, $cache)) { return $cache[$cacheKey]; } $stmt = $pdo->prepare("SHOW COLUMNS FROM `{$tableName}` LIKE ?"); $stmt->execute([$columnName]); $cache[$cacheKey] = (bool) $stmt->fetchColumn(); return $cache[$cacheKey]; } function cc_test_add_column_if_missing(PDO $pdo, string $tableName, string $columnName, string $definition): void { if (!cc_test_column_exists($pdo, $tableName, $columnName)) { $pdo->exec("ALTER TABLE `{$tableName}` ADD COLUMN `{$columnName}` {$definition}"); } } function cc_test_ensure_schema(PDO $pdo): void { static $checked = false; if ($checked) { return; } $pdo->exec("CREATE TABLE IF NOT EXISTS `callcenter_test_orders` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `source_key` CHAR(40) NOT NULL, `codigo` VARCHAR(80) DEFAULT NULL, `import_id` VARCHAR(120) DEFAULT NULL, `drive_imported_at` DATETIME NULL, `nombre` VARCHAR(255) DEFAULT NULL, `direccion_drive` TEXT NULL, `referencia_drive` TEXT NULL, `sede_drive` VARCHAR(120) NULL, `ciudad_drive` VARCHAR(120) NULL, `distrito_drive` VARCHAR(120) NULL, `dni_drive` VARCHAR(40) NULL, `observaciones_drive` TEXT NULL, `celular` VARCHAR(40) DEFAULT NULL, `producto` TEXT NULL, `cantidad` VARCHAR(60) NULL, `precio` VARCHAR(60) NULL, `pais` VARCHAR(80) NULL, `coordenadas` VARCHAR(255) NULL, `metodo` VARCHAR(120) NULL, `first_seen_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `last_seen_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY `uniq_callcenter_test_orders_source_key` (`source_key`), KEY `idx_callcenter_test_orders_drive_imported_at` (`drive_imported_at`), KEY `idx_callcenter_test_orders_last_seen_at` (`last_seen_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); $pdo->exec("CREATE TABLE IF NOT EXISTS `callcenter_test_tracking` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `source_key` CHAR(40) NOT NULL, `estado` VARCHAR(40) NOT NULL DEFAULT 'POR LLAMAR', `nota_seguimiento` TEXT NULL, `proxima_llamada` DATETIME NULL, `fecha_ultimo_contacto` DATETIME NULL, `direccion_editada` TEXT NULL, `referencia_editada` TEXT NULL, `sede_editada` VARCHAR(120) NULL, `ciudad_editada` VARCHAR(120) NULL, `distrito_editado` VARCHAR(120) NULL, `dni_editado` VARCHAR(40) NULL, `observaciones_editadas` TEXT NULL, `user_id` INT NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); cc_test_add_column_if_missing($pdo, 'callcenter_test_tracking', 'proxima_llamada', 'DATETIME NULL AFTER `nota_seguimiento`'); cc_test_add_column_if_missing($pdo, 'callcenter_test_tracking', 'fecha_ultimo_contacto', 'DATETIME NULL AFTER `proxima_llamada`'); cc_test_add_column_if_missing($pdo, 'callcenter_test_tracking', 'direccion_editada', 'TEXT NULL AFTER `fecha_ultimo_contacto`'); cc_test_add_column_if_missing($pdo, 'callcenter_test_tracking', 'referencia_editada', 'TEXT NULL AFTER `direccion_editada`'); cc_test_add_column_if_missing($pdo, 'callcenter_test_tracking', 'sede_editada', 'VARCHAR(120) NULL AFTER `referencia_editada`'); cc_test_add_column_if_missing($pdo, 'callcenter_test_tracking', 'ciudad_editada', 'VARCHAR(120) NULL AFTER `sede_editada`'); cc_test_add_column_if_missing($pdo, 'callcenter_test_tracking', 'distrito_editado', 'VARCHAR(120) NULL AFTER `ciudad_editada`'); cc_test_add_column_if_missing($pdo, 'callcenter_test_tracking', 'dni_editado', 'VARCHAR(40) NULL AFTER `distrito_editado`'); cc_test_add_column_if_missing($pdo, 'callcenter_test_tracking', 'observaciones_editadas', 'TEXT NULL AFTER `dni_editado`'); if (cc_test_table_exists($pdo, 'historial_llamadas')) { cc_test_add_column_if_missing($pdo, 'historial_llamadas', 'pedido_id', 'VARCHAR(80) NULL'); cc_test_add_column_if_missing($pdo, 'historial_llamadas', 'asesor_id', 'INT NULL'); cc_test_add_column_if_missing($pdo, 'historial_llamadas', 'resultado', 'VARCHAR(120) NULL'); cc_test_add_column_if_missing($pdo, 'historial_llamadas', 'observacion', 'TEXT NULL'); cc_test_add_column_if_missing($pdo, 'historial_llamadas', 'fecha_llamada', 'DATETIME NULL DEFAULT CURRENT_TIMESTAMP'); } else { $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 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_sync_orders_from_drive(PDO $pdo, int $limit = 250): array { cc_test_ensure_schema($pdo); $preview = drive_test_fetch_orders($limit); $orders = $preview['orders'] ?? []; if (empty($orders)) { return [ 'synced_count' => 0, 'total_rows' => (int) ($preview['total_rows'] ?? 0), ]; } $stmt = $pdo->prepare( 'INSERT INTO callcenter_test_orders ( source_key, codigo, import_id, drive_imported_at, nombre, direccion_drive, referencia_drive, sede_drive, ciudad_drive, distrito_drive, dni_drive, observaciones_drive, celular, producto, cantidad, precio, pais, coordenadas, metodo ) VALUES ( :source_key, :codigo, :import_id, :drive_imported_at, :nombre, :direccion_drive, :referencia_drive, :sede_drive, :ciudad_drive, :distrito_drive, :dni_drive, :observaciones_drive, :celular, :producto, :cantidad, :precio, :pais, :coordenadas, :metodo ) ON DUPLICATE KEY UPDATE codigo = VALUES(codigo), import_id = VALUES(import_id), drive_imported_at = VALUES(drive_imported_at), nombre = VALUES(nombre), direccion_drive = VALUES(direccion_drive), referencia_drive = VALUES(referencia_drive), sede_drive = VALUES(sede_drive), ciudad_drive = VALUES(ciudad_drive), distrito_drive = VALUES(distrito_drive), dni_drive = VALUES(dni_drive), observaciones_drive = VALUES(observaciones_drive), celular = VALUES(celular), producto = VALUES(producto), cantidad = VALUES(cantidad), precio = VALUES(precio), pais = VALUES(pais), coordenadas = VALUES(coordenadas), metodo = VALUES(metodo), last_seen_at = CURRENT_TIMESTAMP' ); foreach ($orders as $order) { $stmt->execute([ ':source_key' => $order['source_key'], ':codigo' => $order['codigo'] ?: null, ':import_id' => $order['import_id'] ?: null, ':drive_imported_at' => cc_test_parse_datetime($order['import_id'] ?? ''), ':nombre' => $order['nombre'] ?: null, ':direccion_drive' => $order['direccion'] ?: null, ':referencia_drive' => $order['referencia'] ?: null, ':sede_drive' => $order['sede'] ?: null, ':ciudad_drive' => $order['ciudad'] ?: null, ':distrito_drive' => $order['distrito'] ?: null, ':dni_drive' => $order['dni'] ?: null, ':observaciones_drive' => $order['observaciones'] ?: null, ':celular' => $order['celular'] ?: null, ':producto' => $order['producto'] ?: null, ':cantidad' => $order['cantidad'] ?: null, ':precio' => $order['precio'] ?: null, ':pais' => $order['pais'] ?: null, ':coordenadas' => $order['coordenadas'] ?: null, ':metodo' => $order['metodo'] ?: null, ]); } return [ 'synced_count' => count($orders), 'total_rows' => (int) ($preview['total_rows'] ?? 0), ]; } function cc_test_fetch_dashboard_orders(PDO $pdo): array { cc_test_ensure_schema($pdo); $sql = "SELECT o.source_key, o.codigo, o.import_id, o.drive_imported_at, o.nombre, o.celular, o.producto, o.cantidad, o.precio, o.pais, o.coordenadas, o.metodo, o.direccion_drive, o.referencia_drive, o.sede_drive, o.ciudad_drive, o.distrito_drive, o.dni_drive, o.observaciones_drive, o.first_seen_at, o.last_seen_at, COALESCE(NULLIF(t.estado, ''), 'POR LLAMAR') AS estado, COALESCE(t.nota_seguimiento, '') AS nota_seguimiento, t.proxima_llamada, t.fecha_ultimo_contacto, t.user_id, t.updated_at AS tracking_updated_at, t.direccion_editada, t.referencia_editada, t.sede_editada, t.ciudad_editada, t.distrito_editado, t.dni_editado, t.observaciones_editadas, COALESCE(NULLIF(t.direccion_editada, ''), NULLIF(o.direccion_drive, '')) AS direccion_final, COALESCE(NULLIF(t.referencia_editada, ''), NULLIF(o.referencia_drive, '')) AS referencia_final, COALESCE(NULLIF(t.sede_editada, ''), NULLIF(o.sede_drive, '')) AS sede_final, COALESCE(NULLIF(t.ciudad_editada, ''), NULLIF(o.ciudad_drive, '')) AS ciudad_final, COALESCE(NULLIF(t.distrito_editado, ''), NULLIF(o.distrito_drive, '')) AS distrito_final, COALESCE(NULLIF(t.dni_editado, ''), NULLIF(o.dni_drive, '')) AS dni_final, COALESCE(NULLIF(t.observaciones_editadas, ''), NULLIF(o.observaciones_drive, '')) AS observaciones_final, IFNULL(c.total_llamadas, 0) AS total_llamadas, c.ultima_llamada, COALESCE(u.nombre_asesor, u.username, '') AS gestor FROM callcenter_test_orders o LEFT JOIN callcenter_test_tracking t ON t.source_key = o.source_key LEFT JOIN ( SELECT pedido_id, COUNT(*) AS total_llamadas, MAX(fecha_llamada) AS ultima_llamada FROM historial_llamadas GROUP BY pedido_id ) c ON c.pedido_id = o.source_key LEFT JOIN users u ON u.id = t.user_id ORDER BY COALESCE(o.drive_imported_at, o.first_seen_at) DESC, o.id DESC"; $stmt = $pdo->query($sql); $orders = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($orders as &$order) { $digits = preg_replace('/\D+/', '', (string) ($order['celular'] ?? '')); $order['telefono_url'] = $digits !== '' ? 'tel:' . $digits : ''; $order['whatsapp_url'] = $digits !== '' ? 'https://wa.me/' . $digits : ''; } unset($order); return $orders; } function cc_test_is_same_day(?string $value, DateTimeImmutable $day): bool { $parsed = cc_test_parse_datetime($value); if ($parsed === null) { return false; } try { return (new DateTimeImmutable($parsed))->format('Y-m-d') === $day->format('Y-m-d'); } catch (Throwable $exception) { return false; } } function cc_test_is_due_today(array $order, DateTimeImmutable $endOfToday): bool { if (!in_array($order['estado'] ?? '', cc_test_open_states(), true)) { return false; } $nextCall = cc_test_parse_datetime($order['proxima_llamada'] ?? null); if ($nextCall === null) { return true; } try { return new DateTimeImmutable($nextCall) <= $endOfToday; } catch (Throwable $exception) { return true; } } function cc_test_matches_search(array $order, string $search): bool { $search = trim($search); if ($search === '') { return true; } $haystack = mb_strtolower(implode(' ', [ $order['codigo'] ?? '', $order['nombre'] ?? '', $order['celular'] ?? '', $order['dni_final'] ?? '', $order['direccion_final'] ?? '', $order['distrito_final'] ?? '', $order['ciudad_final'] ?? '', $order['producto'] ?? '', ])); return str_contains($haystack, mb_strtolower($search)); } function cc_test_build_dashboard(array $orders, string $view, string $search = ''): array { $today = new DateTimeImmutable('today'); $endOfToday = $today->setTime(23, 59, 59); $stats = [ 'todos' => count($orders), 'nuevos_hoy' => 0, 'pendientes_hoy' => 0, 'confirmados' => 0, 'cerrados' => 0, ]; $visibleOrders = []; foreach ($orders as $order) { $createdReference = $order['drive_imported_at'] ?: $order['first_seen_at']; $isNewToday = cc_test_is_same_day($createdReference, $today); $isConfirmed = ($order['estado'] ?? '') === 'CONFIRMADO'; $isClosed = in_array($order['estado'] ?? '', cc_test_closed_states(), true); $isPendingToday = cc_test_is_due_today($order, $endOfToday); if ($isNewToday) { $stats['nuevos_hoy']++; } if ($isPendingToday) { $stats['pendientes_hoy']++; } if ($isConfirmed) { $stats['confirmados']++; } if ($isClosed) { $stats['cerrados']++; } $order['is_new_today'] = $isNewToday; $order['is_pending_today'] = $isPendingToday; $order['is_confirmed'] = $isConfirmed; $order['is_closed'] = $isClosed; $matchesView = match ($view) { 'nuevos_hoy' => $isNewToday, 'confirmados' => $isConfirmed, 'cerrados' => $isClosed, 'todos' => true, default => $isPendingToday, }; if ($matchesView && cc_test_matches_search($order, $search)) { $visibleOrders[] = $order; } } usort($visibleOrders, static function (array $a, array $b) use ($view): int { $aNext = cc_test_parse_datetime($a['proxima_llamada'] ?? null); $bNext = cc_test_parse_datetime($b['proxima_llamada'] ?? null); if ($view === 'pendientes_hoy') { if ($aNext === null && $bNext !== null) { return -1; } if ($aNext !== null && $bNext === null) { return 1; } if ($aNext !== null && $bNext !== null && $aNext !== $bNext) { return strcmp($aNext, $bNext); } } $aBase = cc_test_parse_datetime($a['drive_imported_at'] ?? null) ?: cc_test_parse_datetime($a['first_seen_at'] ?? null) ?: ''; $bBase = cc_test_parse_datetime($b['drive_imported_at'] ?? null) ?: cc_test_parse_datetime($b['first_seen_at'] ?? null) ?: ''; return strcmp($bBase, $aBase); }); return [ 'stats' => $stats, 'visible_orders' => $visibleOrders, ]; } function cc_test_fetch_histories(PDO $pdo, array $sourceKeys): array { if (empty($sourceKeys)) { return []; } $placeholders = implode(',', array_fill(0, count($sourceKeys), '?')); $sql = "SELECT h.*, COALESCE(u.nombre_asesor, u.username, CONCAT('Asesor #', h.asesor_id)) AS asesor FROM historial_llamadas h LEFT JOIN users u ON u.id = h.asesor_id WHERE h.pedido_id IN ($placeholders) ORDER BY h.fecha_llamada DESC, h.id DESC"; $stmt = $pdo->prepare($sql); $stmt->execute($sourceKeys); $histories = []; foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { $histories[$row['pedido_id']][] = $row; } return $histories; }