Guía Interactiva: Programar Google Forms

Automatiza la apertura y cierre de tus formularios.

Automatiza tu Google Form

Google Forms es una herramienta fantástica para crear encuestas, cuestionarios y exámenes. Sin embargo, tiene una limitación importante: no puedes programar de forma nativa una hora de apertura y una hora de cierre. Solo puedes cerrar un formulario manualmente.

Si quieres que un examen esté disponible solo el viernes de 9:00 AM a 10:00 AM, tendrías que estar presente para abrirlo y cerrarlo.

Este tutorial te guiará paso a paso para crear un script que añade un menú personalizado a tu formulario. Con él, podrás programar fácilmente una ventana de tiempo (fecha y hora de inicio y fin) usando una interfaz gráfica amigable.

¿Cómo funciona?

Usaremos Google Apps Script, la plataforma de automatización integrada en Google Workspace. Crearemos dos archivos:

  • Un archivo `.gs` (Apps Script): Contendrá la lógica principal para crear el menú, cerrar el formulario y programar los "disparadores" (triggers) que abrirán y cerrarán el formulario a la hora exacta.
  • Un archivo `.html` (HTML/CSS/JS): Creará la ventana emergente (modal) que nos permitirá seleccionar la fecha y hora de forma visual, sin tener que tocar el código nunca más.

Paso 1: Abre el Editor de Secuencias de Comandos

  1. Abre el Google Form que deseas programar.
  2. Haz clic en el icono de los tres puntos (Más) en la esquina superior derecha.
  3. Selecciona "Editor de secuencias de comandos".

Nota Visual:

En este punto, verías una captura de pantalla del menú de Google Forms con la opción "Editor de secuencias de comandos" resaltada.

Se abrirá una nueva pestaña con un proyecto de Apps Script. Es posible que veas un archivo llamado `Código.gs`.

Paso 2: Crea el Archivo de Lógica Principal (.gs)

  1. Borra cualquier contenido que haya en el archivo `Código.gs`.
  2. Copia y pega el siguiente código completo en ese archivo.
  3. (Opcional) Puedes cambiar el nombre del archivo a `CierreAutomaticoFormulario.gs`.
/**
 * @OnlyCurrentDoc
 *
 * Este script añade un menú personalizado a tu Google Form para
 * programar una hora de APERTURA y CIERRE automáticos.
 */

// Se ejecuta cuando el editor abre el formulario.
function onOpen(e) {
  FormApp.getUi()
    .createMenu('Temporizador del Formulario')
    .addItem('1. Programar Apertura y Cierre', 'mostrarDialogoProgramacion')
    .addSeparator()
    .addItem('2. Eliminar Programación Actual', 'eliminarTriggersYReabrir')
    .addToUi();
}

/**
 * Muestra el diálogo HTML modal para configurar las fechas.
 */
function mostrarDialogoProgramacion() {
  // Llama al archivo HTML que crearemos en el siguiente paso
  var html = HtmlService.createHtmlOutputFromFile('DialogoConfiguracion')
    .setWidth(400)
    .setHeight(350);
  FormApp.getUi().showModalDialog(html, 'Programar Apertura y Cierre');
}

/**
 * Función que se llama desde el HTML para configurar los triggers.
 * @param {string} fechaHoraAperturaString La fecha/hora de inicio.
 * @param {string} fechaHoraCierreString La fecha/hora de fin.
 * @return {string} Un mensaje de confirmación o error.
 */
function programarEventosFormulario(fechaHoraAperturaString, fechaHoraCierreString) {
  try {
    var fechaApertura = new Date(fechaHoraAperturaString);
    var fechaCierre = new Date(fechaHoraCierreString);
    var ahora = new Date();

    // --- Validación de Fechas ---
    if (isNaN(fechaApertura.getTime()) || isNaN(fechaCierre.getTime())) {
      throw new Error('Formato de fecha u hora inválido.');
    }
    if (fechaApertura < ahora) {
      throw new Error('La fecha de APERTURA debe ser en el futuro.');
    }
    if (fechaCierre <= fechaApertura) {
      throw new Error('La fecha de CIERRE debe ser posterior a la de APERTURA.');
    }

    // Elimina cualquier programación anterior
    eliminarTriggers();

    // 1. Crear el trigger para ABRIR el formulario
    ScriptApp.newTrigger('abrirFormulario')
      .timeBased()
      .at(fechaApertura)
      .create();

    // 2. Crear el trigger para CERRAR el formulario
    ScriptApp.newTrigger('cerrarFormulario')
      .timeBased()
      .at(fechaCierre)
      .create();
      
    // 3. CERRAR el formulario INMEDIATAMENTE
    // (Esperará cerrado hasta que se ejecute el trigger de apertura)
    var form = FormApp.getActiveForm();
    form.setAcceptingResponses(false);

    // Confirma al usuario
    var tz = Session.getScriptTimeZone(); // Obtiene la zona horaria del script
    var msg = '¡Éxito! El formulario se programó:\n\n' +
      'Se ABRIRÁ el:\n' + fechaApertura.toLocaleString('es-ES', { timeZone: tz }) + '\n\n' +
      'Se CERRARÁ el:\n' + fechaCierre.toLocaleString('es-ES', { timeZone: tz });

    Logger.log('Formulario programado. Apertura: ' + fechaApertura + ', Cierre: ' + fechaCierre);
    
    return msg; // Envía éxito de vuelta al HTML

  } catch (e) {
    Logger.log('Error al programar: ' + e.message);
    return 'Error: ' + e.message; // Envía error de vuelta al HTML
  }
}

/**
 * Esta es la función que el trigger de APERTURA llamará.
 */
function abrirFormulario() {
  var form = FormApp.getActiveForm();
  form.setAcceptingResponses(true);
  Logger.log('Formulario "' + form.getTitle() + '" ABIERTO por el script.');
  
  // Opcional: Notificar al propietario
  var email = Session.getEffectiveUser().getEmail();
  if (email) {
    MailApp.sendEmail(email, 'Tu formulario de Google ha sido ABIERTO', 
      'El formulario "' + form.getTitle() + '" ahora acepta respuestas según lo programado.');
  }
}

/**
 * Esta es la función que el trigger de CIERRE llamará.
 */
function cerrarFormulario() {
  var form = FormApp.getActiveForm();
  form.setAcceptingResponses(false);
  Logger.log('Formulario "' + form.getTitle() + '" CERRADO por el script.');
  
  // Opcional: Notificar al propietario
  var email = Session.getEffectiveUser().getEmail();
  if (email) {
    MailApp.sendEmail(email, 'Tu formulario de Google ha sido CERRADO', 
      'El formulario "' + form.getTitle() + '" ha dejado de aceptar respuestas según lo programado.');
  }
}

/**
 * Elimina todos los triggers (disparadores) de este proyecto.
 */
function eliminarTriggers() {
  var triggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < triggers.length; i++) {
    ScriptApp.deleteTrigger(triggers[i]);
  }
  Logger.log('Se eliminaron los triggers anteriores.');
}

/**
 * Una función de menú para eliminar triggers y asegurarse de que el form esté ABIERTO.
 */
function eliminarTriggersYReabrir() {
  eliminarTriggers();
  FormApp.getActiveForm().setAcceptingResponses(true);
  
  var ui = FormApp.getUi();
  ui.alert('Programación Eliminada', 'Se eliminó toda la programación. El formulario está ABIERTO y acepta respuestas ahora.', ui.ButtonSet.OK);
  Logger.log('Programación eliminada manualmente por el usuario.');
}

Paso 3: Crea el Archivo de Interfaz (.html)

Este es el paso más importante. El nombre del archivo debe ser exacto.

  1. En el editor de Apps Script, haz clic en el icono `+` (Añadir un archivo).
  2. Selecciona HTML.
  3. En el diálogo que aparece, escribe el nombre: `DialogoConfiguracion` (sin el `.html`. El editor lo añadirá automáticamente).

Nota Visual:

Aquí verías una captura de pantalla del diálogo "Crear nuevo archivo" en Apps Script, escribiendo "DialogoConfiguracion".

Borra el contenido por defecto de este nuevo archivo y copia y pega el siguiente código:

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <style>
    body {
      font-family: Arial, sans-serif;
      padding: 0 20px 20px 20px;
      box-sizing: border-box;
      background-color: #f9f9f9;
    }
    .grupo-input {
      margin-bottom: 15px;
    }
    label {
      font-weight: bold;
      display: block;
      margin-top: 10px;
      margin-bottom: 5px;
      color: #333;
    }
    input[type="date"], input[type="time"] {
      width: 100%;
      padding: 8px;
      border: 1px solid #ccc;
      border-radius: 4px;
      box-sizing: border-box;
    }
    button {
      background-color: #4285f4;
      color: white;
      border: none;
      padding: 10px 15px;
      border-radius: 4px;
      cursor: pointer;
      font-size: 14px;
      margin-top: 20px;
    }
    button:disabled {
      background-color: #ccc;
      cursor: not-allowed;
    }
    #close-btn {
      background-color: #aaa;
      float: right;
    }
    #status {
      margin-top: 15px;
      font-size: 13px;
      color: #555;
      white-space: pre-wrap; 
      border-top: 1px solid #eee;
      padding-top: 15px;
    }
  </style>
</head>
<body>

  <div class="grupo-input">
    <label for="fecha-apertura">Fecha de Apertura:</label>
    <input type="date" id="fecha-apertura">
    <label for="hora-apertura">Hora de Apertura:</label>
    <input type="time" id="hora-apertura">
  </div>

  <div class="grupo-input">
    <label for="fecha-cierre">Fecha de Cierre:</label>
    <input type="date" id="fecha-cierre">
    <label for="hora-cierre">Hora de Cierre:</label>
    <input type="time" id="hora-cierre">
  </div>

  <button id="submit-btn">Programar</button>
  <button id="close-btn" onclick="google.script.host.close()">Cerrar</button>

  <div id="status"></div>

  <script>
    document.getElementById('submit-btn').addEventListener('click', function() {
      // Obtener valores de Apertura
      var fechaApertura = document.getElementById('fecha-apertura').value;
      var horaApertura = document.getElementById('hora-apertura').value;
      
      // Obtener valores de Cierre
      var fechaCierre = document.getElementById('fecha-cierre').value;
      var horaCierre = document.getElementById('hora-cierre').value;

      var statusDiv = document.getElementById('status');
      var submitBtn = document.getElementById('submit-btn');

      // Validación simple en el cliente
      if (!fechaApertura || !horaApertura || !fechaCierre || !horaCierre) {
        statusDiv.style.color = 'red';
        statusDiv.innerText = 'Por favor, completa todos los campos.';
        return;
      }

      // Combinar fecha y hora
      var fechaHoraAperturaString = fechaApertura + 'T' + horaApertura;
      var fechaHoraCierreString = fechaCierre + 'T' + horaCierre;

      // Validación de lógica de fechas en el cliente (para ahorrar una llamada)
      if (new Date(fechaHoraCierreString) <= new Date(fechaHoraAperturaString)) {
        statusDiv.style.color = 'red';
        statusDiv.innerText = 'Error: La fecha de CIERRE debe ser posterior a la de APERTURA.';
        return;
      }
      
      if (new Date(fechaHoraAperturaString) <= new Date()) {
        statusDiv.style.color = 'red';
        statusDiv.innerText = 'Error: La fecha de APERTURA debe ser en el futuro.';
        return;
      }

      statusDiv.style.color = '#555';
      statusDiv.innerText = 'Programando...';
      submitBtn.disabled = true;

      // Esta es la magia: llama a la función del lado del servidor (.gs)
      google.script.run
        .withSuccessHandler(onSuccess)
        .withFailureHandler(onFailure)
        .programarEventosFormulario(fechaHoraAperturaString, fechaHoraCierreString);
    });

    function onSuccess(mensaje) {
      var statusDiv = document.getElementById('status');
      if (mensaje.startsWith('Error:')) {
         onFailure({ message: mensaje });
      } else {
        statusDiv.style.color = 'green';
        statusDiv.innerText = mensaje;
        document.getElementById('submit-btn').disabled = true;
        // Cierra el modal automáticamente tras el éxito
        setTimeout(function() {
          google.script.host.close();
        }, 8000); // Dar más tiempo para leer la confirmación
      }
    }

    function onFailure(error) {
      var statusDiv = document.getElementById('status');
      statusDiv.style.color = 'red';
      statusDiv.innerText = 'Error: ' + error.message;
      document.getElementById('submit-btn').disabled = false;
    }
  </script>
</body>
</html>

Paso 4: Guarda y Autoriza el Script

  1. Haz clic en el icono "Guardar proyecto" (el disquete) en la barra de herramientas del editor de Apps Script.
  2. Vuelve a la pestaña de tu Google Form. Recarga la página (F5 o Cmd+R).
  3. Deberías ver el nuevo menú: "Temporizador del Formulario".
  4. Haz clic en él y selecciona "1. Programar Apertura y Cierre".
  5. La primera vez que lo ejecutes, Google te pedirá autorización para que el script pueda gestionar tu formulario.
  6. Haz clic en "Continuar" o "Revisar permisos".
  7. Elige tu cuenta de Google.
  8. Verás una advertencia de "Google no ha verificado esta aplicación". Esto es normal, ya que tú eres el desarrollador. Haz clic en "Configuración avanzada" o "Avanzado".
  9. Haz clic en "Ir a [Nombre de tu script] (no seguro)".
  10. Revisa los permisos (necesita gestionar tus formularios) y haz clic en "Permitir".

Paso 5: ¡Úsalo!

¡Ya está todo listo!

Para Programar:

  1. Ve a "Temporizador del Formulario" > "1. Programar Apertura y Cierre".
  2. Selecciona la fecha y hora de inicio y fin en la ventana emergente.
  3. Haz clic en "Programar".
  4. El script te dará un mensaje de confirmación y el formulario se cerrará (verás que la pestaña "Respuestas" deja de aceptar respuestas). Esperará hasta la hora de apertura.

Nota Visual:

Aquí verías una captura de la ventana emergente final, con los selectores de fecha y hora listos para usar.

Para Cancelar:

  1. Si quieres cancelar la programación y volver a abrir el formulario manualmente, simplemente ve a "Temporizador del Formulario" > "2. Eliminar Programación Actual".
  2. Esto borrará todos los disparadores y reabrirá tu formulario de inmediato.