Integración de Speckle con Power BI usando Google Sheets y Google Apps Script

En el mundo de la construcción, la capacidad de gestionar y analizar datos en tiempo real es fundamental para tomar decisiones rápidas y basadas en evidencia. Sin embargo, las herramientas BIM como Revit suelen estar aisladas, lo que limita el acceso y análisis de esos datos desde plataformas más accesibles como Google Sheets.

¿Qué te contaré aquí?

Desafío

Obtener y manipular datos BIM de manera eficiente y sin necesidad de realizar procesos manuales repetitivos puede ser un desafío. Los datos relevantes de las distintas categorías del proyecto se dispersan en diferentes sistemas y herramientas, lo que hace que su centralización y análisis sea una tarea tediosa y propensa a errores.

Tecnologías

  • Revit: Para modelado y exportación de datos.
  • Power BI: Para análisis y visualización.
  • Google Sheets: Para almacenar y transformar datos.
  • Speckle Viewer: Para integrar la visualización 3D.

Flujo de datos

He desarrollado un flujo automatizado que integra los datos BIM de Speckle directamente en Google Sheets con el uso de la API GraphQL de Speckle. Este proceso permite:

  • Conectar con Speckle: Usando GraphQL, se obtienen datos de categorías específicas como «Armazón estructural», «Muros», «Pilares», entre otras.
  • Automatizar la extracción de datos: Los objetos BIM se extraen y se organizan en un formato tabular, facilitando la consulta y manipulación en Google Sheets.
  • Crear hojas de importación: Cada importación se documenta en una hoja nueva con detalles como el tipo de objeto, categoría, sector, etapa, volumen, etc.
  • Registrar el proceso: La importación queda registrada en una hoja de «Registro de Importaciones», lo que asegura un historial detallado de cada carga de datos, con fecha, usuario y cantidad de registros importados.
				
					function getAllObjectsFromBranch(streamId, objectId, token) {
  const url = "https://speckle.xyz/graphql";
  const categories = ["Armazón estructural", "Suelos", "Muros", "Pilares estructurales", "Cimentación estructural"];
  const now = new Date();
  const dataToExport = [["Speckle Type", "Type", "Family", "Category", "Level Name", "Level Elevation", "Object ID", "Sector", "Etapa", "Volumen"]];
  
  // Obtenemos la fecha y hora para nombrar la nueva hoja
  const fechaImportacion = now.toISOString().replace(/T/, ' ').replace(/\..+/, '');
  const nuevaHojaNombre = `Importación ${fechaImportacion}`;

  // Obtener el spreadsheet activo
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  // Crear una nueva hoja para esta importación
  let nuevaHoja = ss.insertSheet(nuevaHojaNombre);
  Logger.log(`Hoja '${nuevaHojaNombre}' creada para los datos de la nueva importación.`);

  // Iterar sobre cada categoría y obtener los objetos de Speckle
  categories.forEach(category => {
    const graphqlQuery = JSON.stringify({
      query: `
        query Select($streamId: String!, $objectId: String!, $depth: Int!, $myQuery: [JSONObject!], $select: [String]) {
          stream(id: $streamId) {
            object(id: $objectId) {
              children(depth: $depth, query: $myQuery, select: $select, limit: 5000) {
                totalCount
                objects {
                  data
                }
              }
            }
          }
        }
      `,
      variables: {
        streamId: "tu streamId",
        objectId: "tu objectId",
        depth: 5,
        myQuery: [{ field: "category", operator: "=", value: category }],
        select: ["speckle_type", "type", "family", "category", "level.name", "level.elevation", "parameters"]
      }
    });

    const params = {
      method: "POST",
      payload: graphqlQuery,
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer " + "token",
      },
      muteHttpExceptions: true
    };

    try {
      const response = UrlFetchApp.fetch(url, params);
      const jsonData = JSON.parse(response.getContentText());

      if (jsonData.errors) {
        Logger.log(`Error en la respuesta para categoría '${category}': ${JSON.stringify(jsonData.errors)}`);
        return;
      }

      const objects = jsonData.data?.stream?.object?.children?.objects || [];

      // Extraer y agregar los datos al arreglo dataToExport
      objects.forEach(obj => {
        const data = obj.data || {};
        dataToExport.push([
          data.speckle_type || null,
          data.type || null,
          data.family || null,
          data.category || null,
          data.level?.name || null,
          data.level?.elevation || null,
          data.id || null,
          data.parameters?.["03f5c09d-c158-4945-bf08-08d5b12743a7"]?.value || null, // Sector
          data.parameters?.["ef6871a2-162e-459e-89da-e42727703e26"]?.value || null, // Etapa
          data.parameters?.["HOST_VOLUME_COMPUTED"]?.value || null // Volumen
        ]);
      });

    } catch (error) {
      Logger.log(`Error fetching data para categoría '${category}': ${error}`);
    }
  });

  // Pegamos los datos en la nueva hoja
  nuevaHoja.getRange(1, 1, dataToExport.length, dataToExport[0].length).setValues(dataToExport);
  Logger.log("Datos importados y pegados en la nueva hoja.");

  // Registrar la importación en la hoja "Registro de Importaciones"
  const logSheetName = "Registro de Importaciones";
  let logSheet = ss.getSheetByName(logSheetName);

  if (!logSheet) {
    // Si no existe, crear la hoja de registro
    logSheet = ss.insertSheet(logSheetName);
    logSheet.appendRow(["Fecha de Importación", "Hoja de Datos", "Usuario", "Cantidad de Registros"]);
    Logger.log(`Hoja '${logSheetName}' creada para el registro de importaciones.`);
  }

  // Calcular la cantidad de registros importados (sin contar la fila de encabezados)
  const registrosImportados = dataToExport.length - 1;

  // Obtener el email del usuario actual
  const user = Session.getActiveUser().getEmail();

  // Agregar una nueva fila al registro
  logSheet.appendRow([fechaImportacion, nuevaHojaNombre, user, registrosImportados]);

  Logger.log("Registro de importación actualizado.");
}

				
			

Visualización en Power BI

Los datos tabulares se integraron en Power BI, donde se desarrollaron dashboards dinámicos y visualizaciones interactivas. Esto permitió a los equipos obtener insights clave sobre el progreso del proyecto, costos y cumplimiento de objetivos.

Demo y Resultados

Con esta solución, se mejora la accesibilidad de los datos BIM, permitiendo a los equipos de administración, planificación y finanzas trabajar con datos actualizados y organizados de manera más eficiente. La automatización del proceso reduce errores manuales y permite que todos trabajen con la misma información en tiempo real.

¡Contáctame! Si deseas implementar esta solución o explorar cómo mejorar tus flujos de trabajo de datos en el entorno BIM, estaré encantado de ayudarte.

Scroll al inicio