false, 'message' => 'No autorizado']); exit; } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['success' => false, 'message' => 'Método no permitido']); exit; } function cc_test_normalize_nullable_text(string $key, int $maxLen = 3000): ?string { $value = trim((string) ($_POST[$key] ?? '')); if ($value === '') { return null; } if (mb_strlen($value) > $maxLen) { throw new RuntimeException('El campo ' . $key . ' es demasiado largo.'); } 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( 'SELECT source_key, store_key, codigo, import_id, drive_imported_at, first_seen_at, nombre, celular, producto, cantidad, precio, coordenadas, direccion_drive, referencia_drive, sede_drive, ciudad_drive, distrito_drive, dni_drive, observaciones_drive FROM callcenter_test_orders WHERE source_key = ? LIMIT 1' ); $stmt->execute([$sourceKey]); $row = $stmt->fetch(PDO::FETCH_ASSOC); return $row ?: null; } function cc_test_build_contraentrega_notes(array $parts): string { $cleanParts = []; foreach ($parts as $part) { $part = trim((string) $part); if ($part !== '') { $cleanParts[] = $part; } } return implode("\n\n", $cleanParts); } function cc_test_prepare_route_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 ruta está activa solo para TUANI.'); } $nombreCompleto = trim((string) ($sourceOrder['nombre'] ?? '')); $celular = trim((string) ($sourceOrder['celular'] ?? '')); $direccion = trim((string) ($formData['direccion'] ?? '')); $referencia = trim((string) ($formData['referencia'] ?? '')); $departamento = trim((string) ($formData['sede'] ?? '')); $provincia = trim((string) ($formData['ciudad'] ?? '')); $distrito = trim((string) ($formData['distrito'] ?? '')); $dni = trim((string) (($formData['dni'] ?? '') !== '' ? ($formData['dni'] ?? '') : ($sourceOrder['dni_drive'] ?? ''))); $observacionesPedido = trim((string) (($formData['observaciones'] ?? '') !== '' ? ($formData['observaciones'] ?? '') : ($sourceOrder['observaciones_drive'] ?? ''))); $notaSeguimiento = trim((string) ($formData['nota_seguimiento'] ?? '')); $fechaEntrega = $formData['fecha_entrega_programada'] ?? null; $coordenadasRaw = trim((string) ($formData['coordenadas'] ?? '')); if ($nombreCompleto === '' || $celular === '') { throw new RuntimeException('El pedido no tiene nombre o celular suficientes para enviarlo a Ruta Contraentrega.'); } if ($fechaEntrega === null || $fechaEntrega === '') { throw new RuntimeException('Debes seleccionar la fecha de entrega antes de subir el pedido.'); } if ($direccion === '' || $departamento === '' || $provincia === '' || $distrito === '') { throw new RuntimeException('Para subir el pedido completa dirección, departamento, provincia y distrito.'); } $departamentos = contraentregaProvinciasPorDepartamento(); $distritosPorProvincia = contraentregaDistritosPorProvincia(); if (!array_key_exists($departamento, $departamentos)) { throw new RuntimeException('Selecciona un departamento válido para Ruta Contraentrega.'); } $provinciasPermitidas = $departamentos[$departamento] ?? []; if ($provincia === '' || !in_array($provincia, $provinciasPermitidas, true)) { throw new RuntimeException('Selecciona una provincia válida para el departamento elegido.'); } $distritosPermitidos = $distritosPorProvincia[$provincia] ?? null; if (is_array($distritosPermitidos) && !empty($distritosPermitidos) && !in_array($distrito, $distritosPermitidos, true)) { throw new RuntimeException('Selecciona un distrito válido para la provincia elegida.'); } $coordenadas = cc_test_normalize_contraentrega_coordinates($coordenadasRaw); if ($coordenadas === null) { throw new RuntimeException('Ingresa coordenadas válidas con el formato -12.082029, -77.069024.'); } $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 Ruta Contraentrega.'); } $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), $observacionesPedido !== '' ? 'Observaciones del pedido: ' . $observacionesPedido : '', $notaSeguimiento !== '' ? 'Nota interna Call Center: ' . $notaSeguimiento : '', ]); $seguimiento = 'Subido desde Call Center TUANI'; $codigoTracking = trim((string) ($sourceOrder['codigo'] ?? '')); return [ 'source_key' => (string) ($sourceOrder['source_key'] ?? ''), 'dni_cliente' => $dni, 'nombre_completo' => $nombreCompleto, 'celular' => $celular, 'agencia' => 'CONTRAENTREGA', 'sede_envio' => $departamento, 'codigo_rastreo' => trim($provincia . ' / ' . $distrito, ' /'), 'codigo_tracking' => $codigoTracking !== '' ? $codigoTracking : null, 'direccion_exacta' => $direccion, 'referencia_domicilio' => $referencia !== '' ? $referencia : null, 'coordenadas' => $coordenadas, 'producto' => implode(', ', $productos), 'cantidad' => $cantidadTotal, 'monto_total' => round($montoTotal, 2), 'asesor_id' => $fallbackUserId > 0 ? $fallbackUserId : null, 'notas' => $notas, 'nota_adicional' => $notaSeguimiento !== '' ? $notaSeguimiento : null, 'observacion' => $observacionesPedido !== '' ? $observacionesPedido : null, 'descargo' => $observacionesPedido !== '' ? $observacionesPedido : null, 'seguimiento' => $seguimiento, 'fecha_entrega' => $fechaEntrega, 'tipo_paquete' => 'CONTRAENTREGA', 'estado' => 'RUTA_CONTRAENTREGA', ]; } function cc_test_sync_route_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) { $montoAdelantado = round((float) ($existing['monto_adelantado'] ?? 0), 2); $montoDebe = round(max(0, $payload['monto_total'] - $montoAdelantado), 2); $stmtUpdate = $pdo->prepare( 'UPDATE pedidos SET dni_cliente = :dni_cliente, nombre_completo = :nombre_completo, celular = :celular, agencia = :agencia, sede_envio = :sede_envio, codigo_rastreo = :codigo_rastreo, codigo_tracking = COALESCE(NULLIF(:codigo_tracking, \'\'), codigo_tracking), referencia_domicilio = :referencia_domicilio, direccion_exacta = :direccion_exacta, coordenadas = :coordenadas, producto = :producto, cantidad = :cantidad, monto_total = :monto_total, monto_debe = :monto_debe, asesor_id = :asesor_id, notas = :notas, nota_adicional = :nota_adicional, observacion = :observacion, descargo = :descargo, seguimiento = :seguimiento, fecha_entrega = :fecha_entrega, tipo_paquete = :tipo_paquete, 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_rastreo' => $payload['codigo_rastreo'], ':codigo_tracking' => $payload['codigo_tracking'] ?? '', ':referencia_domicilio' => $payload['referencia_domicilio'], ':direccion_exacta' => $payload['direccion_exacta'], ':coordenadas' => $payload['coordenadas'], ':producto' => $payload['producto'], ':cantidad' => $payload['cantidad'], ':monto_total' => $payload['monto_total'], ':monto_debe' => $montoDebe, ':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'], ':fecha_entrega' => $payload['fecha_entrega'], ':tipo_paquete' => $payload['tipo_paquete'], ':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_rastreo, codigo_tracking, referencia_domicilio, direccion_exacta, coordenadas, producto, cantidad, monto_total, monto_adelantado, monto_debe, estado, asesor_id, notas, nota_adicional, observacion, descargo, seguimiento, fecha_entrega, tipo_paquete ) VALUES ( :dni_cliente, :nombre_completo, :celular, :agencia, :sede_envio, :codigo_rastreo, :codigo_tracking, :referencia_domicilio, :direccion_exacta, :coordenadas, :producto, :cantidad, :monto_total, 0, :monto_debe, :estado, :asesor_id, :notas, :nota_adicional, :observacion, :descargo, :seguimiento, :fecha_entrega, :tipo_paquete )' ); $stmtInsert->execute([ ':dni_cliente' => $payload['dni_cliente'], ':nombre_completo' => $payload['nombre_completo'], ':celular' => $payload['celular'], ':agencia' => $payload['agencia'], ':sede_envio' => $payload['sede_envio'], ':codigo_rastreo' => $payload['codigo_rastreo'], ':codigo_tracking' => $payload['codigo_tracking'], ':referencia_domicilio' => $payload['referencia_domicilio'], ':direccion_exacta' => $payload['direccion_exacta'], ':coordenadas' => $payload['coordenadas'], ':producto' => $payload['producto'], ':cantidad' => $payload['cantidad'], ':monto_total' => $payload['monto_total'], ':monto_debe' => $payload['monto_total'], ':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'], ':fecha_entrega' => $payload['fecha_entrega'], ':tipo_paquete' => $payload['tipo_paquete'], ]); return [ 'pedido_id' => (int) $pdo->lastInsertId(), 'action' => 'created', ]; } 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(); $subirALogistica = (string) ($_POST['subir_a_logistica'] ?? ($_POST['subir_a_ruta'] ?? '')) === '1'; $moverAPromoFinal = (string) ($_POST['mover_a_promo_final'] ?? '') === '1'; $eliminarPedido = (string) ($_POST['eliminar_pedido'] ?? '') === '1'; if ($sourceKey === '' || !preg_match('/^[a-f0-9]{40}$/', $sourceKey)) { http_response_code(400); echo json_encode(['success' => false, 'message' => 'Pedido de prueba inválido']); exit; } if (!in_array($estado, $validStates, true)) { http_response_code(400); echo json_encode(['success' => false, 'message' => 'Estado inválido']); exit; } $createdFiles = []; try { $nota = cc_test_normalize_nullable_text('nota_seguimiento', 3000); $direccion = cc_test_normalize_nullable_text('direccion', 1000); $referencia = cc_test_normalize_nullable_text('referencia', 1000); $agencia = cc_test_normalize_nullable_text('agencia', 80); $sedeAgencia = cc_test_normalize_nullable_text('sede_agencia', 120); $sede = cc_test_normalize_nullable_text('sede', 120); $ciudad = cc_test_normalize_nullable_text('ciudad', 120); $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, 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, eliminado_at, eliminado_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) { $currentUserId = (int) ($_SESSION['user_id'] ?? 0); $trackingUserId = $currentTracking ? (int) ($currentTracking['user_id'] ?? 0) : null; if (!$currentTracking || $trackingUserId !== $currentUserId) { http_response_code(403); echo json_encode(['success' => false, 'message' => 'No autorizado para gestionar este pedido.']); exit; } } $deletedAtCurrent = trim((string) ($currentTracking['eliminado_at'] ?? '')); $isDeleted = $deletedAtCurrent !== ''; if ($eliminarPedido) { if (!$isAdmin) { http_response_code(403); echo json_encode(['success' => false, 'message' => 'Solo un administrador puede eliminar pedidos del panel.']); exit; } if ($isDeleted) { echo json_encode(['success' => true, 'message' => 'El pedido ya estaba eliminado del panel.']); exit; } $deleteState = cc_test_normalize_state((string) ($currentTracking['estado'] ?? $estado)); if (!in_array($deleteState, $validStates, true)) { $deleteState = 'POR LLAMAR'; } $deleteUserId = $currentTracking && (int) ($currentTracking['user_id'] ?? 0) > 0 ? (int) $currentTracking['user_id'] : null; $deletedAt = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s'); $deletedBy = (int) ($_SESSION['user_id'] ?? 0); $stmtDelete = $pdo->prepare( 'INSERT INTO callcenter_test_tracking ( source_key, estado, user_id, eliminado_at, eliminado_por, ultima_gestion_at ) VALUES ( :source_key, :estado, :user_id, :eliminado_at, :eliminado_por, CURRENT_TIMESTAMP ) ON DUPLICATE KEY UPDATE estado = VALUES(estado), user_id = VALUES(user_id), eliminado_at = VALUES(eliminado_at), eliminado_por = VALUES(eliminado_por), ultima_gestion_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP' ); $stmtDelete->bindValue(':source_key', $sourceKey, PDO::PARAM_STR); $stmtDelete->bindValue(':estado', $deleteState, PDO::PARAM_STR); if ($deleteUserId === null) { $stmtDelete->bindValue(':user_id', null, PDO::PARAM_NULL); } else { $stmtDelete->bindValue(':user_id', $deleteUserId, PDO::PARAM_INT); } $stmtDelete->bindValue(':eliminado_at', $deletedAt, PDO::PARAM_STR); if ($deletedBy > 0) { $stmtDelete->bindValue(':eliminado_por', $deletedBy, PDO::PARAM_INT); } else { $stmtDelete->bindValue(':eliminado_por', null, PDO::PARAM_NULL); } $stmtDelete->execute(); echo json_encode([ 'success' => true, 'message' => 'Pedido eliminado del panel correctamente.', 'eliminado_at' => $deletedAt, ]); exit; } if ($isDeleted) { throw new RuntimeException('Este pedido fue eliminado del panel por un administrador.'); } $userIdToSet = $isAdmin ? ($currentTracking ? ((int) ($currentTracking['user_id'] ?? 0) > 0 ? (int) $currentTracking['user_id'] : null) : null) : (int) $_SESSION['user_id']; $proximaRaw = trim((string) ($_POST['proxima_llamada_at'] ?? '')); $proximaLlamada = null; if ($proximaRaw !== '') { $proximaLlamada = DateTimeImmutable::createFromFormat('Y-m-d\TH:i', $proximaRaw); if (!$proximaLlamada) { throw new RuntimeException('La fecha de próxima llamada no es válida.'); } $proximaLlamada = $proximaLlamada->format('Y-m-d H:i:s'); } $fechaEntregaRaw = trim((string) ($_POST['fecha_entrega_programada'] ?? '')); $fechaEntrega = null; if ($fechaEntregaRaw !== '') { $fechaEntregaDate = DateTimeImmutable::createFromFormat('Y-m-d', $fechaEntregaRaw); if (!$fechaEntregaDate) { throw new RuntimeException('La fecha de entrega no es válida.'); } $fechaEntrega = $fechaEntregaDate->format('Y-m-d'); } if (cc_test_requires_delivery_date($estado) && $fechaEntrega === null) { throw new RuntimeException('Debes seleccionar la fecha de entrega para CONFIRMADO CONTRAENTREGA.'); } if (!in_array($estado, cc_test_open_states(), true)) { $proximaLlamada = null; } if (!cc_test_requires_delivery_date($estado)) { $fechaEntrega = null; } $numeroCuentaEnviadoAt = $currentTracking['numero_cuenta_enviado_at'] ?? null; $currentState = cc_test_normalize_state((string) ($currentTracking['estado'] ?? '')); if ($estado === 'SE ENVIO NUMERO DE CUENTA') { if ($currentState !== 'SE ENVIO NUMERO DE CUENTA' || trim((string) $numeroCuentaEnviadoAt) === '') { $numeroCuentaEnviadoAt = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s'); } } 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 = [ 'nota_seguimiento' => $nota, 'direccion' => $direccion, 'referencia' => $referencia, 'agencia' => $agencia, 'sede_agencia' => $sedeAgencia, 'sede' => $sede, 'ciudad' => $ciudad, '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, 'confirmacion_producto_extra' => $confirmacionProductoExtra, 'confirmacion_cantidad_extra' => $confirmacionCantidadExtra, 'confirmacion_precio_extra' => $confirmacionPrecioExtra, 'fecha_entrega_programada' => $fechaEntrega, ]; $pdo->beginTransaction(); $stmt = $pdo->prepare( 'INSERT INTO callcenter_test_tracking ( source_key, estado, nota_seguimiento, user_id, direccion, referencia, agencia, sede_agencia, sede, ciudad, distrito, coordenadas, dni, numero_cuenta_sede_id, numero_cuenta_dni, observaciones, 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, ultima_gestion_at ) VALUES ( :source_key, :estado, :nota, :user_id, :direccion, :referencia, :agencia, :sede_agencia, :sede, :ciudad, :distrito, :coordenadas, :dni, :numero_cuenta_sede_id, :numero_cuenta_dni, :observaciones, :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, CURRENT_TIMESTAMP ) ON DUPLICATE KEY UPDATE estado = VALUES(estado), nota_seguimiento = VALUES(nota_seguimiento), user_id = VALUES(user_id), direccion = VALUES(direccion), referencia = VALUES(referencia), agencia = VALUES(agencia), sede_agencia = VALUES(sede_agencia), sede = VALUES(sede), ciudad = VALUES(ciudad), 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), confirmacion_producto_extra = VALUES(confirmacion_producto_extra), confirmacion_cantidad_extra = VALUES(confirmacion_cantidad_extra), confirmacion_precio_extra = VALUES(confirmacion_precio_extra), 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' ); $stmt->execute([ ':source_key' => $sourceKey, ':estado' => $estado, ':nota' => $nota, ':user_id' => $userIdToSet, ':direccion' => $direccion, ':referencia' => $referencia, ':agencia' => $agencia, ':sede_agencia' => $sedeAgencia, ':sede' => $sede, ':ciudad' => $ciudad, ':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, ':confirmacion_producto_extra' => $confirmacionProductoExtra, ':confirmacion_cantidad_extra' => $confirmacionCantidadExtra, ':confirmacion_precio_extra' => $confirmacionPrecioExtra, ':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 ? (int) $currentTracking['ruta_contraentrega_pedido_id'] : 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 ($subirALogistica) { $sourceOrder = cc_test_fetch_source_order($pdo, $sourceKey); if (!$sourceOrder) { throw new RuntimeException('No encontré el pedido base para enviarlo a logística.'); } 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'); $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 ($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, 'message' => $message, 'estado' => $estado, '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([ 'success' => false, 'message' => $exception instanceof RuntimeException ? $exception->getMessage() : 'No se pudo guardar la gestión.', ]); }