Como crear un PDF de una selección con Google Apps Script


Hace ya tiempo expliqué como generar PDFs con Google Apps Script pero era una forma muy simple de hacerlo. Para ser claros, se quedaba corto en muchos aspectos. Por suerte lo que os expliqué no es la única forma de generar PDFs con Google Scripts.

Existe un servicio dentro de Google que permite exportar el rango que quieras en formato PDF, pudiéndolo formatear como quieras. Es un poco más complejo que lo último que os expliqué pero su potencial es inacabable. Con un ejemplo lo veréis claro.

En este caso, deberíais seleccionar un rango de un spreadsheet y llamar a este script. Lo podéis modificar fácilmente para conseguir lo que queráis.

function ExportPDF() 
{
     var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
     var sheet           = spreadsheet.getActiveSheet();
     var range          = sheet.getActiveRange();

  var exportUrl = 'https://docs.google.com/spreadsheets/d/' + spreadsheet.getId()
      + '/export?exportFormat=pdf&format=pdf'
      + '&size=FOLIO'                  //Tamaño del papel: 0-LETTER,1-TABLOID,2-LEGAL,3-STATEMENT,4-EXECUTIVE,5-FOLIO,6-A3,7-A4,8-A5,9-B4,10-B5
      + '&portrait=false'              //Orientación: False - Horizontal / True - Vertical
      + '&fitw=true'                   //Forzamos que quepa correctamente en el ancho de la hoja
      + '&top_margin=1.75'             //Margen Superior          
      + '&bottom_margin=1.75'          //Margen inferior         
      + '&left_margin=1.5'             //Margen Izquierdo          
      + '&right_margin=1.5'            //Margen Derecho        
      + '&sheetnames=false'            //Marcamos si se ha de poner el nombre de las hojas o no
      + '&printtitle=false'            //Marcamos si se ha imprimir el titulo del documento o no
      + '&pagenum=false'               //Indicamos si se de pintar el número de página
      + '&gridlines=true'              //Marcamos si queremos que se vean las lineas de las celdas o no   
      + '&fzr=FALSE'                   //Frozen: marcamos si las cabeceras se han de ir imprimiendo o no en las consecutivas hojas
      + '&gid=' + sheet.getSheetId()   //Identificador del tab
      + '&r1=' + (range.getRow() - 1)  //Margenes del rango seleccionado
      + '&r2=' + range.getLastRow()
      + '&c1=' + (range.getColumn() - 1)
      + '&c2=' + range.getLastColumn();
       
  //Hacemos la petición para pedir el PDF
  var response = UrlFetchApp.fetch(exportUrl, {  muteHttpExceptions: true, headers: { Authorization: 'Bearer ' +  ScriptApp.getOAuthToken()  } } );
  
  //Creamos el PDF con el binario que nos devuelve la petición anterior
  var blob = response.getBlob();
  blob = blob.setName(spreadsheet.getName());
  var pdfFile = DriveApp.createFile(blob);
  SpreadsheetApp.getUi().alert(pdfFile.getUrl());
}

Una vez tenéis hecho el PDF podéis enviarlo por correo , guardarlo en una carpeta de Drive o dejarlo disponible para que alguien se lo baje de una web, las posibilidades son infinitas.

Espero que os haya servido

No vaciléis en hacerme llegar vuestras dudas

Nos vemos

NOTA: Tal vez también te interesa convertir  GDocs a PDF

47 comentarios:

  1. Hola Amigo!

    He intentado ponerlo en practica, pero siempre me arroja el siguiente error:

    https://drive.google.com/file/d/15hDKZ3mkEGvwttIGsf9TpMaeL9T7bS_e/view?usp=drivesdk

    Me podeis orientar?

    Mil gracias

    ResponderEliminar
    Respuestas
    1. Te he pedido acceso al documento. Así sin más no soy capaz de ver lo que te pasa.

      Dame acceso y te lo miro


      Nos vemos

      Eliminar
    2. Ya te lo he compartido!

      Muchas gracias

      Eliminar
  2. Que problema te da? Acabo de probarlo y me va perfecto : https://drive.google.com/file/d/1RyzMQw7Fy3_QxLg7kxyT_bSYY78YrJq1/view

    Tenias un rango seleccionado en el momento de ejecutar el script?

    Nos vemos

    ResponderEliminar
  3. como se hace para especificar donde se guarda, gracias.

    ResponderEliminar
    Respuestas
    1. Esta duda también la tengo yo, quedo atento si pudiste dar con el objetivo :) saludos

      Eliminar
    2. Disculpad que no vi el comentario. En este link vereis como se hace para especificar donde guardarlo : https://www.tutorialesgoogleappscripts.com/2020/10/como-generar-un-pdf-de-hojas-seleccionadas-con-google-apps-script.html

      Eliminar
  4. Me funciona perfectamente para exportar el rango a PDF! Pero no sé cómo incluirlo en el MailApp para enviarlo como archivo adjunto! Se que hay otro artículo explicando como enviar el correo, pero no encuentro la forma de unir ambos. Agradecería enormemente la ayuda del que pueda lograrlo! Saludos

    ResponderEliminar
    Respuestas
    1. Acabo de hacerte este artículo solo para ti :) : https://www.tutorialesgoogleappscripts.com/2020/10/como-generar-un-pdf-y-enviarlo-por-email-con-google-apps-script.html

      Eliminar
    2. Eres grande crack! Te has ganado un fiel seguidor de tu trabajo. Muchas gracias :)

      Eliminar
    3. Para eso estamos! Cualquier cosa hazmela saber. Si te gusta lo que hago recuerda que puedes seguirme en twiter y facebook :)

      Eliminar
    4. Quiero contarte que estoy bastante entusiasmado con esto, pues tengo semanas intentando dar con el resultado.

      Ya puse en práctica el código que compartiste en https://www.tutorialesgoogleappscripts.com/2020/10/como-generar-un-pdf-y-enviarlo-por-email-con-google-apps-script.html

      Sin embargo, al no ser programador, hay varias cosas con las que no estoy familiarizado, y, por ende, me cuesta un poco entender la naturaleza del código, por lo cual tengo varias dudas: cómo funciona la variable "tab"? pregunto porque más adelante dice .getSheetByName(tab)y no tengo ninguna sheet con ese nombre, por lo cual no sé si deba crearla o si se genera sola.

      Por otro lado, al ejecutar la funcion SendPDF me genera el siguiente error: Exception: "Se produjo un error inesperado al obtener el método o la propiedad getFolderById en el objeto DriveApp". Y, nuevamente, por no entender la naturaleza del código no sabría qué hacer para resolverlo :(.

      Agradezco mucho tu apoyo, compartiré con la comunidad el enlace al archivo que llevo meses trabajando, que es un sistema de gestión de ordenes de compra a proveedores, por si alguien desea hacer algo parecido. https://docs.google.com/spreadsheets/d/1e_uhHmvZiXxirMqaLLkNkxwRUTAX_3CuBRi1FRnasNA/edit#gid=829165323

      Un fuerte abrazo crack!

      Eliminar
    5. He montado un artículo para explicarte un poco esos conceptos, espero que te sirva: https://www.tutorialesgoogleappscripts.com/2020/11/conceptos-basicos-de-google-apps-script.html

      Eliminar
  5. Oye, me parece genial todo el contenido que generas y la gran ayuda que das, eres un grande, tus tutos me ayudaron muchisimo, muchas gracias :)

    ResponderEliminar
    Respuestas
    1. Para eso estamos! Gracias por ese tipo de comentarios.

      Nos vemos!

      Eliminar
  6. Hola, gracias por el código.
    Tengo la siguiente consulta, quiero ajustar el tamaño de la página al mismo tamaño que el rango.
    Al momento de definir "&size=" si me permitió definir una media exacta, por ejemplo 10x30, pero ahora tengo la necesidad que el valor 30 sea variable, es decir calcular el valor real de altura del rango porque algunas celdas de mi rango aumentaron de altura. definí una variable altura con el método getHeight pero no reconoce el código cuando escribo &size = 10xaltura. gracias por tu ayuda

    ResponderEliminar
  7. Hola, quiero guardar especificamente una sola hoja de sheets en pdf, pero solo solo he podido guardar todas las hojas, no he encontrado la manera de que sea solo un hoja en especifico, gracias por tu ayuda

    ResponderEliminar
    Respuestas
    1. Hola,

      Lo que comentas es raro... seguro que estas usando el código de este post? Que yo recuerde este código exportaba solamente un tab... Dime algo y lo miramos.

      Eliminar
    2. A muy malas puedes mirarte este artículo que también debería funcionarte: https://www.tutorialesgoogleappscripts.com/2020/10/como-generar-un-pdf-de-hojas-seleccionadas-con-google-apps-script.html

      Eliminar
  8. hola!
    Sirve este script para usarlo en la App HCG en un movil con Android?
    Gracias por responder.
    Felicitaciones por tus publicaciones!!!
    Un abrazo.
    Victor

    ResponderEliminar
  9. Hola!
    Antes que nada quiero expresar mi reconocimiento a la calidad y claridad de tus publicaciones...
    He adaptado tu scrip para satisfacer mis necesidades...quedando el mismo como sigue...:

    function Nota2PDF()

    {
    var Ss = SpreadsheetApp.getActiveSpreadsheet();
    var NOTAS= Ss.getSheetByName("NOTA");
    var Rango2export= NOTAS.getRange("A15:G15")
    var NombrePDF=NOTAS.getRange("B8").getValue();
    var carpetas=DriveApp.getRootFolder().getFoldersByName("NOTIFICACIONES");
    if(carpetas.hasNext())
    {
    var folder=carpetas.next();
    }
    else
    {
    var folder=DriveApp.getRootFolder().createFolder("NOTIFICACIONES");
    }
    var exportUrl = 'https://docs.google.com/spreadsheets/d/' + Ss.getId()
    + '/export?exportFormat=pdf'
    + '&size=8' //Tamaño del papel: 0-LETTER,1-TABLOID,2-LEGAL,3-STATEMENT,4-EXECUTIVE,5-FOLIO,6-A3,7-A4,8-A5,9-B4,10-B5
    + '&portrait=True' //Orientación: False - Horizontal / True - Vertical
    + '&fitw=true' //Forzamos que quepa correctamente en el ancho de la hoja
    + '&top_margin=0.05' //Margen Superior
    + '&bottom_margin=0.05' //Margen inferior
    + '&left_margin=0.05' //Margen Izquierdo
    + '&right_margin=0.05' //Margen Derecho
    + '&sheetnames=false' //Marcamos si se ha de poner el nombre de las hojas o no
    + '&printtitle=false' //Marcamos si se ha imprimir el titulo del documento o no
    + '&pagenum=false' //Indicamos si se ha de imprimir el número de página
    + '&gridlines=true' //Marcamos si queremos que se vean las lineas de las celdas o no
    + '&frz=false' //Frozen: marcamos si las cabeceras se han de ir imprimiendo o no en las consecutivas hojas
    + '&gid=' + NOTAS.getSheetId() //Identificador del tab
    + '&r1=' +Rango2export.getRow(); //Margenes del rango seleccionado
    + '&r2=' +Rango2export.getLastRow();
    + '&c1=' +Rango2export.getColumn();
    + '&c2=' + Rango2export.getLastColumn();
    //Petición para pedir el PDF
    var response = UrlFetchApp.fetch(exportUrl, { muteHttpExceptions: true, headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() } } );
    //Crear PDF con el binario que devuelto en la petición anterior
    var blob = response.getBlob();
    //Poner nombre PDF
    blob = blob.setName(NombrePDF);
    var pdfFile = DriveApp.createFile(blob);
    //mover PDF a la carpeta correspondiente
    pdfFile.moveTo(folder);
    //DriveApp.getFileById(pdfFile.getId()).setTrashed(true);
    }

    Mi inquietud es que no logro que sólo exporte el rango Rango2export...
    Alguna sugerencia?
    Agradezco tu tiempo..
    Victor. wsp +54 9351 529-8474

    ResponderEliminar
    Respuestas
    1. Buenas, tambien estoy con el mismo dilema tuyo ya que selecciona toda la hoja, lo otro que necesito hacer es que extraiga datos de 3 celdas para crear el nombre de archivo, estaba revisando el metodo getRangeList, pero no me guarda los datos

      Eliminar
  10. Buenas tardes!!

    Lo primero, enhorabuena por tu blog, es de gran ayuda! Tenía la siguiente duda:

    Tengo una script para exportar a pdf, funciona correctamente pero mi problema es que no sé el código para configurar que no se impriman las notas. En las opciones de impresión está dentro de formato, pero no localizo en el código. Iría a continuación de'&gridlines=true'

    Mil gracias!!!!

    ResponderEliminar
  11. Hola buenas tardes! priemro que todo gracias por tu blog. He aprendido mucho.
    Me gustaría saber si existe algún script para imprimir un rango en una hoja sheet.
    O que aparezca la venta de impresión (lo que en excel sería algo como: application.Dialogs(xlDialogPrint).Show)
    He buscado en varios foros, pero al parecer se debe conectar con una API. Y para mi mala suerte, google dio de baja la API para imprimir en Diciembre 2020.

    Quedo atenta,

    gracias!

    ResponderEliminar
  12. Hola, excelente blog en serio cualquier persona que te aporte conocimiento es de las buenas.

    Haciendo el código me esta generando un problema quería saber si me podías ayudar.

    ResponderEliminar
    Respuestas
    1. Encantado que te sirva. Tu diras cual es el problema q tienes...

      Eliminar
  13. Hola, necesito exportar en formato PDF pero sólo las hojas que tengas datos, si una hoja no tiene datos me interesa que esa hoja no aparezca en el pdf luego.

    Quedo atento y agradezco desde ya tu ayuda.

    Saludos.

    ResponderEliminar
    Respuestas
    1. Siento haber tardado en la respuesta. Te he montado un post para explicarte como implementarlo: https://www.tutorialesgoogleappscripts.com/2021/05/como-extraer-un-pdf-solo-de-las-hojas-llenas-con-google-script.html
      Espero q te sirva!
      Nos vemos!

      Eliminar
  14. Hola, como hago para que se guarde en una carpeta especifica de Drive?

    ResponderEliminar
    Respuestas
    1. Creo que esto te podría servir: https://www.tutorialesgoogleappscripts.com/2020/10/como-generar-un-pdf-de-hojas-seleccionadas-con-google-apps-script.html

      Eliminar
  15. Hola! Lo primero felicitarte por tu trabajo y el contenido super útil que aportas ;)

    He seguido los pasos para generar un pdf en dos ficheros distintos de google sheets, que funcionan de forma similar. En el primero de ellos funciona todo sin problemas, pero en el otro, estoy teniendo el siguiente problema.

    El script se ejecuta cuando se inserta una nueva fila, lee los datos, los copia en otra pestaña que utilizo como plantilla para crear facturas, y exporta esa pestaña a pdf, guardando el archivo pdf en una carpeta de google drive.

    Aparentemente funciona, pero he detectado que el contenido que aparece en el archivo pdf guardado en google drive, es el de la ejecución anterior. Es raro, porque en google sheets, sí que se han copiado bien los datos en la pestaña de la plantilla, pero al generar el pdf, no se por qué coge los datos anteriores, no los nuevos.

    Alguna idea de por qué puede estar pasando? Me estoy volviendo loco! xD

    Gracias!!

    ResponderEliminar
    Respuestas
    1. Jjajaajajaja, tranquilo que a mi me pasó lo mismo. Lo que pasa es que existe un tiempo de latencia entre que escribes en el spreadsheet y que realmente eso se ha escrito. Aunque parezca que está escrito, no lo está, dependiendo de la carga de trabajo, tardará un poco más o poco menos.
      Solución: SpreadsheetApp.flush();
      El flush lo que hace es esperarse hasta que todas las tareas pendientes de escritura acaben.
      Tienes que hacer el flush antes de crear el PDF, con eso asegurarás que los datos con los que harás el PDF són los correctos.
      Espero que te sirva, ya me contarás!
      Nos vemos!

      Eliminar
    2. Con el flush funciona todo perfectamente! Me viene genial porque quiero hacer otras automatizaciones generando pdfs, y seguro que me encontraba con el mismo problema.

      Mil gracias!!

      Eliminar
    3. Hola de nuevo! Me ha surgido otro problema también un poco extraño...

      En el script detecto primero cuando se inserta una nueva fila con el siguiente código:
      function onChange(e)
      {
      if(e.changeType=="INSERT_ROW")
      {
      ...

      y a partir de ahí se ejecutan las siguientes acciones:
      1. Se cogen los datos de la nueva fila
      2. Se ponen esos datos en otra pestaña con el modelo de factura
      3. Se añade una fila en otra pestaña con un registro de facturas
      4. Se genera el pdf y se guarda en drive
      5. Se envía la factura por email

      En principio todo funciona, pero no se por qué el pdf se genera y se envía por email 3 veces cada vez que se inserta una nueva fila.

      ¿Alguna idea de lo que puede estar pasando?

      Cuando ejecuto el código manualmente para depurar o hacer alguna prueba no ocurre esto, funciona todo bien.

      Muchas gracias de nuevo!!

      Eliminar
    4. No estoy seguro 100% pero la intuición me dice que: "3. Se añade una fila en otra pestaña con un registro de facturas" te está llamando al mismo onChange. Ponte logs y mira lo que sale, pero tiene toda la pinta que es eso. Tendras que modificar la linea, no añadirla.
      Ya me contarás como te ha ido

      Eliminar
  16. Esa fila que se añade en el paso 3, realmente no es una fila nueva, se escriben los datos celda por celda, con getRange().setValue().

    Estoy haciendo alguna prueba y efectivamente al escribir datos en la pestaña de registro de facturas, salta el onChange.

    Pero después del onChange() hay un: if(e.changeType=="INSERT_ROW"){
    y todo el código está dentro de este if, por lo que no debería ejecutarse en el caso de sólo modificar una celda.

    ¿Alguna otra idea?

    ¿Sabes si se puede paralizar temporalmente la detección del onChange() mientras se ejecuta el código? Por si fuera eso, evitar que ocurra.

    Mil gracias!!

    ResponderEliminar
    Respuestas
    1. El onChange es un poco puñetero. Te recomiendo que debuges bien para ver realmente cual es el problema ( yo no soy capaz de verlo la verdad, pero es echarle horas hasta verlo ). Si no hay más solución supongo que podrías montarte algo tipo "semaforo" con la cache: https://www.tutorialesgoogleappscripts.com/2016/02/como-recordar-valores-entre-ejecucion-y.html Podrias setear un flag cuando entres en la función y desetearlo cuando salgas. Si lo tienes seteado no puedes entrar en la función. No es muy elegante pero debería funcionarte para salir del paso. No obstante, repito, lo correcto seria debugar hasta verlo. Siento no ser de más ayuda.
      Nos vemos!

      Eliminar
  17. Gracias de nuevo! Y perdona por seguir preguntando....

    He probado a hacer lo siguiente, que he visto por ahí, pero nada, no funciona.

    function onChange(e) {
    var scriptProperties = PropertiesService.getScriptProperties();

    // Waits until last trigger is already done
    while(scriptProperties.getProperty('flag') == '1')
    Utilities.sleep(10000);

    // Once started set flag value as '1'
    scriptProperties.setProperty('flag', '1');

    // Your code...

    // Once finished set flag value as '0'
    scriptProperties.setProperty('flag', '0');
    }

    Me mosquea que cada vez que se inserta una fila nueva, salta 3 veces el onChange(). Y siempre son 3 veces.

    ResponderEliminar
    Respuestas
    1. No te se decir. Se que me he encontrado alguna vez con este problema o parecidos y son exasperantes. La única opción buena es ir debugando hasta que lo veas.
      Siento no ser de más ayuda.

      Eliminar
    2. Hola de nuevo! Por fin lo he conseguido, y como siempre, era una tontería....

      Resulta que tenía una pequeña función que se ejecuta manualmente para crear el trigger onChange(), y por lo que sea, se debió ejecutar 3 veces, creando 3 instancias del trigger, por eso siempre saltaba 3 veces.

      Gracias de nuevo por toda la ayuda!

      Eliminar
    3. Tenía que haber caido en ello. Sorry. Tienes toda la razón.

      Al menos lo has visto, bien por ti!

      Eliminar
  18. Hola! Deberia funcionar sin problema utilizando un activador la función? Me corre perfecto en modo editor en appscript y guarda el pdf, pero utilizando un activador (una vez al día) no se guarda. En la lista de ejecuciones no indica ningún error, pero no se guarda el pdf en ningun lado. Alguna idea?

    ResponderEliminar
    Respuestas
    1. Permisos? El usuario con el que llamas al activador es el mismo con el que lo ejecutas manualmente?

      Eliminar
    2. Hola! Si es el mismo y no funiona. He intentado varías cosas y no logro dar con el problema

      Eliminar
  19. Como poner un tamaño personalizado a la hoja que se va a generar

    ResponderEliminar
  20. Como puedo adaptar el script, para que en lugar que mande todas las hojas de la columna "ToPrint" a los distintos correos me envie solo una hoja del documento a un correo por separado.

    ResponderEliminar

Tal vez te interese