34849-vm/includes/drive_test_orders.php
2026-07-10 06:17:47 +00:00

610 lines
24 KiB
PHP

<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/callcenter_test_helpers.php';
function drive_test_normalize_header(string $header): string
{
$header = trim($header);
$header = preg_replace('/\s+/', ' ', $header ?? '');
$header = strtoupper((string) $header);
return $header;
}
function drive_test_available_stores(): array
{
return [
// Hoja original que ya venía funcionando.
'flower' => [
'label' => 'Flower',
'spreadsheet_id' => '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c',
'sheet_gid' => null,
'startRow' => 5552,
],
// Segunda tienda (nuevo link que pasaste).
'otra_tienda' => [
'label' => 'TUANI',
'spreadsheet_id' => '1QYKeBJIIqYm6yW6Ka-5gKdxhUHwXOAc0No7RjnimxTw',
'sheet_gid' => 1523126328,
'startRow' => 6710,
],
];
}
function drive_test_get_store_config(string $storeKey): array
{
$storeKey = trim(mb_strtolower($storeKey));
$stores = drive_test_available_stores();
if (!isset($stores[$storeKey])) {
return $stores['flower'];
}
return $stores[$storeKey] + ['key' => $storeKey];
}
function drive_test_resolve_sheet_a1_range($service, string $spreadsheetId, ?int $sheetGid, string $rangeA1): string
{
if ($sheetGid === null) {
return $rangeA1;
}
static $cache = [];
$cacheKey = $spreadsheetId . '|' . (string) $sheetGid . '|' . $rangeA1;
if (isset($cache[$cacheKey])) {
return $cache[$cacheKey];
}
try {
$meta = $service->spreadsheets->get($spreadsheetId, ['fields' => 'sheets(properties(sheetId,title))']);
foreach (($meta->getSheets() ?? []) as $sheet) {
$props = $sheet->getProperties();
if (!$props) {
continue;
}
if ((int) $props->getSheetId() === (int) $sheetGid) {
$title = trim((string) ($props->getTitle() ?? ''));
if ($title !== '') {
// Sheet names with spaces/special chars must be quoted.
$escaped = str_replace("'", "''", $title);
$cache[$cacheKey] = "'{$escaped}'!{$rangeA1}";
return $cache[$cacheKey];
}
}
}
} catch (Throwable $exception) {
// Fallback to default range.
}
$cache[$cacheKey] = $rangeA1;
return $rangeA1;
}
function drive_test_get_cell(array $row, array $indexes, array $aliases, string $default = ''): string
{
foreach ($aliases as $alias) {
$normalized = drive_test_normalize_header($alias);
if (array_key_exists($normalized, $indexes)) {
return trim((string) ($row[$indexes[$normalized]] ?? $default));
}
}
return $default;
}
function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $storeKey = 'flower'): array
{
$storeConfig = drive_test_get_store_config($storeKey);
$credentialsPath = __DIR__ . '/../google_credentials.json';
$spreadsheetId = (string) ($storeConfig['spreadsheet_id'] ?? '');
$sheetGid = array_key_exists('sheet_gid', $storeConfig) ? ($storeConfig['sheet_gid'] === null ? null : (int) $storeConfig['sheet_gid']) : null;
$rangeA1 = 'A:Z';
if ($spreadsheetId === '') {
throw new RuntimeException('Configuración de Drive para la tienda no válida.');
}
if (!file_exists($credentialsPath)) {
throw new RuntimeException('No se encontró el archivo de credenciales de Google.');
}
$client = new Google\Client();
$client->setAuthConfig($credentialsPath);
$client->addScope(Google\Service\Sheets::SPREADSHEETS_READONLY);
$service = new Google\Service\Sheets($client);
$range = drive_test_resolve_sheet_a1_range($service, $spreadsheetId, $sheetGid, $rangeA1);
$response = $service->spreadsheets_values->get($spreadsheetId, $range);
$values = $response->getValues();
if (empty($values)) {
return [
'headers' => [],
'orders' => [],
'total_rows' => 0,
];
}
$headers = $values[0] ?? [];
$headerIndexes = [];
foreach ($headers as $index => $header) {
$normalized = drive_test_normalize_header((string) $header);
if ($normalized === '') {
$normalized = $index === 0 ? 'CODIGO' : 'COL_' . $index;
}
$headerIndexes[$normalized] = $index;
}
$dataRows = array_slice($values, 1);
$startIndex = max(0, $startRow - 2);
$totalDataRows = count($dataRows);
if ($startIndex >= $totalDataRows) {
$dataRows = [];
} elseif ($startIndex > 0) {
$dataRows = array_slice($dataRows, $startIndex);
}
$previewRows = $limit > 0
? array_reverse(array_slice($dataRows, -$limit))
: array_reverse($dataRows);
$orders = [];
foreach ($previewRows as $row) {
$codigo = trim((string) ($row[0] ?? ''));
$importId = drive_test_get_cell($row, $headerIndexes, ['ID']);
$nombre = drive_test_get_cell($row, $headerIndexes, ['NOMBRE']);
$celular = preg_replace('/\D+/', '', drive_test_get_cell($row, $headerIndexes, ['CELULAR']));
$producto = drive_test_get_cell($row, $headerIndexes, ['PRODUCTO']);
$cantidad = drive_test_get_cell($row, $headerIndexes, ['CANTIDAD']);
$precio = drive_test_get_cell($row, $headerIndexes, ['PRECIO']);
$pais = drive_test_get_cell($row, $headerIndexes, ['PAIS']);
$coordenadas = drive_test_get_cell($row, $headerIndexes, ['COORDENADAS']);
$ciudad = drive_test_get_cell($row, $headerIndexes, ['CIUDAD']);
$metodo = drive_test_get_cell($row, $headerIndexes, ['METODO']);
$sede = drive_test_get_cell($row, $headerIndexes, ['SEDE / ID', 'SEDE/ID']);
$dni = drive_test_get_cell($row, $headerIndexes, ['N° DNI', 'N° DNI ', 'NRO DNI', 'DNI']);
$observaciones = drive_test_get_cell($row, $headerIndexes, ['OBSERVACIONES', 'OBSERVACIONES ']);
$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']);
// Importante: para 'flower' mantenemos EXACTAMENTE el algoritmo anterior
// para no romper el historial/tracking ya existente.
$sourceKeyBase = implode('|', [$codigo, $importId, $nombre, $celular, $producto]);
if (($storeConfig['key'] ?? 'flower') === 'flower') {
$sourceKey = sha1($sourceKeyBase);
} else {
$sourceKey = sha1(($storeConfig['key'] ?? $storeKey) . '|' . $sourceKeyBase);
}
$orders[] = [
'source_key' => $sourceKey,
'codigo' => $codigo,
'import_id' => $importId,
'nombre' => $nombre,
'direccion' => $direccion,
'referencia' => $referencia,
'agencia' => '',
'sede_agencia' => '',
'distrito' => $distrito,
'celular' => $celular,
'producto' => $producto,
'cantidad' => $cantidad,
'precio' => $precio,
'pais' => $pais,
'coordenadas' => $coordenadas,
'ciudad' => $ciudad,
'metodo' => $metodo,
'sede' => $sede,
'dni' => $dni,
'observaciones' => $observaciones,
];
}
return [
'headers' => $headers,
'orders' => $orders,
'total_rows' => count($dataRows),
];
}
function drive_test_fetch_tracking(PDO $pdo, array $sourceKeys): array
{
if (empty($sourceKeys)) {
return [];
}
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->execute($sourceKeys);
$tracking = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$tracking[$row['source_key']] = $row;
}
return $tracking;
}
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'];
foreach ($orders as &$order) {
$current = $tracking[$order['source_key']] ?? null;
$order['estado'] = $current['estado'] ?? 'POR LLAMAR';
$order['nota_seguimiento'] = $current['nota_seguimiento'] ?? '';
$userId = array_key_exists('user_id', (array) $current) ? $current['user_id'] : null;
$order['user_id'] = $userId !== null ? ((int) $userId > 0 ? (int) $userId : null) : null;
$order['seguimiento_actualizado'] = $current['updated_at'] ?? null;
$order['proxima_llamada_at'] = $current['proxima_llamada_at'] ?? null;
$order['fecha_entrega_programada'] = $current['fecha_entrega_programada'] ?? null;
$order['numero_cuenta_enviado_at'] = $current['numero_cuenta_enviado_at'] ?? null;
$order['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['ultima_gestion_at'] = $current['ultima_gestion_at'] ?? ($current['updated_at'] ?? null);
foreach ($editableFields as $field) {
$order[$field . '_drive'] = $order[$field] ?? '';
$trackedValue = $current[$field] ?? null;
$order[$field . '_editado'] = $trackedValue;
if ($trackedValue !== null && trim((string) $trackedValue) !== '') {
$order[$field] = $trackedValue;
}
}
$digits = preg_replace('/\D+/', '', $order['celular'] ?? '');
$order['whatsapp_url'] = $digits !== '' ? 'https://wa.me/' . $digits : '';
$order['telefono_url'] = $digits !== '' ? 'tel:' . $digits : '';
}
unset($order);
return $orders;
}
function drive_test_parse_datetime_mysql(?string $value): ?string
{
$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",
];
foreach ($formats as $format) {
try {
$date = DateTimeImmutable::createFromFormat($format, $value);
if ($date instanceof DateTimeImmutable) {
return $date->format("Y-m-d H:i:s");
}
} catch (Throwable $exception) {
// ignore and try next format
}
}
try {
return (new DateTimeImmutable($value))->format("Y-m-d H:i:s");
} catch (Throwable $exception) {
return null;
}
}
function drive_test_ensure_orders_table(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,
`store_key` VARCHAR(50) NULL,
`source_key` CHAR(40) NOT NULL,
`codigo` VARCHAR(80) DEFAULT NULL,
`import_id` VARCHAR(120) DEFAULT NULL,
`is_agregado` TINYINT(1) NOT NULL DEFAULT 0,
`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` LONGTEXT 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");
// 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");
// Asegura columna para pedidos cargados manualmente (Agregados)
cc_test_ensure_column($pdo, "callcenter_test_orders", "is_agregado", "TINYINT(1) NOT NULL DEFAULT 0");
// Asegurar que `dni_drive` soporte valores grandes desde Google Sheets.
try {
$colStmt = $pdo->prepare("
SELECT DATA_TYPE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'callcenter_test_orders'
AND COLUMN_NAME = 'dni_drive'
LIMIT 1
");
$colStmt->execute();
$dataType = strtolower((string) $colStmt->fetchColumn());
if ($dataType !== 'longtext') {
$pdo->exec("ALTER TABLE `callcenter_test_orders` MODIFY COLUMN `dni_drive` LONGTEXT NULL");
}
} catch (Throwable $exception) {
// No bloqueamos el panel si por alguna razón no se puede ajustar el tipo.
error_log('drive_test_ensure_orders_table: ' . $exception->getMessage());
}
$checked = true;
}
function drive_test_ensure_import_checkpoints_table(PDO $pdo): void
{
static $checked = false;
if ($checked) {
return;
}
$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,
`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");
$checked = true;
}
function drive_test_sync_orders_incremental(PDO $pdo, string $storeKey, int $overlapRows = 50): array
{
$storeKey = mb_strtolower(trim($storeKey));
$storeConfig = drive_test_get_store_config($storeKey);
$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->execute([$storeKey]);
$checkpointRow = (int) ($stmtCheckpoint->fetchColumn() ?? 0);
$overlapRows = max(0, $overlapRows);
$effectiveStartRow = $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);
$sheetLastRow = ($effectiveStartRow - 1) + $driveTotalRows;
$newCheckpointRow = max($checkpointRow, (int) $sheetLastRow);
if (!empty($driveOrders)) {
$stmtUpsert = $pdo->prepare(
"INSERT INTO callcenter_test_orders (
store_key,
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 (
:store_key,
: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
store_key = VALUES(store_key),
codigo = VALUES(codigo),
import_id = VALUES(import_id),
is_agregado = 0,
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 ($driveOrders as $order) {
$driveImportedAt = drive_test_parse_datetime_mysql($order["import_id"] ?? 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,
]);
}
}
$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)
ON DUPLICATE KEY UPDATE
last_processed_row = VALUES(last_processed_row),
last_sync_at = CURRENT_TIMESTAMP"
);
$stmtCheckpointUpsert->execute([
":store_key" => $storeKey,
":last_processed_row" => $newCheckpointRow,
]);
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),
];
}
function drive_test_fetch_orders_from_db(PDO $pdo, string $storeKey, ?bool $onlyAgregados = null): array
{
$storeKey = mb_strtolower(trim($storeKey));
drive_test_ensure_orders_table($pdo);
$where = "WHERE store_key = ?";
$params = [$storeKey];
if ($onlyAgregados === true) {
$where .= " AND is_agregado = 1";
} elseif ($onlyAgregados === false) {
$where .= " AND (is_agregado = 0 OR is_agregado IS NULL)";
}
$stmt = $pdo->prepare(
"SELECT
id,
is_agregado,
source_key,
codigo,
import_id,
nombre,
celular,
producto,
cantidad,
precio,
pais,
coordenadas,
metodo,
direccion_drive,
referencia_drive,
sede_drive,
ciudad_drive,
distrito_drive,
dni_drive,
observaciones_drive
FROM callcenter_test_orders
$where
ORDER BY 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"] ?? ""),
];
}
return $orders;
}