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























