284 lines
8.0 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(`
    <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
// ///////////////////////////////////////////////////////////
```