domingo, 24 de junio de 2018

Como meter una imagen (una firma) desde un formulario a un documento

Con esta función, un formulario y una plantilla podemos hacer que los usuarios suban su firma (imagen) y se inserte en el lugar de un documento que decidamos. El código lo he metido en la hoja de cálculo unida al formulario y he activado el evento "al enviar el formulario"

(Hay unas cuantas partes interesantes en sí mismas:

  • Como conseguir datos del formulario
  • Como conseguir el ID de un archivo desde su URL
  • Como hacer público un archivo
  • Como crear una copia de una plantilla y meterla en una carpeta concreta )


function insertar_imagen(e) {
// Pillamos la imagen subida desde el formulario (Conseguimos por defecto la URL de dicha imagen)
var imagen= e.values[2]
// Utilizamos esta fórmula para extrar de la URL el ID
var id = imagen.match(/[-\w]{25,}/)
// Con el ID abrimos el archivo de imagen subido
var irudia = DriveApp.getFileById(id)
// Le damos permiso para que sea accesible por cualquiera (Parece que sino no funciona el código)
irudia.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW)

// Conseguimos un ID de una carpeta para meter el nuevo documento
   var folderID='1ROGPRhAgtozzpcQr0jM_ekwBiuFsaw5E' //Este ID debería ir fuera del código
   var folder = DriveApp.getFolderById(folderID)
  // Creamos una copia de la plantilla y abrimos el documento
  var nuevoID= DriveApp.getFileById('1hIjlBCktJdIy8GuDB72XPW-H3_Uw_WhygnbyYLq0sbA').makeCopy('Firmado',folder).getId()
  var doc = DocumentApp.openById(nuevoID)


  // Conseguimos todas las partes del documento, mayormente los párrafos
  var todos = doc.getBody().getNumChildren()
  // Creamos una variable que apunte al cuerpo del documento, no a todo el en su conjunto
  var body = doc.getBody()
// Creamos un array para ir metiendo el texto de cada párrafo, con intención de buscar la marca que hemos elegido ### y sustituirla por la firma
  var el=[]

  // Recorremos todos los elementos del documento
 for (var j=0; j<todos; j++){
 // Metemos por cada vuelta el elemento en una variable
 var elemento = doc.getChild(j)
 // Apuntamos que tipo de documento es en otra variable
 var tipo = elemento.getType()
//Utilizamos condicional para solamente quedarnos con los párrafos
 if (tipo=='PARAGRAPH'){
 // Si es párrafo, conseguimos el texto interno
 el[j]= elemento.getText()
// Buscamos en el texto la marca mediante un condicional
   if (el[j]=='###'){
// Borramos dicho texto cuando lo encontramos
   elemento.removeFromParent()
 // Y lo sustituimos por la imagen de la firma
      body.insertImage(j, irudia)
   }
 }
 }
}

jueves, 21 de junio de 2018

Arrays (Segunda parte)

Hoy he vuelto a tener problemas con arrayformula()
Ya escribí este artículo sobre el tema hace unos días. Podéis consultarlo también

He utilizado una vlookup() para, dado un correo electrónico, me coja de una lista el nombre oficial de dicha persona.
La fórmula me ha quedado así: =vlookup(Q3,responsables!$A:$D,3,false)) >>> Busca el correo que queremos y que está en la celda Q3 entre la lista de responsables y sus datos que tenemos en la pestaña responsable, en la columna A. Y me da el nombre que está en la columna 3 de la lista A:D de responsables, osea en la columna C 

Como tenía que repetir la misma fórmula para todos los correos de la columna Q y, además, para los nuevos que se vayan añadiendo, no me bastaba con arrastrar la columna, sino que debía utilizar la fórmula arrayformula()

Inconsciente de mí he cambiado la fórmula a
=arrayformula(vlookup($Q$3:$Q,responsables!$A:$D,3,false)), queriendo conseguir que pillara cualquier dato de la columna Q.

¡Maldita sea! ha empezado la barra de progreso a moverse y ha creado hasta 50000 filas. Y, a pesar de eliminarlas como cucarachas, la hoja de cálculo ha vuelto a crearla y así hemos estado luchando un buen rato. ¿Quién se ha rendido? :(

He cerrado la hoja de cálculo. Punto!!!

Pero... necesitaba ese array. Por lo que he probado a cambiar un poco la fórmula, metiéndolo un condicional para que solamente se aplicase el vlookup cuando hubiese algo en la columna Q.

Me ha quedado así:
=arrayformula(if($Q$3:$Q="","",vlookup($Q$3:$Q,responsables!$A:$D,3,false)))

y... ¡¡¡¡¡ha funcionado!!!!!


A partir de ahora, meteré siempre en las arraysformulas un freno, un condicional que limite su aplicación, osea que quite los datos vacíos.

Os lo recomiendo.


miércoles, 20 de junio de 2018

Problemas con las referencias y función indirect

A veces me ha ocurrido que meto en una fórmula una referencia tal que así:

=iferror(join(char(10);filter(ekintzak!$F$2:$F;ekintzak!$G$2:$G=B4;len(ekintzak!$G$2:$G)>0)))

Es decir con referencias que empiezan en la línea 2, para que no cuente dentro del filtro las cabeceras de columna y...
si borro los datos y empiezo de nuevo a meterlos la fórmula, de forma casí a mala leche, va cambiando el principio de la referencia a la línea 3, luego a la 4, etc... justo con el resultado que nunca se referencia a los datos que existen, sino a los que no existen todavía. En mi caso haría esto:
  • Meto un dato en la línea 2 de la pestaña "ekintzak" 
  • La fórmula cambia a
 
=iferror(join(char(10);filter(ekintzak!$F$3:$F;ekintzak!$G$3:$G=B4;len(ekintzak!$G$3:$G)>0)))

Y si meto algo en la línea 3, la fórmula coherente y malignamente se convierte en:

=iferror(join(char(10);filter(ekintzak!$F$4:$F;ekintzak!$G$4:$G=B4;len(ekintzak!$G$4:$G)>0)))

El resultado es que nunca hay nada en el filtro que queremos que aparezca.

La solución que he encontrado es utilizar la función indirect. Gracias a ella, consiguo que el número 2 se mantenga siempre estático:



=iferror(join(char(10);filter(indirect("ekintzak!$F$"&2&":$F");indirect("ekintzak!$G$"&2&":$G")=B4;len(indirect("ekintzak!$G$"&2&":$G"))>0)))

Dicha función es muy potente, ya que permite crear referencias con mezcla de partes fijas y dinámicas y, en este caso, de partes fijas que no hacen caso a lo dinámico. Pero tiene dos inconvenientes:
  • No es fácil de construir, ya que hay que trozear las referencias, meter entre comillas el texto fijo, unirlo al dinámico con & y volver a abrir y cerrar comillas si hay mas texto fijo detrás.
  • Una vez escrita la función es difícil de interpretar
(Mientras escribía el artículo me he dado cuenta que podía haber metido todas las referencias dentro de indirect entre comillas, sin sacar el número y quedaría así:
=iferror(join(char(10);filter(indirect("ekintzak!$F$2:$F");indirect("ekintzak!$G$2:$G")=B4;len(indirect("ekintzak!$G$2:$G"))>0)))

por lo que, simplemente, gracias a indirect, he conseguido que las referencias no cambien de forma dinámica, sean totalmente estables y no les influyan los nuevos registros)

martes, 12 de junio de 2018

Convertir casillas de verificación ☑️ en selección múltiple 🔘

No hace mucho, Google creó la opción de incluir casillas de verificación en las hojas de cálculo. Os muestro un ejemplo:






En este caso utilizo las casillas para que el usuario decida si un correo se manda en castellano, en euskera o en bilingüe.
Pero, mi problema es que, como son casillas de verificación, no quiero que me marquen mas de una, sino que cuando marquen una las restantes se desmarquen.

Las casillas no tienen por defecto dicho comportamiento, ya que puedes marcar una, varias o todas.

Mediante código podemos conseguir que se comporten como selección múltiple (según el lenguaje de los formularios de drive) o radio buttons ( según html), es decir como:
🔘


Para conseguirlo vamos a utilizar la función onEdit(event) que viene por defecto en Google Script

Esta función corre cada vez que hay un cambio en la hoja de cálculo, por lo que, con una estructura condicional, haremos que cada vez que hagamos clic en una casilla de verificación los valores de todas las demás pasen a "false"

function onEdit(event) {
// Gracias al argumento "event" pillamos la celda que cambia en cada momento
     var celda_cambiada= event.source.getActiveRange().getA1Notation();
//  Enlazamos a la hoja de cálculo activa y la pestaña que nos interesa    
     var aplicacion = SpreadsheetApp.getActiveSpreadsheet()
     var pestana = aplicacion.getRangeByName('correo_plantilla').getSheet()
 

// Por medio de switch tenemos un condicional con más de dos opciones, más claro y eficaz que IF   
     switch(celda_cambiada) { // comparamos la celda cambiada y creamos tres casos
    case 'C15':
        pestana.getRange('c16').setValue(false) // hacemos que la c16 se desmarque
        pestana.getRange('c17').setValue(false)
// hacemos que la c17 se desmarque 
       break; // Salimos del código condicional
    case 'C16':
        pestana.getRange('c15').setValue(false)
        pestana.getRange('c17').setValue(false)
        break;
    case 'C17':
        pestana.getRange('c15').setValue(false)
        pestana.getRange('c16').setValue(false)
        break;
    default:
        Browser.msgBox('Mala puntería')
}
}

lunes, 4 de junio de 2018

Mandar correo con datos de formulario

Os muestro una pequeña función para que cuando se mande un formulario le llegue al jefe de estudios (al correo que queramos) aviso de ello. Así no tiene que estar pendiente de mirar las respuestas todo el día. 

function onFormSubmit(e) {
  var aplicacion = SpreadsheetApp.getActiveSpreadsheet()
  var datos= aplicacion.getRangeByName('datos').getSheet()

// Conseguimos el correo al que queremos mandar, que debe estar escrito en una pestaña de la hoja 
de cálculo, en este caso en la celda B1
   var correo = datos.getRange('B1').getValue()


// Conseguimos los datos del formulario, cada e.values(número] es una pregunta  
var nombre= e.values[1]
    var dia= e.values[2]
      var sesiones= e.values[3]
        var razon_ausencia= e.values[4]
          var trabajo= e.values[5]
            var negociar= e.values[6]
trabajo= trabajo.replace(/\n/g, "<br>") // En campos de texto de formulario, convertimos los saltos de //línea para  que en el correo los mantenga
 

// Escribimos el mensaje en html y así podemos hacer saltos de línea y negrita               
  var mensaje= 'Acaba de llegar una ausencia de profesorado <br><br>Aquí tienes los datos'+
  '<br><br><b>Nombre:</b> '+nombre+
    '<br><b>Día:</b> '+dia+
        '<br><b>Sesiones:</b> '+sesiones+
            '<br><b>Motivo:</b> '+razon_ausencia+
                '<br><b>Trabajo:</b> '+trabajo+
                    '<br><b>¿Has acordado algo con algún otro profesor?:</b> '+negociar
 

// Gracias a la siguiente línea de código, podemos utilizar html                   
  var opciones= {htmlBody: mensaje}


// Mandamos el mensaje
  MailApp.sendEmail(correo, 'Se acaba de madar una ausencia de profesorado' , 'Eso mismo',opciones)
}


Pero para poder hacer correr esta función, necesitamos activar el activador de la hoja de cálculo al enviar el formulario

 

sábado, 2 de junio de 2018

Mandar correo

Google script puede ser muy potente ya que desde un código se pueden utilizar todos los servicios de Google: Hojas de cálculo, documentos, correo, carpetas, calendar, etc...

Gracias a esto podemos hacer que una función, por ejemplo, coja datos de un formulario, los mezcle con los de una hoja de datos y mande por correo un documento convertido a pdf.

En este artículo me voy a limitar a la parte de mandar correos.

Un correo simple

Tan sencillo como utilizar la clase MailApp (También tenemos GmailApp, pero está mas orientada a las funciones de Gmail y no las necesitamos aquí)

var correo_responsable = 'alguien@educacion.navarra.es'
MailApp.sendEmail(correo_responsable, 'Informe de mantenimiento', 'Aquí está el informe del aviso de mantenimiento')
Normalmente, la variable de correo viene del formulario o de una lista de correos que tenemos en la hoja de cálculo y, si utilizamos un bucle, podemos enviar varios correos a la vez

Un correo con formato


Para que el mensaje del correo enviado pueda tener saltos de línea, estilos de letra, etc.. necesitamos crear una variable tal que así:


  var opciones= { htmlBody: contenido_email}

El código quedaría así:
var contenido_email = 'Aquí está el informe del aviso de mantenimiento' 
  var opciones= { htmlBody: contenido_email}
MailApp.sendEmail(correo_responsable, 'Informe de mantenimiento',contenido_email, opciones)

Como veis sacamos el mensaje de la línea de MailApp y lo definimos en una variable que le hemos puesto el nombre de contenido_email. También hemos definido la variable opciones y la hemos metido como cuarto argumento en MailApp.

Al definir en opciones el argumento htmlBody podemos utilizar todas las etiquetas de html para dar formato al mensaje, por ejemplo:

var contenido_email= 'Aquí está el informe del aviso de mantenimiento<br>El plazo para contestar es de <b><font color="blue">10 días</font></b>' 

El correo quedaría así:


 Aquí está el informe del aviso de mantenimiento
El plazo para contestar es de 10 días 


En opciones hemos utilizado htmlBody: pero tenemos otras que también son interesantes, como: (copiadas de la página: https://developers.google.com/apps-script/reference/mail/mail-app)

attachmentsBlobSource[]an array of files to send with the email
bccStringa comma-separated list of email addresses to BCC
bodyStringthe body of the email
ccStringa comma-separated list of email addresses to CC
htmlBodyStringif set, devices capable of rendering HTML will use it instead of the required body argument; you can add an optional inlineImages field in HTML body if you have inlined images for your email
inlineImagesObjecta JavaScript object containing a mapping from image key (String) to image data (BlobSource); this assumes that the htmlBody parameter is used and contains references to these images in the format <img src="cid:imageKey" /> (see example)
nameStringthe name of the sender of the email (default: the user's name)
noReplyBooleantrue if the email should be sent from a generic no-reply email address to discourage recipients from responding to emails; this option is only possible for G Suite accounts, not Gmail users
replyToStringan email address to use as the default reply-to address (default: the user's email address)
subjectStringthe subject of the email
toStringthe address of the recipient

Crear PDF horizontal

El código que vamos a mostrar aquí no ha sido creado por nosotros, sino que lo hemos encontrado en Internet. Objetivo: Publicar una pesta...