Exporta tus pedidos de Shopify a Google : guía paso a paso

Ecommerce 7 de abr. de 2024

En un post anterior, ya vimos como conectarnos a la API de Shopify desde Google Sheets, si nunca lo has hecho te recomendamos partir con ese post que contiene las indicaciones paso a paso. En este post nos concentraremos en conectarnos con la API Order de Shopify para obtener las compras que clientes hayan realizado en nuestra tienda.

La necesidad para esto surgió ya que estamos trabajando con un courier que aún no tiene integración con Shopify, y todos los días debíamos exportar manualmente los envíos que realizaríamos con el y agregar/eliminar columnas para que el archivo quedara en el formato que debemos enviar a su plataforma. En cambio ahora basta apretar un botón para obtener los pedidos según los filtros que le indiquemos, descargar el archivo y subirlo a su plataforma.

Paso 1: Crea una aplicación privada con acceso a la API del panel de control read_orders, con este podrás acceder a todos los pedidos de tu tienda Shopify.

Paso 2: Con los datos que obtienes de tu aplicación privada, prueba la conexión a través de una aplicación como Postman u otra similar, llamando al enpoint https://[tu-tienda-shopify].myshopify.com/admin/api/2022-04/orders.json?limit=10.

Deberías obtener un listado de tus últimas 10 compras, esto te servirá también para conocer la estructura de esta API. Además puedes consultar la documentación en Shopify de esta API.

Paso 3: Genera un nuevo Apps Script en Google Sheets, supondremos que queremos escribir los pedidos que obtendremos a una hoja llamada Pedidos, este es el código completo:

function getOrdersUpdateSheet() {

  var status = "any" //valores posibles: any, open, closed, cancelled
  var created_at_max = "2024-04-04" //formato date ISO 8601 "2024-03-04T11:00:00-05:00"
  var created_at_min = "2024-04-01" //formato date ISO 8601 "2024-03-04T11:00:00-05:00"
  var financial_status = "any" //valores posibles: any, authorized, pending, paid, partially_paid, refunded, voided, partially_refunded. unpaid
  var fulfillment_status = "any" ////valores posibles: any, shipped, partial, unshipped, unfulfilled

  var params = '?limit=100'; //Hasta 250
  params = addParamIfNotEmpty(params, 'status', status);
  params = addParamIfNotEmpty(params, 'created_at_min', created_at_min);
  params = addParamIfNotEmpty(params, 'created_at_max', created_at_max);
  params = addParamIfNotEmpty(params, 'financial_status', financial_status);
  params = addParamIfNotEmpty(params, 'fulfillment_status', fulfillment_status);

  var fields = 'order_number,financial_status,fulfillment_status,contact_email,shipping_address';  //permite acotar la cantidad de campos que traemos
  params += '&fields=' + fields;

  var username = 'tu_clave_api'; // Sustituye por tu clave API
  var password = 'tu_token_acceso_privad'; // Sustituye por tu token de acceso privado
  var baseUrl = 'https://tu-tienda..myshopify.com/admin/api/2022-04/orders.json';
  var shopUrl = baseUrl + params;

  var headers = {
    "Authorization": "Basic " + Utilities.base64Encode(username + ':' + password)
  };

  var options = {
    "method": "get",
    "headers": headers,
    "muteHttpExceptions": true // Esto permite que la respuesta completa se incluya en los logs incluso si hay un error.
  };

  // Asume que inicialmente puede haber una página siguiente.
  var hasNextPage = true;

  // Obtener referencia a la hoja de cálculo y la hoja específica
  var sheetID = 'el_id_de_tu_planilla_en_Google_Sheets'; // Sustituye por valor real
  var sheetName = 'Pedidos' // Sustituye por el nombre de la hoja donde exportaras los resultados
  var hoja = SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);

  // Primero limpiamos la hoja, excepto los encabezados
  clearSheet(sheetID, sheetName); //función auxiliar

  try {
    while (hasNextPage) {
      Logger.log('Llamando a Shopify API URL: ' + shopUrl); // Registrar la URL antes de hacer la llamada
      var response = UrlFetchApp.fetch(shopUrl, options);
      // Registro de tanto la respuesta como cualquier error que pueda suceder durante la llamada a la API.
      if (response.getResponseCode() == 200) {
        Logger.log('Respuesta de Shopify API: ' + response.getContentText());
      } else {
        Logger.log('Error de Shopify API: ' + response.getContentText());
      }

      var json = JSON.parse(response.getContentText());
      var pedidos = json.orders;

      if (pedidos.length > 0) {
        pedidos.forEach(function (pedido) {
          var direccionCompleta = pedido.shipping_address.address1;
          if (pedido.shipping_address.address2) {
            direccionCompleta += ', ' + pedido.shipping_address.address2;
          }
          var fila = [
            (pedido.shipping_address.first_name || "").trim().toUpperCase(), // Nombre, quitamos espacios sobrantes y transformamos a mayúsculas
            (pedido.shipping_address.last_name || "").trim().toUpperCase(), // Apellido
            (pedido.shipping_address.phone || "").trim().toUpperCase().replace(/\s+/g, ''), // Teléfono
            direccionCompleta.trim().toUpperCase(),  // Dirección de Envío
            (pedido.shipping_address.city || "").trim().toUpperCase(), // Comuna de Envío
            (pedido.order_number.toString() || "").trim().toUpperCase(), // Número del pedido
            (pedido.contact_email || "").trim().toLowerCase(), // Email del Cliente
          ];
          // Escribimos la fila
          hoja.appendRow(fila);
        });
      }
      // Verificar si hay más páginas
      var linkHeader = response.getHeaders()['Link'];
      if (linkHeader) {
        var nextLink = linkHeader.split(',').find(s => s.includes('rel="next"'));
        if (nextLink) {
          var matches = nextLink.match(/<(.*?)>; rel="next"/);
          if (matches) {
            shopUrl = matches[1]; // URL de la próxima página
          } else {
            hasNextPage = false; // No más páginas
          }
        } else {
          hasNextPage = false; // No más páginas
        }
      } else {
        hasNextPage = false; // No más páginas
      }
    }
  } catch (e) {
    Logger.log("Error al obtener y actualizar datos de órdenes: " + e.toString());
    // Incluyendo la respuesta de la API si está disponible dentro de la excepción
    if (typeof response !== 'undefined') {
      Logger.log('Respuesta de la API de Shopify durante el error: ' + response.getContentText());
    }
  }
  autoResizeSheetColumns(sheetID, sheetName) //función auxiliar
}

//función auxiliar: limpia la hoja excepto encabezados
function clearSheet(sheetID, sheetName) {
  var sheet = SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);
  var lastRow = sheet.getLastRow();
  var lastColumn = sheet.getLastColumn();
  if (lastRow > 1) {
    // Borra todas las filas después del encabezado
    sheet.deleteRows(2, lastRow - 1);
    // Borra el contenido de las celdas que pudieran quedar si hay más celdas que filas
    if (lastColumn > 1) {
      sheet.getRange(2, 1, sheet.getMaxRows() - 1, lastColumn).clearContent();
    }
  }
}

//función auxiliar: auto ajusta el tamaño de las columnas al tamaño del texto que contienen
function autoResizeSheetColumns(sheetID, sheetName) {
  var sheet = SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);
  var lastColumn = sheet.getLastColumn();
  Logger.log('Ajustando columnas hasta: ' + lastColumn);
  sheet.autoResizeColumns(1, lastColumn);
}

// Función auxiliar para agregar parámetros a la URL si el valor no está vacío
function addParamIfNotEmpty(params, paramName, paramValue) {
  if (paramValue) {
    // Verificar si paramValue es una instancia de Date
    if (paramValue instanceof Date) {
      // Convertir directamente a formato ISO 8601
      paramValue = formatDate(paramValue);
    } else if (typeof paramValue === 'string') {
      // Solo intentar la coincidencia de regex si paramValue es una cadena
      var matches = paramValue.match(/^\d{1,2}\/\d{1,2}\/\d{4}$/);
      if (matches) {
        paramValue = formatDate(paramValue);
      }
    }
    params += `&${paramName}=${encodeURIComponent(paramValue)}`;
  }
  return params;
}

Revisemos ahora los puntos más importantes de este código:

  1. Autenticación y Acceso a la API de Shopify: El código utiliza la autenticación básica para conectarse a la API de Shopify. Las variables username y password deben reemplazarse por la clave API y el token de acceso privado respectivamente. Esta autenticación es crucial para acceder de forma segura a la información de los pedidos de Shopify.
  2. Parámetros de Consulta Personalizables: El script permite configurar varios filtros a través de parámetros como status, created_at_min, created_at_max, financial_status y fulfillment_status. Estos parámetros se pueden ajustar para recuperar un conjunto específico de pedidos, lo que hace que la exportación sea muy flexible.
  3. Limitación y Selección de Campos: Se utiliza un límite de 100 pedidos por solicitud (ajustable hasta 250) y se seleccionan campos específicos para recuperar (revisaremos esto con más detalle un poco más abajo), optimizando así el rendimiento de la API y asegurando que solo se obtenga la información relevante.
  4. Manejo de Paginación para Grandes Volúmenes de Datos: El código está diseñado para manejar la paginación automáticamente, permitiendo recuperar más de 100 pedidos iterando a través de las páginas de resultados de la API de Shopify hasta que no haya más páginas.
  5. Preparación y Limpieza de la Hoja de Google Sheets: Antes de la importación, la hoja especificada se limpia con la función auxiliar clearSheet, manteniendo únicamente los encabezados, para evitar duplicados y mantener los datos actualizados.
  6. Estructuración y Escritura de Datos en Google Sheets: Se estructuran los datos de cada pedido en un formato de fila antes de escribirlos en la hoja de Google Sheets. Esto incluye limpiar y formatear adecuadamente los datos como nombres, direcciones y correos electrónicos.
  7. Automatización y Registro: El script registra en el Log cada paso importante, incluyendo la construcción de URL de la API, respuestas de la API, y errores, facilitando la depuración y el seguimiento del proceso de importación.
  8. Funciones Auxiliares para Mejorar la Eficiencia del Código: Se utilizan funciones auxiliares para tareas repetitivas o complejas, como addParamIfNotEmpty para construir la URL de la API con los parámetros adecuados, y autoResizeSheetColumns para ajustar automáticamente el tamaño de las columnas basándose en el contenido.
  9. Personalización y Extensibilidad: Aunque el código se presenta con un conjunto específico de campos y parámetros, es fácilmente personalizable y extensible para adaptarse a diferentes necesidades y requerimientos, haciendo que esta solución sea versátil para varios casos de uso.
  10. Seguridad y Privacidad: Al trabajar con información sensible de los pedidos y autenticación de API, es muy importante que tengas en cuenta la seguridad y privacidad de tus datos, recomendamos mantener las claves API y tokens de acceso en un lugar seguro y no expuestos en el código como lo hicimos en el ejemplo para facilitar su implementación inicial.

Un punto importante es el uso del parámetro fields, al principio del post (paso 2) te indicamos hacer una prueba a un endpoint, prueba ahora el mismo endpoint pero agrégale el parámetro fields con la información de los campos que quieres traer, ejemplo: https://[tu-tienda-shopify].myshopify.com/admin/api/2022-04/orders.json?limit=10&fields=order_number,financial_status,fulfillment_status,contact_email,shipping_address.

Puedes ver en la respuesta de la API que sólo trae los campos indicados, esto es muy útil para facilitar la lectura y mapeo de esos datos, en el ejemplo limitamos los resultados a los que estamos exportando a la hoja Pedidos.

Probando la solución

Para probarlo, basta con que guardes tu script y luego lo ejecutes desde Apps Scripts, te pedirá la autorización para utilizar las funciones necesarias y luego ejecutará el código agregando a la hoja Pedidos los datos que obtenga desde Shopify según los filtros que utilices.

Para exportar los datos de solo la hoja Pedidos puedes utilizar el link: https://docs.google.com/spreadsheets/d/[id-de-tu-planilla]/export?format=xlsx&gid=[id-de-la-hoja-de-tu-planilla], reeemplazando los datos correspondientes, en format puedes poner xlsx (excel), csv (separado por comas), tsv (separado por tabulaciones), ods (open document) o pdf.

Extendiendo la solución

Nosotros extendimos un poco este ejemplo agregando una hoja CONFIG con los distintos filtros, un botón para ejecutar el script y un enlace para la descarga de la hoja Pedidos y algunos campos que tenían valores fijos solicitados por nuestro courier.

Añadiendo una hoja CONFIG para filtrar las valores de los parámetros del Script

También agregamos un filtro de para procesar sólo la Región Metropolitana y validador de comunas (ciudades) poniendo un color rojo en la celda si la comuna no es encontrada en la lista, de esta forma podemos revisar si hay un problema en los datos que enviaremos al courier. Para la comparación tomamos las versiones "normalizadas" o "transliteradas" de las comunas, ya que al ser escritas por usuarios pueden venir de distintas formas (Ejemplo: ÑUÑOA > NUNOA, CHÉPICA > CHEPICA, OLLAGÜE > OLLAGUE, etc.). En la imagen que aparece a continuación pueden ver como detecta un problema en una Comuna "ÑUÑOS" que no existe y se debe revisar antes de enviar al courier:

Ejemplo Hoja Pedidos

En resumen, este flujo de trabajo no solo optimiza la gestión de pedidos desde Shopify hacia Google Sheets sino que también abre la puerta a una variedad de personalizaciones futuras. Al integrar eficientemente estos sistemas, puedes liberar tiempo valioso, reducir errores manuales y mejorar la satisfacción de tus clientes con procesos más rápidos y precisos.

¿Ya estás suscrito a Mente Digital? ¡Suscríbete y no te pierdas nuestro contenido!

Etiquetas

Juan Pablo Traverso

Profesional con más de 25 años de experiencias en diseño, creación y crecimiento de plataformas digitales, principalmente en el área de servicios financieros & ecommerce.