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:
- Template Management: Una sheet de plantilla designada sirve como copia maestra
- Sheet Duplication: El script crea una nueva copia de la plantilla con nomenclatura basada en el mes
- PDF Export: Convierte la sheet específica a formato PDF usando la API de exportación de Google
- Cloud Storage: Guarda el PDF en una carpeta designada en Google Drive
- 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
- Abrir spreadsheet por ID → obtener template sheet → (eliminar «[Month] Invoice» existente si está presente) → copiar template → renombrar.(Operaciones de Spreadsheet vía
SpreadsheetApp.openById,Sheet.copyTo, ysetName.) Google for Developers+1 - Construir nombres basados en el mes usando
Utilities.formatDate(now, 'America/Argentina/Buenos_Aires', 'MMMM'). Google for Developers - Exportar single sheet a PDF usando el spreadsheet export endpoint con
gid=<sheetId>; fetch conUrlFetchAppautorizado porScriptApp.getOAuthToken()(Bearer token). Google for Developers+1 - Guardar PDF en Drive vía
DriveApp.getFolderById(...).createFile(blob). Google for Developers - Enviar el PDF por email con
MailApp.sendEmail({ to, subject, body, attachments }). Google for Developers+1
Servicios principales usados (con docs)
- Spreadsheet:
SpreadsheetApp,Sheet.copyTo,setActiveSheet,moveActiveSheet. Google for Developers+1 - Date/format:
Utilities.formatDate. Google for Developers - Auth token:
ScriptApp.getOAuthToken()para llamar la export URL con un Bearer token. Google for Developers - HTTP fetch:
UrlFetchApp.fetchpara la llamada de PDF export. Google for Developers - Drive:
DriveApp.getFolderById,Folder.createFile. Google for Developers - Mail:
MailApp.sendEmailcon attachments. Google for Developers
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 RESTfiles.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-to
MailApp.sendEmailsoporta opciones avanzadas comonameyreplyTo. (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
- Operaciones de Spreadsheet:
SpreadsheetApp.openById,Sheet.copyTo, rename y ordering. Google for Developers+1 - Formateo de fecha:
Utilities.formatDate. Google for Developers - Auth token para Bearer flows:
ScriptApp.getOAuthToken. Google for Developers - HTTP requests:
UrlFetchApp.fetch. Google for Developers - Creación de Drive folder y file:
DriveApp.getFolderById/createFile. Google for Developers - Email con attachments:
MailApp.sendEmail. Google for Developers - Official export endpoint: Drive API
files.export. Google for Developers+1
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