Descripción del Proyecto

Este proyecto implementa un sistema automatizado para generar y distribuir facturas mensuales usando Google Apps Script. El sistema duplica una plantilla de spreadsheet, la personaliza con la información del mes actual, la exporta como PDF, la almacena en cloud storage y la envía por email a los destinatarios designados—todo con mínima intervención manual.

Problema a Resolver

La generación manual de facturas consume tiempo y es propensa a errores. El proceso típicamente involucra:

  • Duplicar manualmente las plantillas de factura cada mes
  • Renombrar archivos con el mes correcto
  • Exportar documentos al formato apropiado
  • Organizar archivos en sistemas de almacenamiento
  • Redactar y enviar emails con adjuntos

Estas tareas repetitivas consumen tiempo valioso y crean oportunidades para errores humanos, como olvidar enviar una factura, usar el nombre de mes incorrecto, o enviar a destinatarios incorrectos.

Implementación

Arquitectura:

La solución usa Google Apps Script, que se ejecuta dentro del entorno de Google Sheets y se integra perfectamente con otros servicios de Google (Drive, Gmail, etc.).

Componentes Clave:

  1. Template Management: Una sheet de plantilla designada sirve como copia maestra
  2. Sheet Duplication: El script crea una nueva copia de la plantilla con nomenclatura basada en el mes
  3. PDF Export: Convierte la sheet específica a formato PDF usando la API de exportación de Google
  4. Cloud Storage: Guarda el PDF en una carpeta designada en Google Drive
  5. Email Distribution: Envía automáticamente el PDF a los destinatarios con un mensaje personalizado

Opciones de Ejecución:

  • Activación manual vía custom menu item
  • Ejecución directa desde el Apps Script editor
  • Automatización programada usando time-based triggers (ej. primer día de cada mes)

Flujo de alto nivel

  1. Abrir spreadsheet por ID → obtener template sheet(eliminar «[Month] Invoice» existente si está presente)copiar templaterenombrar.(Operaciones de Spreadsheet vía SpreadsheetApp.openById, Sheet.copyTo, y setName.) Google for Developers+1
  2. Construir nombres basados en el mes usando Utilities.formatDate(now, 'America/Argentina/Buenos_Aires', 'MMMM'). Google for Developers
  3. Exportar single sheet a PDF usando el spreadsheet export endpoint con gid=<sheetId>; fetch con UrlFetchApp autorizado por ScriptApp.getOAuthToken() (Bearer token). Google for Developers+1
  4. Guardar PDF en Drive vía DriveApp.getFolderById(...).createFile(blob). Google for Developers
  5. Enviar el PDF por email con MailApp.sendEmail({ to, subject, body, attachments }). Google for Developers+1

Servicios principales usados (con docs)

Detalles de Export

  • **¿Por qué usar la export URL?**Es una manera simple de exportar un solo tab pasando el gid para esa sheet y parámetros típicos de impresión (size, portrait, fit-to-width, etc.).Alternativa oficial: Usar Drive API files.export (vía Advanced Drive Service o HTTP). Esa página de docs es autoritativa para exportar Google Docs/Sheets a PDF. Google for Developers+1
  • Auth a export URLPasar header Authorization: Bearer <ScriptApp.getOAuthToken()>. El token refleja los scopes del script; si los scopes son insuficientes, la llamada falla y necesitarás re-autorizar / agregar scopes. Google for Developers

Scopes (típicos)

Dependiendo de lo que Apps Script auto-detecte, verás prompts para scopes como:

  • .../auth/spreadsheets (leer/escribir sheets)
  • .../auth/drive (escribir PDFs a Drive)
  • .../auth/script.external_request (UrlFetch)
  • .../auth/script.send_mail (MailApp)(Otorgados automáticamente en la primera ejecución cuando apruebas el prompt; no se necesita edición manual de JSON en la mayoría de los casos.) Google for Developers+3Google for Developers+3Google for Developers+3

Código (anotado)

/**
 * Creates a monthly invoice from the template sheet, exports just that tab to PDF,
 * saves it to a target Drive folder, and emails it as an attachment.
 *
 * Services:
 * - SpreadsheetApp (openById / Sheet.copyTo / rename / activate / move)
 * - Utilities.formatDate (month label in BA timezone)
 * - ScriptApp.getOAuthToken + UrlFetchApp.fetch (authorized export to PDF)
 * - DriveApp (store PDF in folder)
 * - MailApp (send PDF via email)
 */
const SPREADSHEET_ID = 'SPREADSHEETID';
const TEMPLATE_SHEET_NAME = 'Invoice Template [Automation]';
const DRIVE_FOLDER_ID = 'DRIVEFOLDERID';
const RECIPIENT_EMAIL = 'YOUREMAIL@gmail.com';
const TIMEZONE = 'America/Argentina/Buenos_Aires';

function createAndSendInvoice() {
  const ss = SpreadsheetApp.openById(SPREADSHEET_ID);             // Open spreadsheet
  const template = ss.getSheetByName(TEMPLATE_SHEET_NAME);
  if (!template) throw new Error(`Sheet "${TEMPLATE_SHEET_NAME}" not found.`);

  // Localized month label (e.g., "October")
  const monthName = Utilities.formatDate(new Date(), TIMEZONE, 'MMMM');
  const sheetName = `${monthName} Invoice`;
  const pdfFileName = `${sheetName}.pdf`;

  // Ensure idempotency: if a prior "[Month] Invoice" exists, delete it
  const existing = ss.getSheetByName(sheetName);
  if (existing) ss.deleteSheet(existing);

  // Duplicate template → rename → bring to front
  const newSheet = template.copyTo(ss).setName(sheetName);
  ss.setActiveSheet(newSheet);
  ss.moveActiveSheet(1);

  // Export only this sheet via the spreadsheet export endpoint (authorized with OAuth token)
  const exportUrl =
    `https://docs.google.com/spreadsheets/d/${SPREADSHEET_ID}/export?` +
    [
      'format=pdf',
      `gid=${newSheet.getSheetId()}`,  // limits export to this tab
      'size=letter',
      'portrait=true',
      'fitw=true',
      'gridlines=false',
      'sheetnames=false',
      'printtitle=false',
      'pagenum=UNDEFINED',
      'fzr=false'                      // don't repeat frozen rows
    ].join('&');

  const token = ScriptApp.getOAuthToken();
  const pdfBlob = UrlFetchApp.fetch(exportUrl, {
    headers: { Authorization: `Bearer ${token}` },
    muteHttpExceptions: true
  }).getBlob().setName(pdfFileName);

  // Persist PDF to Drive
  const folder = DriveApp.getFolderById(DRIVE_FOLDER_ID);
  const savedFile = folder.createFile(pdfBlob);

  // Email the PDF
  const subject = `Invoice for ${monthName}`;
  const body = `Hello!\\n\\nPlease find attached below the Invoice for the month of ${monthName}.\\n\\n` +
               `Let me know if you need anything else!\\nThanks,\\nJuani`;

  MailApp.sendEmail({
    to: RECIPIENT_EMAIL,
    subject,
    body,
    attachments: [pdfBlob]
  });

  Logger.log(`Created sheet: ${sheetName}`);
  Logger.log(`Saved PDF: ${savedFile.getUrl()}`);
}

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Invoices')
    .addItem('Create & Send Current Month Invoice', 'createAndSendInvoice')
    .addToUi();
}


Alternativas y extensiones

  • Usar official Drive API export (Advanced Drive Service) en lugar de la export URL:Drive.Files.export(fileId, 'application/pdf') (v2) o REST files.export (v3). Útil si quieres control de API más estricto o evitar parámetros manuales de URL. Puedes exportar una versión de single-tab copiando la sheet deseada a un spreadsheet temporal primero. Google for Developers+1
  • Enviar desde una dirección diferente / reply-toMailApp.sendEmail soporta opciones avanzadas como name y replyTo. (Nota: enviar «desde» otra dirección depende de la configuración de Gmail/Workspace; ver la guía de Google.) Google for Developers+1
  • ResilienciaEnvolver la sección de export/email en try/catch y registrar errores en una sheet «Logs» o enviar a Slack vía webhook.

Referencias Clave

Si quieres, también puedo agregar un bloque de plantilla «Copy/Paste» corto en Notion (toggle + checklist) para rastrear ejecuciones mensuales y links a los PDFs guardados.

Características Clave:

  • Nomenclatura de mes consciente de timezone para localización precisa
  • Operación idempotente (puede ejecutarse múltiples veces sin crear duplicados)
  • Exportación de PDF de single-sheet (excluye otras sheets del documento)
  • Constantes configurables para personalización fácil (emails de destinatarios, ubicaciones de carpetas, plantillas de mensajes)

Resultados Esperados

Ganancias de Eficiencia:

  • Reduce el tiempo de generación de facturas de 10-15 minutos a menos de 1 minuto
  • Elimina pasos manuales y errores asociados
  • Habilita automatización completa con scheduled triggers

Consistencia:

  • Convenciones de nomenclatura estandarizadas en todas las facturas
  • Formateo de email y mensajería consistentes
  • Estructura organizada de almacenamiento de archivos

Confiabilidad:

  • Entrega de factura mensual garantizada cuando se usan scheduled triggers
  • Audit trail a través del version history de Google Drive y registros de email
  • Error handling y gestión de autorización integrados en el script

Escalabilidad:

  • Fácil de modificar para múltiples destinatarios o diferentes tipos de facturas
  • Puede extenderse con características adicionales (ej. logging, notificaciones, lógica condicional)
  • Enfoque basado en template permite cambios de diseño de factura sin modificaciones de código

Deja un comentario