8.0 KiB
8.0 KiB
Creada: 13/05/2025 Hora: 16:14 Autor: DAVID ITEHUA XALAMIHUA Email: davidix1991@gmail.com
// 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(`
<script>
window.open('https://drive.google.com/uc?export=download&id=${fileId}', '_blank');
google.script.host.close();
</script>
`).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
// ///////////////////////////////////////////////////////////