Creada: 13/05/2025 Hora: 16:14 Autor: DAVID ITEHUA XALAMIHUA Email: davidix1991@gmail.com ```js // URL AYUDA GENERAR PDF: https://xfanatical.com/blog/print-google-sheet-as-pdf-using-apps-script/ // CONFIGURACIÓN const CONFIG = {   SHEETS: {     PDF: "pdf",     ALLOWED: ["Internacionales", "Nacionales"]   },   RANGES: {     PDF_EXPORT: "C1:H17",     OUTPUT: {       ID: "K2",       SHEET_NAME: "K3"     },     COUNTRY: "D3",     DOC_TYPE: "D4"   } }; // FUNCIONES PRINCIPALES function onOpen() {   try {     SpreadsheetApp.getUi()       .createMenu('PDF')       .addItem('Descargar PDF', 'downloadPdf')       .addItem('Enviar PDF', 'sendPdfEmail')       .addToUi();     validarVencimientos();   } catch (error) {     console.error("Error en onOpen:", error);   } } function sendPdfEmail() {   processPdfFlow('email'); } function downloadPdf() {   processPdfFlow('download'); } // NÚCLEO DEL PROCESO function processPdfFlow(action) {   try {     if (!validateEnvironment()) return;     const {fileId, fileName} = generateAndSavePdf();     if (!fileId) throw new Error("Error al generar PDF");     switch(action) {       case 'email':         sendEmailWithAttachment(fileId, fileName);         break;       case 'download':         downloadFile(fileId);         break;     }     trashFile(fileId);     showAlert(`✅ PDF ${action === 'email' ? 'enviado' : 'descargado'} correctamente`);   } catch (error) {     showAlert(`❌ Error: ${error.message}`);     console.error(error);   } } // FUNCIONES DE APOYO function validateEnvironment() {   const ss = SpreadsheetApp.getActiveSpreadsheet();   const activeSheet = ss.getActiveSheet();   const activeCell = activeSheet.getActiveCell();   if (!CONFIG.SHEETS.ALLOWED.includes(activeSheet.getName())) {     showAlert('Solo disponible en hojas "Internacionales" o "Nacionales"');     return false;   }     const idValue = activeSheet.getRange(`A${activeCell.getRow()}`).getValue();   if (!Number.isInteger(idValue)) {     showAlert("Seleccione un registro con ID numérico en columna A");     return false;   }     const pdfSheet = ss.getSheetByName(CONFIG.SHEETS.PDF);   if (!pdfSheet) {     showAlert(`No se encontró la hoja "${CONFIG.SHEETS.PDF}"`);     return false;   }   // Actualizar datos   pdfSheet.getRange(CONFIG.RANGES.OUTPUT.ID).setValue(idValue);   pdfSheet.getRange(CONFIG.RANGES.OUTPUT.SHEET_NAME).setValue(activeSheet.getName());   pdfSheet.autoResizeRows(2, 40);   SpreadsheetApp.flush();   return true; } function generateAndSavePdf() {   const ss = SpreadsheetApp.getActiveSpreadsheet();   const pdfSheet = ss.getSheetByName(CONFIG.SHEETS.PDF);   const range = pdfSheet.getRange(CONFIG.RANGES.PDF_EXPORT);   const exportUrl = `${ss.getUrl().replace(/\/edit.*$/, '')}/export?` + [     'exportFormat=pdf',     'format=pdf',     'size=LETTER',     'portrait=true',     'fitw=true',     'sheetnames=false',     'printtitle=false',     `gid=${pdfSheet.getSheetId()}`,     `r1=${range.getRow() - 1}`,     `r2=${range.getLastRow()}`,     `c1=${range.getColumn() - 1}`,     `c2=${range.getLastColumn()}`   ].join('&');   const country = pdfSheet.getRange(CONFIG.RANGES.COUNTRY).getValue();   const docType = pdfSheet.getRange(CONFIG.RANGES.DOC_TYPE).getValue().replace(/\s+/g, '_');   const fileName = `${getFormattedDate()}_resumen_${country}_${docType}.pdf`;   const blob = UrlFetchApp.fetch(exportUrl, {     headers: { Authorization: `Bearer ${ScriptApp.getOAuthToken()}` }   }).getBlob().setName(fileName);   return {     fileId: DriveApp.createFile(blob).getId(),     fileName: fileName   }; } // FUNCIONES DE UTILIDAD function sendEmailWithAttachment(fileId, fileName) {   MailApp.sendEmail({     to: Session.getActiveUser().getEmail(),     subject: `📄 ${fileName}`,     body: "Documento PDF generado automáticamente desde Google Sheets.",     attachments: [DriveApp.getFileById(fileId).getBlob()],     name: "Sistema Automático de PDF"   }); } function downloadFile(fileId) {   const html = HtmlService.createHtmlOutput(`       `).setWidth(100).setHeight(50);   SpreadsheetApp.getUi().showModalDialog(html, "Descargando..."); } function trashFile(fileId) {   try {     DriveApp.getFileById(fileId).setTrashed(true);   } catch (error) {     console.warn("No se pudo eliminar archivo:", error);   } } function getFormattedDate() {   const format = num => String(num).padStart(2, '0');   const now = new Date();   return `${format(now.getDate())}.${format(now.getMonth()+1)}.${now.getFullYear()}-${format(now.getHours())}.${format(now.getMinutes())}`; } function showAlert(message) {   SpreadsheetApp.getUi().alert(message); } // \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // NOTIFICACIONES VENCIMIENTOS // ///////////////////////////////////////////////////////////////////// // 1. Optimización de formatearFecha (más eficiente con array estático) const MESES = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']; function formatearFecha(date) {   return !(date instanceof Date) || isNaN(date.getTime())     ? "Fecha inválida"     : `${date.getDate().toString().padStart(2, '0')}/${MESES[date.getMonth()]}/${date.getFullYear()}`; } // 2. Función genérica para procesar hojas (DRY - Don't Repeat Yourself) function procesarHoja(hoja, colFecha, colPais, colInstitucion) {   const rango = hoja.getRange(`${colFecha}2:${colFecha}${hoja.getLastRow()}`);   const valores = rango.getValues();   const hoy = new Date().setHours(0, 0, 0, 0);   const alertas = [];   valores.forEach(([valorFecha], i) => {     if (!(valorFecha instanceof Date) || isNaN(valorFecha.getTime())) return;     const fecha = new Date(valorFecha).setHours(0, 0, 0, 0);     const diferenciaDias = Math.floor((fecha - hoy) / 86400000);     if (diferenciaDias >= 0 && diferenciaDias <= 40) {       const fila = i + 2;       alertas.push({         dias: diferenciaDias,         texto: `${hoja.getRange(`${colPais}${fila}`).getValue()}\n${                hoja.getRange(`${colInstitucion}${fila}`).getValue()}\n${                formatearFecha(new Date(fecha))} (${diferenciaDias} días)`       });     }   });   return alertas; } // 3. onOpen optimizado function validarVencimientos() {   const ui = SpreadsheetApp.getUi();   try {     const ss = SpreadsheetApp.getActiveSpreadsheet();     const [hojaInt, hojaNac] = ['Internacionales', 'Nacionales']       .map(nombre => ss.getSheetByName(nombre));     if (!hojaInt || !hojaNac) {       ui.alert("Error", "No se encontró alguna de las hojas requeridas", ui.ButtonSet.OK);       return;     }     const alertas = [       ...procesarHoja(hojaInt, 'H', 'B', 'E'),       ...procesarHoja(hojaNac, 'I', 'B', 'E')     ].sort((a, b) => a.dias - b.dias);     if (alertas.length) {       ui.alert(`⚠️ Alertas de vencimiento (${alertas.length}) ⚠️`,                `📅 Vencimientos próximos (≤40 días):\n\n${                 alertas.map(a => a.texto).join('\n\n')}`,                ui.ButtonSet.OK);     } else {       ui.alert("Sin alertas", "No hay vencimientos próximos", ui.ButtonSet.OK);     }   } catch (error) {     ui.alert("Error", error.message, ui.ButtonSet.OK);     console.error(error);   } } // \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // FUNCIONES DIX EN HOJAS // /////////////////////////////////////////////////////////// function sh_download(idFile) {   return `https://drive.google.com/uc?export=download&id=${idFile}&confirm=no`; } function sh_view(idFile){   return `https://drive.google.com/file/d/${idFile}/view?usp=drive_link`; } // \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // OTRAS // /////////////////////////////////////////////////////////// ```