Como obtener el tamaño de imágenes con Google Apps Script

Puede haber ocasiones en que obtener el tamaño de imágenes usando Google Apps Script puede ser extremadamente útil para automatizar tareas, el problema es que esto es bastante difícil de conseguir (te reto a que lo encuentres :) ).  Aunque me ha costado bastante he conseguido desarrollar un manera de hacerlo y que me permite ahorra bastante tiempo diario al calcular los tamaños de grupos de imágenes.

He montado un spreadsheet con esta estructura, donde puedes poner la lista de url a las imagenes y al ejecutar el script se rellenan el height y el width de la imagen gracias a la magia de Google Apps Script.
como calcular tamaño de una imagen con Google Apps Script


Como conseguirlo?
En este caso concreto, el gran problema no es saber programar, sino encontrar la manera de poder obtener la información que queremos. Desde Google Apps Script es IMPOSIBLE conseguirlo directamente, no hay herramienta que te lo permita, pero, desde JavaScript si que es factible saber el tamaño de una imagen.

El código para saber el tamaño de una imagen con JavaScript seria tal que así:
function GetImagesize()
{
	var img = new Image();
	img.addEventListener("load", function()
            {
               var url = this.currentSrc;
               var width  = this.naturalWidth;
               var height = this.naturalHeight; 
		//Aqui hacemos lo queramos con ese tamaño	
            });
	img.src = url_de_tu_imagen;
}

Básicamente, creamos un objeto de tipo imagen (una de las pocas cosas de JavaScript que no podemos hacer en Google Apps Script), le marcamos que cuando la imagen este cargada obtenga el tamaño de la imagen correspondiente y le damos la url de la imagen a cargar.

Tal como hemos dicho esto no puede hacerse en Google Apps Script, pero... desde Google Apps Script si que podemos servir una web que use JavaScript. Es más, podemos hacer que esta web esta embedida dentro de un popup en un spreadsheet, de forma que cuando llamemos a este popup desde nuestro spreadsheet se realice todo el proceso de obtención de tamaños de imágenes. 

Ahora el problema que tendremos es que la obtención de los tamaños de las imágenes se producirá de forma asíncrona (se obtendrá cuando la imagen se haya cargado), de forma que esta información la tendremos que ir guardando hasta que no la podamos recuperar toda. La forma más fácil es usar la cache ( sessionStorage ) de JavaScript.

Finalmente solo necesitaremos transferir toda esta información desde JavaScript a Google Apps Script para poder guardarla en nuestro spreadsheet.

Como podéis ver, el proyecto es bastante más complicado de lo que parece, pero personalmente ha sido todo un orgullo poderlo completar.

Os adjunto todo el código del proyecto para que podáis ponerlo en práctica.

Espero que os sirva! 

No dudéis en hacerme llegar vuestras propuestas!

Code.gs
function onOpen()
{
  SpreadsheetApp.getUi().createMenu("SCRIPT")
          .addItem("CheckSize","CheckSize")
          .addToUi();
}

function AskForList()
{
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheeturl = spreadsheet.getActiveSheet();
  var images   = sheeturl.getRange(1,1,sheeturl.getLastRow(), 1 ).getValues();

  var elements = new Array();
  for(var i = 1 ; i < images.length ; ++i)
  {
      var url = images[i][0];
      var element = new Object();
      element.url    = url;
      elements.push(element);
  }
  return elements;
}

function ReportSizes(sizes)
{
   var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
   var sheet = spreadsheet.getActiveSheet();
   for(var i = 0 ; i < sizes.length ; ++i)
   {
     var size = sizes[i];
     sheet.getRange(i+2,2).setValue(size.Height);
     sheet.getRange(i+2,3).setValue(size.Width);
   }
}

function CheckSize()
{
  var html = HtmlService.createHtmlOutputFromFile('index').setWidth(100).setHeight(100);
  SpreadsheetApp.getUi().showModalDialog(html,"Image Size");
}

index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function getSize(url)
      {   
        var img = new Image();
        img.addEventListener("load", function()
            {
               var url = this.currentSrc;
               var width  = this.naturalWidth;
               var height = this.naturalHeight; 
               sessionStorage[url+"_width"] = width;
               sessionStorage[url+"_height"]= height;
            });
        img.src = url;
      }
      
      function ReciveDone()
      {
        document.getElementById("button").innerHTML = "<a> PROCESS END </a>";
      }

      function ReciveWaiting(list)
      {
         var errors = new Array();
         
         for(var i = 0 ; i < list.length ; ++i )
         {
            var element = list[i];
            var width  = sessionStorage[element.url+"_width"];
            var height = sessionStorage[element.url+"_height"];
            list[i].Height = height;
            list[i].Width  = width;
         }

         google.script.run.withSuccessHandler(ReciveDone).ReportSizes(list);
      }

      function ReciveList(list)
      {
         sessionStorage.clear();
         for(var i = 0 ; i < list.length ; ++i )
         {
            var element = list[i];
            getSize(element.url);
         }
         google.script.run.withSuccessHandler(ReciveWaiting).AskForList();
      }

      function AskForList()
      {
         document.getElementById("button").innerHTML = "";
         google.script.run.withSuccessHandler(ReciveList).AskForList();
      }
    </script>
  </head>
  <body>
     <div id="button">
        <input type="button" onclick="AskForList()" value="PROCEED">  
     </div>
  </body>
</html>

Cómo poner un popup en un spreadsheet con Google Apps Script


 Hay ocasiones en que nos puede resultar interesante poner un popup en un spreadsheet. Ya sea para mostrar información, para pedirle información al usuario o para permitir mostrar una pequeña web en el spreadsheet, los popups de Google Apps Script pueden ser de gran utilidad.

La forma de poder hacer popups con Google Apps Script se basa en cargar una web en un contexto modal y mostrarlo. Parece complicado pero realmente, es algo trivial.

Lo primero que necesitaremos es un html que es el que mostraremos en nuestro popup dentro del spreadsheet, aquí os dejo un ejemplo, pero en principio podríais poner cualquier web hecha en HTML5 que quisierais:

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
     <h1>Ejemplo de web en popup<h1>
      <select>
        <option value="1">Opcion 1</option>
        <option value="2">Opcion B</option>
        <option value="3">Tegucigalpa</option>
      </select>
  </body>
</html>

Esta web se comporta exactamente igual que una web que podamos servir directamente desde un spreadsheet, así que nos permitirá hacer cosas como comunicar desde JavaScript con el Google Apps Script del servidor. Con esto, está claro que la potencia del sistema es extrema.

Como serviremos este html en un popup del spreadsheet con Google Apps Script? Fácil, vamos a usar la función showModalDialog para servir la web que queramos como un popup del tamaño que queramos.

function onOpen()
{
  SpreadsheetApp.getUi().createMenu("SCRIPT")
          .addItem("ShowPopup","ShowPopup")
          .addToUi();
}

function ShowPopup()
{
  var html = HtmlService.createHtmlOutputFromFile('index').setWidth(300).setHeight(300);
  SpreadsheetApp.getUi().showModalDialog(html,"Ejemplo de popup");
}

Si copiáis estos ficheros en vuestro proyecto y ejecutáis veréis algo de este estilo:

Ejemplo de popup


Como ya os habréis imaginado esto da una funcionalidad adicional a nuestros spreadsheets realmente brutal. Nos permite crear con HTML5 interficies de usuario profesionales ya sea para mostrar información o pedirla.

Bajo mi forma de ver, es una de las características más potentes de Google Apps Script, es un tema que no tiene límites ni fin.

Espero que te haya servido, y recuerda, no vaciles en hacerme llegar tus dudas

Nos vemos




Como crear una biblioteca con Google Apps Script

Seguramente habrás oído hablar de las bibliotecas de Google Apps Script y te preguntarás si es algo que te puede servir en tus proyectos. Una biblioteca de Google Apps Script no es más que un proyecto al que le damos acceso al código desde otro proyectos. 

Para verlo claro, nada más sencillo que un ejemplo.

Abriremos un proyecto de código dentro de un spreadsheet (como hacemos en el resto de proyectos) y escribiremos una función que haga algo concreto, en mi caso he hecho una función muy sencilla que se encarga de sumar vectores:

/**
 * Función que se encarga de sumar vectores. No controla tamaños.
 * @param a Primer vector a sumar
 * @param b Segundo vector a sumar
 * @return Suma de los vectores a y b
 */
function VectorSum(a,b) 
{
  var c = new Array();

  for(var i = 0 ; i < a.length ; ++i)
  {
     c.push( a[i]+b[i] );
  }
  return c;
}

Una vez tengamos hecho este código vamos a publicarlo como librería.

Le vamos a a dar a Implementar > Nueva Implementación

Y seleccionaremos que la nueva implementación es una biblioteca


Y pondremos una descripción de para que sirve nuestra biblioteca.

Con ello conseguiremos una ruta de acceso a la biblioteca que es lo que usaremos para distribuirla y usarla en nuestros proyectos.

Para "instalar" la libreria en nuestro proyecto solo debemos crear un nuevo proyecto de código y darle al botón de la izquierda de + Bibliotecas.


Aquí veréis que una vez detecte el id de nuestra librería nos permitirá seleccionar la versión que tendremos que usar y (muy importante) el nombre que usaremos para el workspace, el identificador.

Todas las funciones de la biblioteca se llamarán a través de ese identificador. Una vez hemos "instalado" la librería en el nuevo proyecto podemos llamar a la función de la librería usando el nombre del workspace de esta manera:

function TestSum()
{
   var a = [1.0,2.0,3.0];  
   var b = [3.0,4.0,5.0];
   var c = MathLib.VectorSum(a,b);
   Logger.log(c);
}

Como podéis ver es una funcionalidad muy potente que nos permite organizar y limpiar el código, poderlo distribuir de una forma sencilla y tener un control bastante mayor sobre nuestras implementaciones.

Espero que os haya servido, no vacileis en hacerme llegar vuestras dudas.

Nos vemos!


Como generar números aleatorios con google apps script

En muchas ocasiones nos puede resultar muy útil generar números aleatorios cuando hacemos nuestros scripts de Google Apps Script: para juegos, para dar cierta aleatoriedad a nuestras respuestas o inventarnos un identificador único para todo aquello que necesitemos. 

Lo que os explicaré hoy no es un sistema criptográficamente seguro de generar números aleatorios con Google Apps Script, solo es una manera de generar números aleatorios de baja calidad estadística ( que para la gran mayoría de nuestros usos va a ser mucho más que suficiente )

Nuestro sistema se basará en el Math.random que todo lo que hace es generar un float entre 0.0 y 1.0  (este último no incluido). Teniendo este valor entre 0 y 1, podemos multiplicarlo, redondearlo y operarlo como queramos.

A fin de hacerlo fácil he creado esta función para que podáis tener números aleatorios con Google Apps Script de forma bastante fácil: 

function GetRandom(min,max)
{
  return Math.floor(Math.random()*(max -(min -1))) + min;
}

Le pasáis el mínimo y el máximo y ya tenéis el número aleatorio que os interesa.

Si estás aprendiendo Google Apps Script y vienes de lenguajes como C , tal vez una nomenclatura en que al random solo quieres pasarle el numero de elementos a sortear te va mejor. Si ese es el caso, aquí tienes el código: 

function crand(elements)
{
  return GetRandom(0,elements-1);
}

Como podéis ver el código es muy sencillo y fácil de seguir. Recordad que estas funciones no sirven para nada estadísticamente "serio" como por ejemplo criptografía, pero para vuestras pequeñas aplicaciones y juegos este código os irá fenomenal.

No vaciléis en hacerme llegar vuestras dudas

Nos vemos




Como aprender a multiplicar jugando



Este sencillo juego esta pensado para ayudar a los niños a aprender las tablas de multiplicar.

Se podría haber montado sin necesidad de Google Apps Script, pero creo que como ejemplo puede ser muy interesante y lo podéis usar como base para vuestros proyectos, tiene todo lo necesario para hacer prácticamente cualquier web.

Aquí os dejo el código:

Index.hmtl
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style type="text/css">
      button
      {
        text-decoration: none;
        padding: 20px;
        font-weight: 600;
        font-size: 40px;
        color: #ffffff;
        background-color: #1883ba;
        border-radius: 12px;
        border: 4px solid #0016b0;
        width: 130px;
      }
      button:hover 
      {
        text-shadow: 1px 1px 6px #fff;
        border-shadow: 
      }
      
      #question
      {
        width: 600px;
        padding: 10px;
        font-weight: 600;
        font-size: 30px;
        color: #44ff55;
        background-color: #f3f3bf;
        border-radius: 6px;
        border: 2px solid #0016b0;
        text-align:center;
      }

      #tabla
      {
        margin: 10px;
        width: 100px;
        padding: 10px;
        font-weight: 600;
        font-size: 40px;
        color: #ff4455;
        background-color: #33f3bf;
        border-radius: 6px;
        border: 2px solid #0016b0;
        text-align:center;
      }

      #countdown
      {
        margin: 10px;
        width: 50px;
        padding: 10px;
        font-weight: 600;
        font-size: 50px;
        color: #ff4455;
        background-color: #fff3bf;
        border-radius: 25px;
        border: 2px solid #ff16b0;
        text-align:center;
      }
      
      #error
      {
        margin: 10px;
        width: 400px;
        min-width: 400px;
        padding: 10px;
        font-weight: 600;
        font-size: 50px;
        color: #ffaabb;
        background-color: #776655;
        border-radius: 25px;
        border: 2px solid #ff16b0;
        text-align:center;
      }

      #accept
      {
        margin: 10px;
        width: 400px;
        min-width: 400px;
        padding: 10px;
        font-weight: 600;
        font-size: 50px;
        color: #aaffbb;
        background-color: #667744;
        border-radius: 25px;
        border: 2px solid #16ffb0;
        text-align:center;
      }

      audio
      {
        display: none
      }
</style>
    <script>
      var end = new Date();
      var process_blocked = 0;
      
      function Init()
      {
        setInterval(showRemaining, 1000);
        Accept();
      }

      function Accept()
      {
        document.getElementById("error").style.visibility  = "hidden";
        document.getElementById("tip").style.visibility    = "hidden";
        document.getElementById("accept").style.visibility = "hidden";

        var now = new Date();  
        end.setTime( now.getTime() + 10*1000 );
        process_blocked = 0;
      }

      function Error(error)
      {
        document.getElementById("error").innerHTML = error;
        document.getElementById("error").style.visibility  = 'visible';
        document.getElementById("accept").style.visibility = 'visible';
        process_blocked = 1;
      }

      function Response(str)
      {
          if(process_blocked)
            return;

          var splitted = str.split(",");
          var msg = splitted[0]; 
          document.getElementById("tabla").innerHTML    = msg.split("X")[0].trim();
          document.getElementById("question").innerHTML = msg;

          if(msg.indexOf("FINAL") != -1 )
          {
            var audio = document.getElementById("audioWIN");
            audio.play();

            document.getElementById("countdown").style.visibility = 'hidden';
          }

          if(splitted.length > 1)
          {
            var tab = splitted[1];
            document.getElementById("buttons").innerHTML = tab;
          }

          if(splitted.length > 2)
          {
            var audio = document.getElementById("audioFAIL");
            audio.play();

            var error = splitted[2];
            Error(error);
            return;
          }
          else
          {
            var audio = document.getElementById("audioOK");
            audio.play();
          }

          Accept();
      }

      function Push(value)
      {
        if (!process_blocked)
        {
          var question = document.getElementById("question").innerHTML;
          google.script.run.withSuccessHandler(Response).Answer(question,value);
        }

        var now = new Date();  
        end.setTime( now.getTime() + 10*1000 );
      }

      function showRemaining() 
      {
         if( process_blocked)
            return;

         var now = new Date();
         var distance = end.getTime() - now.getTime();
         if (distance < 0) 
         {
            Push(0);
            return;
        }
        var seconds = Math.floor(distance/1000);
        document.getElementById('countdown').innerHTML = seconds;
    }
    </script>

  </head>
  <body onload="Init()">
    <audio id="audioOK" controls>
      <source type="audio/wav" src="https://sampleswap.org/samples-ghost/SOUND%20EFFECTS%20and%20NOISES/VIDEO%20GAMES/carnival/27[kb]ranking.wav.mp3">
    </audio>
    <audio id="audioFAIL" controls>
      <source type="audio/wav" src="https://sampleswap.org/samples-ghost/SOUND%20EFFECTS%20and%20NOISES/Alarm%20Sounds/81[kb]vidgame-gliss-down.aif.mp3">
    </audio>
    <audio id="audioWIN" controls>
      <source type="audio/wav" src="https://sampleswap.org/samples-ghost/SOUND%20EFFECTS%20and%20NOISES/VIDEO%20GAMES/carnival/330[kb]bonus1.wav.mp3">
    </audio>

    <p> <a id='tabla'>2<a><a id='question'>2 X 2 =<a> <a id="countdown"></a></p>
    <div id='buttons'>
    <table>  
      <tr>
        <td><button onclick="Push(2)"> 2 </button></td>  
        <td><button onclick="Push(3)"> 3 </button></td>    
        <td><button onclick="Push(4)"> 4 </button></td>    
      </tr>
      <tr>
        <td><button onclick="Push(5)"> 5 </button></td>    
        <td><button onclick="Push(6)"> 6 </button></td>    
        <td><button onclick="Push(7)"> 7 </button></td>    
      </tr>
    </table>
    </div>
    <div id='info'>
      <p><a id='error'> </a> </p>
      <p><a id='tip'> </a> </p>
      <p><button id='accept' onclick="Accept()"> OK </button></p>
    </div>
    
  </body>
</html>


Code.gs

function doGet()
{
    return HtmlService.createTemplateFromFile('index')
        .evaluate() // El evaluate siempre debe estar antes del FrameOptions
        .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

function getRandomNumber(min, max)
{
   return Math.floor( Math.random() * (max - min) + min );
}

function Answer(question, value)
{
  var error = false;
  var result = "";
  var items = question.split("X");

  var num1 = items[0].trim();
  var num2 = items[1].split("=")[0].trim();
  var old1 = num1;
  var old2 = num2;
  var expected = num1*num2;

  if( expected == value )
  {
     num2++;

     if(num2 > 9)
     {
       num1++;
       num2 = 2;

       if(num1 > 9)
       {
         result = "FINAL!!!";
       }
     }
  }
  else
  {
     error = true;
     num2 = 2;
  }

  if ( result == "" )
  {
      result = num1 + " X " + num2 + " =";
  }

  var desired = num1*num2;

  var options = new Array();

  options.push(desired);

  for(var i = 0 ; i < 5 ; ++i)
  {
    var candidate = desired + getRandomNumber(-5,5);

    var ok = true;
    for(var j = 0 ; j < options.length && ok ; j++)
    {
      if( options[j] == candidate )
      {
        i--;
        ok = false;
      }
    }

    if( ok )
    {
      options.push( candidate );
    }
  }
  
  options.sort();

  result += ",";
  result += "<table>";
  result += "<tr>";
  var i = 0;
  for( ; i < options.length/2 ; ++i)
  {
    result += "<td><button onclick='Push(" + options[i] + ")'> " + options[i] + " </button></td>";    
  }
  result += "</tr>";

  result += "<tr>";
  for( ; i < options.length ; ++i)
  {
    result += "<td><button onclick='Push(" + options[i] + ")'> " + options[i] + " </button></td>";    
  }
  result += "</tr>";


  result += "</table>";

  if(error)
  {
     result += "," + question + " " + expected ;
  }

  return result;
}

No vaciléis en hacerme llegar vuestras propuestas. Si sabéis de algún niño que le cueste aprender las tablas de multiplicar no os lo penséis y probad con este juego

Nos vemos

Como seleccionar la fecha actual al abrir un spreadsheet con Google Apps Script

En muchas ocasiones nos puede ser útil que se haga una tarea automáticamente al abrir nuestro spreadsheet en referencia al día actual usando Google Apps Script: Marcar ciertas celdas, aplicar formatos, setear textos, etc...

En este ejemplo vamos a montar un sistema que cada día nos seleccione una columna con la fecha actual tal y como podemos ver en este imagen:

Autoselección de columnas



function onOpen()
{
   MarkToday();  
}

function MarkToday()
{
  var today = new Date();
  var today_day   = today.getDate();
  var today_month = today.getMonth();
  var today_year  = today.getFullYear();

  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); 
  var range = sheet.getRange(1,1,1,sheet.getLastColumn() );
  var caption = range.getValues()[0];

  //Buscamos el indice de columna que nos concuerda con el dia actual
  var index = 0;
  for(var i = 0 ; i < caption.length ; ++i)
  {       
      var d = new Date(caption[i]);

      //Compensamos la diferencia horaria entre el servidor de Google y nuestra situación.
      //De no compensar esta diferencia nos puede marcar dias diferentes de los reales.
      d.setTime( d.getTime() + 12*60*60*1000 );

      if(today_day == d.getDate() && today_month == d.getMonth() && today_year == d.getFullYear())
      {
        index = i+1;
        break;
      }
  }

  //Eliminamos marcas que pudieramos tener
  range.setBackground("white");

  //Marcamos el dia actual
  sheet.getRange(1,index).setBackground("yellow");
}

Como podéis ver el código no es complicado y se puede adaptar a lo que necesitéis. Se os ocurre alguna otra funcionalidad que se podría aplicar?

No vaciléis en hacerme llegar vuestras dudas.

Nos vemos


Como cambiar permisos de un fichero con Google Apps Script

En muchas ocasiones nos puede resultar útil poder modificar los permisos de un fichero de Google ( spreadsheets sobre todo ), así como copias para ciertos usuarios , o durante la preparación para distribuir información a un grupo de personas. Con Google Apps Script podemos automatizar de una forma muy sencilla la asignación de permisos a un fichero.

Primero de todo, hemos de tener claro que toda la gestión se hace a través de DriveApp, es decir, si queremos dar permisos a un spreadsheet o un doc, por ejemplo, primero deberemos obtener su id.

Ya veréis que es muy sencillo: 

function SetPermisos() 
{
    var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    var id = spreadsheet.getId();
    DriveApp.getFileById(id).setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
}

El tipo de acceso puede tener diferentes opciones:

tipos de acceso google apps script
ANYONE, ANYONE_WITH_LINK,DOMAIN,DOMAIN_WITH_LINK, PRIVATE

De la misma manera podemos asignar diferentes tipos de permisos:
Tipos de permisos Google Apps Script
COMMENT, EDIT, FILE_ORGANIZER,NONE,ORGANIZER,OWNER,VIEW


Como veis es muy sencillo cambiar permisos de un fichero de Drive con Google Apps Script.

No vaciléis en hacerme llegar vuestras dudas

Nos vemos!




Como leer datos de un webhook con Google Apps Script


Una aplicación bastante interesante de Google Apps Script es recibir callbacks de un WebHook. Un WebHook básciametne es un servicio web que atacará a la url que nosotros le digamos ( generalmente haciendo un POST ) cuando pase un cierto evento.

Es decir, un webhook nos informará de eventos que pasen en algún servicio externo: pagos recibidos, mensajes pendientes, errores en servicios, etc....

Ahora bien, una cosa que no está demasiado bien documentada y que siempre trae algún quebradero de cabeza es como obtener la información del callback. La solución es fácil, salvo que nos indiquen lo contrario el webhook llamará a nuestra url con un método POST cuyo payload será un JSON con la información que necesitamos. Cada servicio implementará su protocolo y su forma de enviar la información dentro del JSON, pero lo básico es lo que os acabo de explicar.

Así pues el código para procesar esa llamada es extremadamente fácil:

function doPost(e)
{
    var jsonString = e.postData.getDataAsString();
    var data = JSON.parse(jsonString);
    // AQUI VA TU GESTION
}

Con esté código ya tenéis la estructura básica para saber como procesar los JSON que nos vengan de un webhook. Si algo no lo tenéis claro os recomiendo que reviséis mi post sobre como trabajar con JSON en Google Apps Script

No vaciléis en hacerme llegar cualquier duda que tengáis.

Nos vemos



Como detectar que el usuario ha añadido una nueva línea con Google Apps Script


En muchas ocasiones nos puede resultar muy útil usar Google Apps Script para saber cuando alguien inserta una nueva fila. Ya sea Zapier generando una nueva fila, otro script que tengamos o que el mismo usuario inserten una nueva fila, con el código que os enseñaré podréis detectar este hecho y hacer lo que necesitéis (cambiar formatos de celdas, setear timestamps, crear logs, etc...)

function CrearTriggerOnChange()
{
    var sheet = SpreadsheetApp.getActive();
    ScriptApp.newTrigger("onChange")
      .forSpreadsheet(sheet)
      .onChange()
      .create();
}

function onChange(e)
{
   if(e.changeType=="INSERT_ROW")
   {
      SpreadsheetApp.getUi().alert("Se ha insertado una fila nueva");
   }
}

Usamos el evento onChange (parecido, pero diferente al evento onEdit) para detectar cuando alguien inserta una nueva fila. Este evento tiene muchos más usos, pero por ahora, con este ya podemos hacer.

En el ejemplo concreto que os muestro lo que ocurre es que cada vez que alguien cree una nueva fila aparecerá un mensaje informando que se ha creado una nueva fila, algo de este estilo:

Como detectar una nueva fila con Google Apps Script

En otros artículos os explicaré más usos del onChange.

Solo un último detalle. Al contrario del evento onEdit, el evento de onChange es necesario instalarlo. Es por ello que os he dejado la función de CrearTriggerOnChange. La tendréis que ejecutar una vez para que se instale el trigger ( solo una vez :) ). Si lo necesitáis tengo otro artículo que explica muy claramente como funcionan los triggers

No vaciléis en hacerme llegar vuestras dudas

Nos vemos





Tal vez te interese