Una aplicación web de consulta de notas con Google Apps Script


Un artículo más en este espacio y una nueva oportunidad para hablar de Google Apps Script (GAS, en adelante), un tema que este curso se ha convertido en mi obsesión particular. Ojalá todas (las obsesiones) fueran tan productivas e interesantes.

En esta ocasión voy a hablar de las aplicaciones web o webapps, un tipo particular de scripts que abren un amplio horizonte de posibilidades por lo que hace al uso de documentos nativos de Google en entornos educativos (y, por supuesto, también de otro tipo) y a su integración con, oh sorpresa, elementos web.

A diferencia de los scripts tradicionales, que han sido protagonistas de alguna que otra publicación en este espacio y también en mi reciente sitio web TIC_Tools,  al que aprovecho para invitarte, las webapps disponen de una interfaz HTML accesible directamente a través de una URL más o menos convencional utilizando un navegador de Internet. No se trata, por tanto, de guiones inevitablemente asociados e invocables a través del correspondiente menú de un documento nativo de Drive (aunque también pueden serlo) o de elementos instalables sobre él a modo de complemento, sino de auténticas aplicaciones web (de ahí su nombre) que, sin embargo, pueden manipular los objetos y datos expuestos por la interfaz de programación de Google Apps Script. O lo que es lo mismo, acceder a la información almacenada en nuestros documentos de texto, hojas de cálculo, presentaciones, formularios y otros servicios de Google.

De hecho, después de pasarme el último año aprendiendo Google Apps Script siguiendo el camino estrictamente práctico del que va montando cosas, y publicar hace unas semanas mi primer complemento en la tienda de Google, Form Response Control, el siguiente paso natural era abordar el proceso de creación de una de estas webapps. Sí, el aprender haciendo no es solo para nuestros alumnos.

Form Response Control en la tienda de Google. No, esa única valoración no es mía.

¿Por qué una webapp?

Vale, así que una webapp. Pero, ¿para qué? Y más importante... ¿por qué?

La semana que viene se celebran en los centros, públicos y privados, de Educación Secundaria y Formación Profesional de la Comunidad Valenciana unas pruebas oficiales cuya superación permite a sus alumnos obtener el certificado de competencia de nivel básico (A2 MCER) de inglés, francés, alemán o italiano.

En mi centro venimos gestionando la inscripción de nuestros alumnos mediante un sencillo formulario de Google, cuyas respuestas (solicitudes) se vuelcan en una hoja de cálculo asociada. En una segunda pestaña de esta hoja de cálculo se registran, tras las pruebas, las puntuaciones de cada candidato, que mediante el conocido complemento formMule, son posteriormente comunicadas por correo electrónico. Y fin de la historia. Como ves, un flujo de trabajo muy G Suite... bueno, bonito y barato.

No obstante, para las de este año me planteé montar un tinglado que posibilitara que cada alumno pudiera consultar sus notas directamente a través de la web del centro, previa introducción de algún tipo de información de autenticación como por ejemplo DNI y correo electrónico utilizado para realizar la inscripción.

Como seguramente sepas, una hoja de cálculo, e incluso un rango específico en ella, pueden ser fácilmente incrustados en una web (no necesariamente creada con Google Sites). El problema surge cuando queremos limitar el acceso de un usuario concreto únicamente a sus propios datos (resultados, en este caso) personales. Si bien es posible controlar de modo muy granular qué secciones de una hoja de cálculo son editables por los distintos colaboradores (editores) de la misma, carecemos de un mecanismo análogo para hacer lo propio con la visualización. Es más, las personas que deben acceder a esa información ni siquiera tienen por qué disponer de una cuenta de Google / G Suite.

Para resolverlo tenemos que subir la apuesta.... Y ahí entran las webapps. Veamos un esquema de lo que vamos a construir.

Funcionamiento de la webapp de consulta personal de notas.

La cosa creo que queda bastante clara en el esquema anterior:
  1. El candidato introduce sus datos de autenticación en un formulario web.
  2. Esos datos son recibidos por la webapp, que busca las calificaciones del candidato que queda identificado por ellos en la hoja de cálculo de resultados, que es privada, no está publicada y tan solo compartida con las personas del centro autorizadas que intervienen en la administración de las pruebas.
  3. La webapp muestra los resultados en el navegador del candidato.
Como puedes intuir, las posibilidades son enormes. El mecanismo genérico descrito permite que el visitante de una página web acceda a información de su expediente académico, administrativo o a cualquier tipo de notificación o información de su interés, sin que sea en absoluto necesario que utilice para el acceso una cuenta específica de Google o G Suite. Se me ocurre que podría emplearse para facilitar información sobre admisión al centro, becas, o cualquier otra información adicional de interés para futuros alumnos, alumnos y familias de un modo sencillo, elegante y mucho más compatible con el Reglamento General de Protección de Datos que la simple publicación de listados en abierto.

Aunque ando en estos momentos dándole vueltas a la creación de una herramienta para generalizar la publicación de cualquier tipo de información del modo descrito, por el momento la idea está en pañales, por lo que si quieres hacer algo parecido a lo que te cuento, pero adaptado a otro contexto, tendrás que seguir leyendo y asumir que ciertos conocimientos de GAS por tu parte son, por ahora, inevitables.

En lo que sigue hay abundante código de copiar & pegar. Pero esto no va de copiar a lo loco, sino de entender el proceso y llegar a ser capaz de adaptar las técnicas descritas a las propias necesidades. No obstante, vaya por delante que esto tampoco es (ni pretende ser) un curso de programación en GAS... quizás en un futuro.

Pero, como dijo Jack el Destripador, vamos por partes.

La hoja de cálculo es tu base de datos (1 / 6)

Lo primero, claro está, es tener datos que mostrar. En esta hoja de cálculo están los míos. Me los he inventado, naturalmente. Cualquier parecido con personas reales es pura coincidencia. Puedes utilizarla como plantilla y trabajar sobre ella.

Plantilla de HdC de resultados

Pocas cosas reseñables en su interior, tan solo un QUERY para trasladar cierta información de la hoja de datos conectada con el formulario a la de resultados del modo adecuado, un SI, probablemente simplificable, para calcular la nota final y algún que otro formato condicional.

Este es nuestro "backend", una hoja de cálculo de Google privada.

Tenemos por tanto nuestra base de datos. Sigamos.

La estrategia satisfactoria (2 / 6)

Hay varias formas de implementar una webapp que resuelva el problema que se nos plantea. Yo voy a describir con cierto detalle una de ellas y conformarme con sugerir la otra, algo más compleja y exigente por cuanto a los conocimientos necesarios de programación en GAS para hacerla tuya.

Empecemos pues con la primera.

Aunque podemos publicar como webapp un script creado dentro de una hoja de cálculo, yo he preferido optar por uno totalmente independiente para recalcar el hecho de que una de estas aplicaciones web no necesita para nada del andamiaje que le proporciona un documento de Google para existir. Nada de Herramientas → Editor de secuencias de comandos. Hay más razones, pero esta para mi es más que suficiente en estos momentos. Por tanto, nos adentraremos en terreno desconocido y en su lugar haremos otra cosa:

Creando un script independiente.

¿Sabías que los scripts GAS independientes pueden compartirse directamente con otros usuarios? ¿No? Pues ya lo sabes. Otra razón para usarlos, de modo preferente. Aquí tienes el que nos ocupa. 

Script GAS v1

Tendrás que hacerte una copia para trastear con él (Archivo → Crear una copia).

Haciendo una copia de un script GAS independiente compartido.

Vamos a mirar bajo el capó. Comprobarás que el código consta de dos grupos de archivos:

Código.gs: Es el código GAS que realmente resuelve el problema. Como sabemos, Google Apps Script no es más que JavaScript ligeramente tuneado con una API por debajo para interactuar con los servicios de Google.

resultados.html /  error.html: Se trata de sendos documentos HTML. Realmente pueden contener tanto código HTML (contenido) como JavaScript (código similar a GAS) o CSS (presentación del contenido ¡y más!). El primero muestra los resultados de la consulta contra la hoja de cálculo. El segundo, mensajes de error apropiados cuando no pueden recuperarse las calificaciones, bien sea porque no se han introducido correctamente los datos de autenticación, bien porque no han sido aún publicadas.

Dentro de un proyecto como el nuestro puede haber varios archivos de cada tipo, que siempre estarán caracterizados por sus respectivas extensiones, .gs o .html.

Archivos del proyecto de nuestra webapp.

Pero antes de seguir, reflexionemos sobre estos tres principios fundamentales recogidos en las tablas de la ley de la programación (ya no tan básica) en GAS:
  1. Los componentes de tipo GAS se ejecutan (procesan) en el contexto de los servidores de Google y, si están debidamente autorizados, pueden acceder a la información contenida en la cuenta de Google / G Suite del usuario.
  2. Los componentes de tipo HTML se ejecutan, en cambio, en el contexto del cliente, es decir, el navegador de quien utiliza el script, y no pueden acceder directamente a los datos del usuario ni a las variables que manejan los elementos GAS.
  3. Existen diversos mecanismos de intercambio de información entre ambos contextos, varias técnicas que nos permiten acercar ambos mundos. Cliente y servidor son como Rutger Hauer y Michelle Pfeiffer en Lady Halcón, viven en mundos separados. Afortunadamente, no necesitamos a  Matthew Broderick para que se encuentren.
Y sí, el punto 3 es el quid de la cuestión, del que se habla extensamente en la que para mi es una de las secciones clave de la página de soporte de GAS de Google. Pero ya he explicado antes que esto no es un curso de programación, así que tiro adelante comentando la solución adoptada.

Cuando la comunicación es únicamente unidireccional desde el contexto del servidor al del cliente, como es nuestro caso, la forma más inmediata de materializarla es a través de unos artilugios denominados scriptlets. Los scriptlets son secuencias de código GAS que se insertan dentro de los componentes HTML.

La gracia del invento (y también su debilidad) es que estos scriptlets se procesan (evalúan) en el servidor una única vez y su resultado sirve para generar una página HTML de manera dinámica, que es a continuación enviada al navegador (recuerda, cliente) del usuario. Los scriptlets pueden acceder a todo lo que queda al alcance del código GAS del lado del servidor, pero una vez en el contexto del cliente desaparecen de la faz del navegador y de ellos solo queda el resultado de su ejecución.

Bueno, en principio resuelven nuestro problema ¿no? Pues venga.

Entrando en detalles, nuestra primera versión de la webapp será algo como esto:
Primera versión de la webapp, con plantillas HTML y scriptlets explícitos (mi traducción libre de "printing scriptlets").

El código (3 / 6)

¿Te has hecho ya una copia del código? ¿No? El Script GAS v1 de más arriba ¿recuerdas?

Espero.

Ábrela en una ventana aquí al lado y vamos comentado cosas. Comencemos por el archivo resultados.html. Lo interesante está al final, en las líneas 30 - 38. Dirígete allí. ¿Ves esos elementos  delimitados por símbolos en azul?


Por ejemplo:
<?= notaSpeaking ?>

Eso, amigo o amiga, es un scriptlet. Y además uno de tipo explícito (el término real en inglés es printing scriptlet). Los hay de dos tipos más, pero no nos vamos a meter en ese berenjenal.

Los scriptlets explícitos son una de las formas más rápidas, si no la más rápida, de conseguir inyectar en una página web información que reside en el lado del servidor, donde viven tanto la parte GAS de nuestra webapp como la hoja de cálculo que contiene los resultados. Justo antes de que nuestro código GAS sirva la página al navegador del usuario, estos elementos serán interpretados y sustituidos, directamente, por el valor que representan en el documento HTML.

Puedes echarle también un vistazo a error.html. Seguro que ya no se te escapa su utilidad: mostrar una página con cierto mensaje de error, en lugar de la de resultados, en función de la circunstancia que se produzca. Dado que es más de lo mismo, permíteme que no le dedique más tiempo.

Ahora solo nos queda por resolver cómo asignarles los valores correspondientes a estos scriptlets.

Nos vamos a Código.gs.

Las webapps reciben sus parámetros (datos de entrada) desde un formulario web a través de un mecanismo de tipo GET o POST. Cada método tiene sus ventajas e inconvenientes. Yo he optado en este caso por el primero.

Función de entrada a nuestra webapp.

Esta elección condiciona el uso de una función doGet() o doPost(). Sí, con esos nombres a piñón fijo. Dicha función será la que primero se ejecutará de entre todas las que formen parte del código de la webapp. Por eso la denominaremos función de entrada.

De un modo u otro, esta función de entrada recibirá un objeto, a modo de contenedor de parámetros, que es el que utilizaremos en este caso para recoger los datos de identificación del usuario. ¿Todas las webapps reciben información de entrada de esta manera? En absoluto, pero la nuestra, con su diseño actual, sí necesita recoger el DNI y el email desde un formulario web. No temas, cerraremos el círculo en el siguiente apartado.

A continuación sigue un fragmento de código en el que se definen ciertas variables importantes utilizadas en el resto de la función:

Variables importantes, siempre a mano.

En la línea 9 se identifica la hoja de cálculo a través de su ID. Esta secuencia de caracteres se obtiene fácilmente a partir de su URL:


En la línea 10 se determina el nombre de la hoja de datos dentro de la hoja de cálculo. En la 25, por su parte, se obtiene el objeto correspondiente que se utilizará en el código para obtener los datos almacenados en las celdas de la hoja.

Utilizo las líneas 13 - 23 para declarar las posiciones en la tabla de datos de celdas, filas y columnas que contienen información significativa. De este modo dispongo de esta información a la vista en cuanto abro el script, lo que facilitará sin duda cualquier modificación o adaptación del código en otras situaciones. En mi opinión es esta una buena práctica que resulta preferible a enterrar referencias absolutas a las celdas relevantes en diferentes partes del código.

A continuación (línea 29), se comprueba si la información de la hoja de cálculo está publicada, y debe ser facilitada al usuario, o no.


Para ello se verifica el estado de la celda E3 de la hoja de datos Resultados de nuestra hoja de cálculo, que tal vez recuerdes, contenía una casilla de verificación insertada por medio de la función de validación de datos. Un modo extremadamente sencillo de controlar la publicación de los resultados desde el backend por personas que no tienen por qué saber nade de guiones GAS, HTML, formularios, servidores web ni nada que se le parezca.

Un clic sobre la casilla de verificación es todo lo que se necesita para activar o desactivar la webapp.

Cositas interesantes. ¿Cómo se leen los parámetros que recibe nuestra webapp desde el código? Por medio del objeto e.parameter, que queda instanciado en la declaración de doGet(e). Este objeto está formado por una secuencia de pares de tipo clave / valor. Cada clave se corresponde con uno de los parámetros de entrada (líneas 33 - 34). ¿Y quién determina el nombre de estas claves? Es una convención que establezco yo mismo en función de la secuencia que he decidido utilizar como sufijo del URL de la webapp. Calma, todo cuadrará en el apartado 5.

Lo que sigue (líneas 36 - 57) es código GAS poco trascendente en el que se tratan de filtrar situaciones que impedirían recuperar la información correspondiente a las calificaciones solicitadas:
  • No se han indicado DNI o email.
  • El DNI introducido no se encuentra en la hoja de datos de resultados.
  • El DNI sí se encuentra pero el email que recibe la webapp no coincide con el registrado en la tabla para ese candidato. Es esta una sencilla comprobación de seguridad adicional, tal vez innecesaria o quizás insuficiente. A valorar.
Las líneas 42 - 43, no obstante, son las que buscan en la tabla los resultados del candidato caracterizado por un DNI e email determinados mediante un sencillo bucle while:


Si el alumno en cuestión está en la tabla y las credenciales introducidas en el formulario son válidas la cosa sigue y, a partir de la línea 58, se pone más interesante.


En este punto es donde se instancian los scriptlets a los valores leídos de la tabla de resultados. Para ello, antes que nada, se construye sobre la variable (objeto) plantillaHTML un objeto de tipo plantilla HTML, utilizando para ello la clase HtmlService y su método .createTemplateFromFile, basado en el código HTML del archivo resultados.html.

¡Ojo! Este método se da un aire al similar .createHtmlOutputFromFile, utilizado habitualmente para desplegar interfaces de usuario HTML en paneles laterales y ventanas modales y no modales. Pero no es igual, precisamente porque entra en juego la evaluación de los dichosos scritplets.

Con el objeto HTML en el bolsillo ahora se trata de inicializar los scriptlets con los valores a mostrar en el documento HTML. Y nuevamente se consigue considerándolos propiedades del objeto plantillaHTML (líneas 59 - 69):


Por ejemplo, el valor que adoptará el scriptlet...

<?= notaSpeaking ?>

...se asigna simplemente mediante la expresión:

plantillaHtml.notaSpeaking = ...

Sorprendentemente fácil, ¿verdad?

Pues ya lo tenemos. El paso final es solicitar que las expresiones de tipo scriptlet introducidas en el archivo HTML sean evaluadas (recuerda, en el contexto del servidor) y se genere una página HTML dinámica, que es la que se desplegará en el navegador del usuario. Esto se consigue en la línea 72 mediante el método .evaluate():


Fíjate en que lo que realmente está sucediendo es que la función doGet() devuelve (return) el objeto (página) HTML que se ha construido a partir de:
  • La plantilla HTML.
  • Los scriptlets a los que se les han asignado valores desde el código GAS.
En la misma instrucción se encadena el método .setTitle(), que simplemente permite establecer el título de la página HTML que mostrará el navegador.

Lo que queda del código (líneas 74 - 95) solo están ahí para cazar el resto de condiciones de error descritas anteriormente.

El despliegue como webapp (4 / 6)

Ha llegado el momento de ver si esto funciona. Para publicar la webapp hay que dirigirse al comando Publicar → Implementar como aplicación web del editor de scripts.


En definitiva, de lo que se trata es de obtener el URL de la webapp, que introduciremos posteriormente en cualquier navegador para interactuar con ella. En mi caso el botón inferior en la captura de pantalla muestra el texto Actualizar dado que ya ha sido desplegada con anterioridad.

Panel de publicación de la webapp.

Desde aquí se establece bajo qué identidad se ejecuta la aplicación, la de su creador o la de la persona que accede a ella a través de la web. Y es precisamente este un punto crucial.

Deberemos ajustar la webapp para que se ejecute utilizando la identidad del creador (en este caso yo, claro). De ese modo el código GAS podrá acceder a la hoja de datos, que también será de su (mi) propiedad, y mostrarle los resultados a quienquiera que se identifique ante ella mediante el DNI e email correctos. Como era de esperar, al igual que con cualquier otro script GAS, la aplicación deberá ser autorizada antes de ser usada por primera vez, bien por su propietario cuando se ejecuta bajo el paraguas de su cuenta, bien por cualquiera que acceda a ella, en otro caso.

Por último, también existe un ajuste para limitar el acceso a la webapp, de modo que su uso quede restringido a su creador, a los usuarios del dominio G Suite, a cualquier usuario con cuenta de Google o simplemente pueda ser utilizada públicamente de modo no autenticado.

Cada vez que realicemos cambios en un elemento del proyecto deberemos actualizar el despliegue, obteniendo un nuevo URL en este panel de publicación. Además, para facilitar el proceso de desarrollo, se nos facilitará también otro URL (último código), esta vez totalmente privado y solo accesible para el propietario del proyecto, que siempre conduce a la webapp que ejecuta el código más reciente.


¿Y qué pasará si ahora escribimos el URL en la barra de direcciones de un navegador? Compruébalo tú mismo, que al fin y al cabo esto está ya en el aire.

Webapp is in the air...

Pues ahí la tenemos, vivita y coleando... pero ¿cómo demonios le hacemos llegar ahora el DNI e email del candidato cuyos resultados deseamos consultar?

Sigue leyendo.

El formulario (5 / 6)

Nuestra webapp recibe sus parámetros por medio de una función doGet(). Esto quiere decir que los toma de su propia URL, concretamente los espera anexados, como un sufijo introducido por el carácter ? y con el símbolo & como separador entre cada uno de ellos, de este modo:

.../exec?DNI=95461354S&EMAIL=al95461354S@micentro.es

Puedes probarlo nuevamente tú mismo:

Invocando a la webapp con parámetros de modo manual vía GET.

Pero lógicamente no es aceptable esperar que nadie tenga que urdir URLs de este tipo, así que mejor vamos pensando otra cosa. Y esa otra cosa es un formulario publicado en la web de, por ejemplo, nuestro centro (que en absoluto tiene por qué estar montada sobre Google Sites). El candidato introducirá así comodamente sus datos de identificación. Este formulario invocará al script, publicado como webapp, y le pasara esta información a través del atríbuto action de la etiqueta HTML <form>:

Un sencillo código HTML para un formulario como el descrito podría ser este:

1
2
3
4
5
6
7
<form action="https://script.google.com/macros/s/AKfycbzlDk0jJzHB5zIeb78PEWRYb_TOi5wY7RfTdDe3c3Oyac_S2Is/exec" method="get" target="_blank">
<label>DNI</label><br />
<input name="DNI" required="" type="text" /><br /><br />
<label>Correo electr&oacute;nico</label><br />
<input name="EMAIL" required="" type="email" /><br><br>
<button>Buscar</button>
</form>

El URL en la línea 1 es el de la webapp, que obtuvimos en nuestra última visita al panel de publicación en el apartado anterior.

Por su parte, a cada campo de texto del formulario le asignamos un nombre (atributo name) idéntico al del parámetro que representa (líneas 4, 7). Esta nomenclatura debe mantenerse intacta hasta el código GAS que captura sus valores por medio de las propiedades correspondientes en la función doGet() de la webapp.

Todo listo por tanto. Puedes probar el formulario, que encontrarás aquí, con los pares (DNI, email) que desees de la tabla de resultados. Por ejemplo, (95461354S, al95461354S@micentro.es).

Probando el formulario y la webapp.

La estrategia (más) correcta (6 / 6)

Ya has podido comprobar que esto funciona. Y probablemente de modo satisfactorio. Pero se puede mejorar, aunque a costa de complicar ligeramente las cosas.

La principal carencia de la estrategia en que se basa esta primera versión de la webapp reside en el hecho de que el formulario no está integrado dentro de ella.

Además, se usan scriptlets explícitos, que como ya sabemos se evalúan una sola vez y admiten la comunicación en un solo sentido entre los contextos de servidor y cliente, por lo que no es posible hacer cosas más sofisticadas, como por ejemplo incluir un botón para que el usuario solicite que se le envíen las calificaciones por correo electrónico tras consultarlas.

¿Y como se podría ir más allá? Prescindiendo de scriptlets y plantillas HTML e implementando en su lugar un mecanismo de comunicación bidireccional y totalmente dinámico entre el lado del cliente y el del servidor, operativo en tiempo de ejecución. Dicho de otro modo, se trata de construir una interfaz HTML mínima con la que el usuario de la webapp pueda interactuar en todo momento.

El diagrama funcional de esta versión 2 sería algo parecido a esto:

Segunda versión de la webapp, con el formulario integrado y comunicación bidireccional entre cliente y servidor.

Y este es el aspecto que tiene en funcionamiento, ahora ya en mi dominio G Suite:

Versión 2 de la webapp en acción, en el dominio en producción.

Como probablemente aprecies, el uso de un modelo de comunicación cliente - servidor más potente permite hilar ahora mucho más fino: aparecen distintos mensajes de estado, los elementos interactivos de la interfaz de usuario, cuadros de texto y botones, se activan y desactivan según el contexto, etc.

Además, tenemos una función adicional de envío de emails implementada mediante scriptlets explícitos, lo que en este caso tiene todo el sentido del mundo puesto que se trata de generar un elemento HTML, estático tras su producción, y enviarlo embebido en el cuerpo de un mensaje de correo electrónico.

Aspecto de las notificaciones por email de esta versión. Sí, las notas son distintas, se trata de otra prueba (buena vista).

Pero voy a dejar para otra ocasión el análisis de los recovecos de esta nueva implementación, entre los que se cuenta el uso de jQuery, librería JavaScript que comencé a utilizar en un desarrollo anterior y para mi ya imprescindible en estos momentos.

Creo que este artículo ya se ha extendido más allá de lo soportable, así que me despido, por ahora, no sin antes agradecerte tu atención y tu paciencia para llegar hasta aquí. Sin duda volveremos a hablar de Google Apps Script en este espacio. Y espero que sea más pronto que tarde.

Como siempre, la caja de comentarios aquí abajo es toda tuya.

Actualización 27/11/21:

Lo cierto es que llevo tiempo ¡años! 😅 con la segunda segunda parte de este artículo pendiente... pero no ha podido ser. Ten en cuenta que escribí esta entrada de blog en febrero de 2019, cuando estaba aprendiendo Apps Script (ojo con el código, que es muy mejorable), y desde entonces ha llovido mucho, pero mucho, y aunque sigo publicando con frecuencia materiales sobre Apps Script en mi perfil de Twitter y en mi otro blog, esto se ha quedado en el cajón de tareas pendientes.

Como no sé si en algún momento este artículo tendrá continuidad, aquí tienes ya no la V2, sino una V3, que además registra en la columna L de la tabla de notas la fecha en la que cada calificación fue consultada por primera vez. Cuidado porque este nuevo campo de información desplaza hacia la derecha la columna de comentarios en la hoja de cálculo que facilitaba como plantilla en el artículo.


Esta versión además es notablemente más rápida dado que lee los datos de la tabla de notas de un modo más eficiente que el utilizado por la V1, que tan solo pretendía mostrar un caso de uso donde una webapp puede resultar de utilidad, sin entrar en excesivas sutilezas con el código.

Estaré encantado de resolver cualquier duda que pueda surgir con el código de esta versión respondiendo a los comentarios de aquí abajo.

Un saludo y gracias por leerme todos estos años.

Comentarios

  1. Querido Pablo! Me acabo de leer casi todo! Soy Tu padawan! Pero me acabas de regalar una herramienta con aplicación directa a mi asignatura de Biología con gamificación (que se dice ahora) y me ahorro tener que mandar ranking de puntos en pdf con autocrat. Eso para empezar.
    Una de estas “noches” de desvelo lo pruebo!!! Gracias. Pablo Romero.

    ResponderEliminar
    Respuestas
    1. Primera noche. Hago copia de plantilla y actualizo idlibro. Publico. Implementar app web, me lanza el www.... y copio y pego en el navegador. Me devuelve: "Valor incorrecto (línea 25, archivo "Código", proyecto "Notas IN A2 GVA 2019 # v1 (blog)")". NPI del significado pero veo que los nombres de la hoja de notas se llama Pruebas y el otro se llama Notas...¿qué hago? ¿qué no hago? (aquí no puedo "usar la fuerza"). ;)

      Eliminar
    2. Nada más publicar el comentario me llegó la inspiración! Cambié la línea 25 el idlibro y el nombre de la hoja por los correspondientes y llegué!!! Ahora viene lo gordo. No me aclaro con lo que sigue...¿dónde escribo para que se abra mi formulario (unido a las hojas de datos) en mi site? (no se si logro explicarme...me estoy durmiendo...)Mañana lo hablamos.

      Eliminar
    3. Hola, Pablo. Disculpa, esta semana no he podido atender el blog como me hubiera gustado. La primera versión de la webapp toma sus datos de un formulario HTML estándar que debe estar incrustado en una página web. En este sentido, cualquier hospedaje web convencional te sirve. Lo suyo sería insertarlo en la web de tu centro, departamento, etc. En plan prueba yo he empleado un servicio denominado Netlify (https://www.netlify.com). Utilizando su plan gratuito puedes subir y publicar archivos (páginas) HTML, como la que facilito de ejemplo con el formulario que solicita DNI e Email, de un modo muy simple.

      Eliminar
    4. Es que me pierdo en los últimos pasos de la explicación de: insertarlo en mi site. No sé qué tengo que insertar. Entiendo que tú creas una página web para poner la consulta. Yo eso ya lo tengo pero no sé qué tengo que poner para que salga la ventanita de los datos a consultar. Además ¿cómo le digo las casillas que debe mostrar?. Igual debería saber más de los scripts...

      Eliminar
    5. Hola, Pablo. Te dejo mi email (pfelipm@gmail.com) y si quieres continuamos por allí. Con alguna que otra captura de pantalla igual la cosa la ves más clara. Un saludo.

      Eliminar
  2. De nuevo, una herramienta sencilla que resuelve un problema grande. Me ha recordado la funcionalidad de Rowlink, que identifica al usuario, este debe loguearse con Gsuite y le devuelve los datos de la fila que tenga su email en la primera celda. En este caso el uso es más amplio pues la identificación del usuario no viene dadan login sino por dos datos que sirven de contraseña. Gracias por ponerlo disponible para todos.

    ResponderEliminar
    Respuestas
    1. Sí, Rowlink funciona internamente de un modo muy parecido. También se puede conseguir algo similar con la función de proxy de AwesomeTable. De hecho es muy flexible y su funcionamiento puede personalizarse, aunque solo opera sobre cuentas de G Suite (https://sites.google.com/site/scriptsexamples/home/announcements/spotlightonawesometableproxy). De hecho mi primera idea fue usar esto (AT), pero me pareció más entretenido montármelo yo en código y de paso trascender la limitación de usar cuentas del dominio.

      Eliminar
  3. Impresionante, da gusto aprender de gente como tú. Mil gracias me acabas de abrir un nuevo mundo

    ResponderEliminar
  4. WOW! Todos mis respetos hacia el autor de este estupendo trabajo. ¡¡Magnífico como referencia!!

    ResponderEliminar
  5. Lo mejor que he podido ver sobre GAS. Excelente. Éxitos y Bendiciones

    ResponderEliminar
  6. Buenas Pablo! Me encanta tu trabajo. Actualmente llevo la evaluación de mi alumnado con Google Sheets y les muestro sus resultados con un gráfico radial, intentando hacer visible el progreso que van realizando. Pero de momento funciono compartiendo el enlace a la hoja excel y dejando solo editable la pestaña desplegable con sus nombres. Con lo que todos pueden acceder a la info de todos.
    Me encanta tu aportación sobretodo porqué ya no necesitaría "hacer público" el excel con toda la información. Mi pregunta es: ¿Seria posible que entre las calificaciones que da como resultado la appscript también hubiese un gráfico? ¡Muchas gracias de antemano!

    ResponderEliminar
    Respuestas
    1. Hola, Juan Manuel. Gracias por tus comentarios.

      Claro, aunque lógicamente supondría añadir el código necesario para dibujar la gráfica que quisieras mostrar en el navegador utilizando alguna biblioteca JavaScript, por ejemplo Chart.js, D3.js o Google Charts. En estos momentos no entra en mis planes modificar esta sencilla webapp, que solo pretende dar solución a un problema muy concreto, pero me apunto tu idea.

      Sin meterte en código, se me ocurren algunas maneras de compartir los datos de progreso de cada alumno de manera individualizada.

      La primera pasa por crear tantas hojas de cálculo como alumnos, compartir en solo lectura y llevar a ellas mediante funciones IMPORTRANGE los datos necesarios para generar los gráficos. Quizás podrías usar esta plantilla de hdc (IMPORTADOR++), que es capaz de llevar y traer datos entre hojas de cálculo de manera programada (https://github.com/pfelipm/importadorplus), para automatizar el proceso. En este caso las hdc compartidas de manera individual con cada alumno podrían estarlo con permisos de escritura. Ah, y hay también un complemento para hdc, SheetSpider (https://gsuite.google.com/marketplace/app/sheet_spider/572868608968) que podría venirte muy bien para esto.

      Otra posibilidad, mucho más limpia y elegante, pasaría por diseñar un informe con Google Data Studio que, conectado con tu hoja de cálculo, mostrara la información relevante. Desde hace unas semanas es posible que estos informes solo visualicen los datos del usuario (con cuenta de Google) que accede a ellos, lo que abre muchas posibilidades (https://twitter.com/pfelipm/status/1227885457507221506).

      Como ves alternativas hay unas cuantas, ya es cosa de que las valores y decidas cuál de ellas puede mejorar tu situación actual.

      Eliminar
  7. Hola, Pablo! Agradezco enormemente esta solución - la cuál encontré en un momento de nececidad. Seguí tus pasos, pero en mi caso creé una página tipo formulario para acceder a los datos del archivo de Excel, pero obtuve el siguiente error:

    Exception: El servicio Hojas de cálculo no ha podido acceder al documento con ID "*****".

    He cambiado permisos, ubicación del archivo, hasta publiqué la hoja de cálculo pero no he tenido éxito. Cambié la búsqueda del documento, pasando de usar el ID a usar la URL del documento, con el mismo resultado.

    Es posible que me pudieras dar una mano en esta situación? Le agradezco enormemente su ayuda.

    ResponderEliminar
    Respuestas
    1. buenos dias colocaste el nuevo id de la hoja de calculo?

      Eliminar
    2. Buenas tardes! Si, coloqué el id correspondiente pero sin éxito. Luego de mucho buscar en foros, leí que ya no se puede usar los métodos openById() u openByUrl().

      Fuente (una de varias): https://issuetracker.google.com/issues/36762646

      Eliminar
    3. ¡Hola! Los métodos openByID() / openByURL() siguen funcionando correctamente en Apps Script. La incidencia en IssueTracker que citas se circunscribe a su uso dentro de funciones personalizada de hojas de cálculo, donde desde hace ya tiempo no pueden utilizarse por cuestiones de seguridad. Veamos, sin ver tu código, lo primero en lo que pienso es que quizás no hayas publicado la webapp de modo que se ejecute en el contexto de tu propia cuenta (publicar como webapp >> ejecutar la aplicación como >> *Yo*). Comprueba eso primeramente.

      Eliminar
    4. Buenas tardes, Pablo!

      Pues para actualizar este hilo, ya pude traer la información de la hoja de cálculo....lo extraño es que la solución que me indicas ya la tenía configurada previamente, pero no funcionaba ...solo me mostraba ese error.

      Hoy verifiqué todo nuevamente, y esta vez me arrojó un error distinto...pero ya al menos me indicaba que si podía acceder al documento. ¿Será que era un problema de caché del navegador? no tengo idea. Lo curioso es que, a raíz de este error, me tocó hacer un formulario desde una hoja de cálculo para consultar otra, la cual funcionó muy bien.

      Les agradezco a todos los que colaboraron en la solución de este problema.

      Eliminar
  8. Exelente trabajo, muchas gracias por la explicación, mil gracias, me acabas de aclarar muchas cosas de Google App Script.

    ResponderEliminar
  9. por curiosidad, ya has publicado la segunda parte? :)

    ResponderEliminar
    Respuestas
    1. ¡Gracias! No, hasta ahora no ha habido 2ª parte... pero la técnica utilizada en la segunda versión de la webapp está descrita muy detalladamente en este artículo del blog: https://www.gsuiteparaeducacion.tk/2019/11/webapprecogecv.html.

      Eliminar
    2. Muchas gracias, lo leeré inmediatamente 😁😁

      Eliminar
  10. Estimado Pablo!
    Saludos desde Argentina!
    Muchísimas gracias por tan buena explicación. Me sirvió de mucha ayuda.
    Seria tan amable de brindarme una copia de la Version 2 de la webapp? Intente sacar ideas del post de RecogeCV, pero se me complico demasiado.
    Saludos!

    ResponderEliminar
    Respuestas
    1. Si me facilitas una dirección de email te la hago llegar. No me la pases por aquí en abierto, si te parece utiliza el formulario de contacto de esta página (https://tictools.tk/). De todos modos espero poder publicar en algún momento una 2ª parte de este artículo, he recibido numerosos correos con sugerencias, dudas, etc (gracias a todos). Espero sacar tiempo para hacerlo próximamente.

      Eliminar
  11. Hola buenas noches Pablo, darte mi enorabuena por el proyecto que has realizado, he aprendido un montón viendo tu explicación.
    Sería posible que viéramos el código de la segunda versión, estoy intentando hacer algo similar y me ayudaría muchísimo.
    Un saludo y gracias de antemano.

    ResponderEliminar
  12. Enhorabuena Pablo, eres un crack. A ver si compartes la segunda parte y nos ayudas, sería para ponerte en un pedestal!!

    ResponderEliminar
  13. Enhorabuena Pablo, muy bien explicado, sin tener muchos conocimientos he podido entender el código. Me gustaría ver esa segunda parte, creo que se podrían hacer muchos proyectos interesantes.
    Un saludo y gracias.

    ResponderEliminar
  14. Muchas gracias por vuestros comentarios. Como decía más arriba, tengo previsto lanzar una 2ª parte del artículo con su correspondiente código. A ver si puedo terminarlo en breve.

    ResponderEliminar
  15. Muchas gracias por tan fantastico aporte. Sin lugar a dudas las herramientas de la Gsuite nos estan sacando de apuro en el contexto de Covid, pero mucho mas gracias a explicaciones tan asertivas como esta. Ya puse manos en el asusnto para ir jugando con los codigos ya ver que me sale. por ahora todo bien. Espero en vilo al proximo post, para aprender sobre comunicacion cliente - servidor.

    ResponderEliminar
  16. Felicitaciones por tu trabajo. Tengo varias dudas con respecto a la implementación del código para ingreso de datos a una hoja de cálculo de google , por ejemplo llamar un ID como lo hace este ejercicio, que muestre unos datos específicos, pero que además se puedan llenar más datos en la hoja base. Me comunicaré contigo por este link https://tictools.tk para poder obtener una copia de la versión 2 y poder trabajarla. Muchas gracias por tu aporte.

    ResponderEliminar
  17. Muchas gracias Pablo, fantástico aporte. Por favor me puedes guiar en el punto llamado "El formulario (5 / 6)" el código HTML en que lugar lo agrego? Ya coloque el ID y me sale "faltan datos de autenticidad". La verdad no se en donde coloco este código!!

    ResponderEliminar
    Respuestas
    1. Tal y como está planteada esta v1 de la webapp, ese codigo debe ir dentro de un archivo HTML que has de publicar en tu sitio web. En el artículo utilicé una cuenta de prueba gratuita de Netlify (https://www.netlify.com/).

      Si no quieres complicarte la vida, simplemente copia y pega el código en una página creada con Google Sites. Solo tienes que utilizar la herramienta Insertar > Código. Verás que funciona perfectamente.

      Eliminar
  18. Muchas gracias Pablo, excelente aporte. por favor me puedes guiar en cómo puedo hacer que al consultar un Id me aparezca no solo la informacion de una fila sino varias filas. A gradecería tu ayuda!!

    ResponderEliminar
  19. Muchas gracias Pablo, excelente aporte. por favor me puedes guiar en cómo puedo hacer que al consultar un Id me aparezca no solo la informacion de una fila sino varias filas. A gradecería tu ayuda!!

    ResponderEliminar
  20. Hola Pablo, como consultar un ID , y este me traiga totas las filas que corresponde, la verdad no sé cómo

    ResponderEliminar
  21. Hola Pablo:
    He encontrado un ERROR; que me aparece...espero me puedas ayudar

    Mira todo funciona muy bien, he seguido y usado las plantillas que has dado. el formulario funciona y la web presenta los datos..

    Pero cuando se produce una modificación, esta no sale en los resultados HTML. Sigue saliendo los datos anteriores pero no los actualizados.

    Espero me puedas ayudar. Gracias

    ResponderEliminar
    Respuestas
    1. Hola, Daniel. He estado haciendo pruebas y no consigo reproducir el problema que indicas. Al modificar los datos en la hdc y tras recargar la página estos aparecen correctamente actualizados. Revisa todo el proceso, hay muchos pasos y tal vez haya algo no esté del todo correcto.

      Eliminar
  22. En primer lugar me gustaría agradeceros a todos vuestros comentarios recientes acerca de esta webapp, más concretamente en lo que respecta a esa 2ª versión sobre la que me estáis solicitando más información. Estoy abrumado por el alcance de este artículo y la enorme cantidad de comentarios recibidos en un blog tan humilde como este. Al mismo tiempo quiero disculparme por no haber podido contestar durante estas semanas, tampoco a algunos correos electrónicos que me habéis enviado. La carga de trabajo que estamos soportando en los centros educativos como consecuencia de la situación de pandemia mundial está siendo enorme y se extiende más allá de lo habitual en circunstancias normales. Simplemente no he encontrado el tiempo para hacerlo. Como decía en algún comentario anterior, tengo previsto publicar una 2ª parte del artículo que incluirá el código de la versión 2 (mejorado). En él trataré de resolver todas las dudas que me habéis hecho llegar. Muchas gracias a todos nuevamente por vuestra paciencia.

    ResponderEliminar
  23. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
    Respuestas
    1. El código que facilito no tiene línea 36, así que entiendo que has creado tu propia versión. El error que describes puede deberse a que el objeto sobre el que aplicas .toUpperCase() no es de tipo 'string'. Asegúrate de que la columna que contiene los DNI en la tabla contenga texto (si solo tiene números .getValue() devolverá un 'number', lo que provocará el error que indicas. Para evitarlo puedes sustituir en la línea 43 esto:

      hoja.getRange(filaRes, colDni).getValue().toUpperCase()

      por esto otro:

      String(hoja.getRange(filaRes, colDni).getValue()).toUpperCase()

      Eliminar
    2. Que grande que eres amigo gracias, y disculpe por abusar de sus conocimientos pero tengo una consulta mas ,es cierto que adapte tu código para hacer uno propio, por ejemplo en mi tabla hay mas de una fila con el mismo DNI, entonces al filtrarlo por la misma quisiera que me muestre todas las filas pertenecientes a ese DNI, pero solo me muestra la primera ya que el código es para una tabla estática, como podría hacer para que
      automáticamente la tabla se adapte a la cantidad de filas encontradas en la consulta ? se agradece
      su gran aporte estimado un saludo

      Eliminar
  24. Amigo buenas, gracias por atender mi duda anterior, una ultima consulta y es todo estimado,
    Mi hoja de calculo contiene mas de una fila con el mismo DNI como variable de la consulta, si por ejemplo tengo 5 filas al correr el script me lista solo la primera fila encontrada. como lo arias en tu plantilla para que muestre también las otra 4 filas y no solo la primera?. con esto me resuelves la vida amigo te agradezco de antemano.

    ResponderEliminar
    Respuestas
    1. La plantilla está diseñada para recuperar exactamente 6 elementos, correspondientes a una única prueba, y mostrarlos dentro de los "huecos" del archivo HTML resultados.html (líneas 30 - 35). Para permitir que se localizaran y mostrasen en pantalla varias notas (filas de la hoja de cálculo) sería necesario modificar de manera drástica el código, por ejemplo:

      1. Encontrar todos los registros para el DNI / Email solicitados (Código.gs, líneas 42 - 80).
      2. Generar en Código.gs el código HTML requerido para construir una tabla con las filas necesarias para acomodar todos los registros (notas) encontrados, en lugar de simplemente "empujar" los 6 valores actuales dentro de la plantilla resultados.html, como se hace ahora. Ese código HTML podría llevarse a la plantilla usando un único "force-printing scriptlet" (https://developers.google.com/apps-script/guides/html/templates#force-printing_scriptlets).

      Realmente en este caso lo razonable sería probablemente prescindir de scriptlets y utilizar el mecanismo de comunicación cliente - servidor descrito en la v2 del artículo. Me apunta la sugerencia para el artículo que estoy preparando.

      Eliminar
  25. un buen trabajo lo he podido resolver y funciona me encantaria ver la segunda parte de este gran magnifico trabajo donde todo este integrado en una sola pagina web.

    ResponderEliminar
  26. Buenas tardes.
    Excelente artículo.
    Me estaba costando entender el funcionamiento de las apps scripts y con este artículo queda mucho más claro, muchas gracias.
    ¿Podría enviarme por correo una copia de la versión 2 de la webapp, por favor? (dejaré mi email en el formulario de contacto que indica usted en respuesta a un pedido similar que le hacen más arriba.
    Desde ya gracias.
    Saludos, desde Argentina.

    ResponderEliminar
  27. hola podrias publicar el codigo del formulario? gracias

    ResponderEliminar
  28. Hola buen día Pablo tendrás un demo de solo consultar sin checkbox intente modificar pero me sale error, gracias

    ResponderEliminar
    Respuestas
    1. Hola, Armando. Para inutilizar la casilla de verificación que sirve para publicar (o no) las notas solo tienes que eliminar las líneas 29 y 90-94, que son las que comprueban su estado y muestran un mensaje informativo en caso de que no estén disponibles.

      Eliminar
  29. disculpa adecue el codigo a mi base de datos y tengo como 10,000 filas y es super lento en mostrar habra otra opcion?

    ResponderEliminar
    Respuestas
    1. Es normal. Este script en su v1 no es más que una demostración de cómo se pueden aplicar las webapps en un escenario de uso como este. La lentitud se debe a que el bucle en la línea 43 utiliza los métodos getLastRow() y, especialmente, getRange().getValue() en todas y cada una de las filas de la tabla hasta encontrar la buscada. Esto no constituye una buena práctica en absoluto. En su lugar, lo que se debe hacer es leer de una sola vez toda la tabla sobre una matriz, con getRange().getValues() y realizar la búsqueda sobre ella.

      Eliminar
  30. Lo cierto es que llevo tiempo queriendo escribir una segunda parte de este artículo, explicando bien el código de la versión avanzada en lugar de solo colgarlo por aquí, pero unas cosas y otras me lo han impedido. Por favor, tened en cuenta que escribí esta entrada de blog en febrero de 2019, cuando estaba aprendiendo Apps Script, y desde entonces ha llovido mucho. Pero mucho.

    Como no sé si en algún momento este artículo tendrá continuidad, aquí tenéis ya no la V2, sino una V3, que además registra en la columna L de la tabla de notas (ojo que desplaza a la derecha la de comentarios en la hoja de cálculo que facilitaba como plantilla) la fecha en la que cada calificación fue consultada por primera vez.

    Notas IN A2 GVA 2020 # v3 (blog)

    Esta versión además es notablemente más rápida dado que lee los datos de la tabla de notas del modo que le explico a Armando en el comentario anterior.

    Estaré encantado de resolver cualquier duda que pueda surgir con el código de esta versión.

    Aprovecho para deciros que he organizado para el próximo martes 30 de noviembre, a las 19:30 hora de Madrid, una charla - taller de iniciación a la creación de webapps, tal vez os resulte de interés.

    https://twitter.com/pfelipm/status/1458512701676822536

    Un saludo y gracias a todos por leerme.

    ResponderEliminar
  31. Hola he llegado aquí contento y agradecido Ahora como se hace para ver esto en el móvil funciona bien en el escritorio de lalap

    ResponderEliminar

Publicar un comentario