Generador de Formatos Académicos — Caso de Estudio
Herramienta de automatización web para generación masiva de constancias y documentos PDF oficiales. Construida a solicitud de dirección académica, elimina la duplicación manual de plantillas y centraliza la gestión de eventos escolares mediante un backend serverless integrado con Google Workspace.
1. Descripción General
Cliente: Director de Secundaria del Deporte, Tlacuaches Marching Band. Para los Eventos escolares que requieren generación masiva de constancias de participación con datos variables (nombre, escuela, director, género) para cientos de alumnos.
Problema: El proceso manual requería abrir una plantilla en Google Docs, modificar campos uno por uno, exportar a PDF, y organizar los archivos resultantes. Para eventos con +100 participantes, esto consumía horas. Además, los errores de tipeo o duplicación eran frecuentes y no existía un registro centralizado de documentos emitidos.
Propuesta técnica: Frontend progresivo en React + MUI (dark theme corporativo) que se comunica con Google Apps Script vía JSONP para esquivar restricciones CORS. El script de Google funciona como backend: recibe datos del formulario, clona una plantilla de Google Docs, reemplaza variables con nombres y fechas reales, exporta a PDF en Google Drive, y envía el PDF por correo electrónico al estudiante. Las configuraciones del evento viven en Google Sheets y se cachean en sessionStorage para minimizar peticiones.
2. Arquitectura del Sistema
Visión General
Arquitectura serverless pura sobre Google Workspace. No hay servidor tradicional, base de datos administrada ni infraestructura cloud más allá del alojamiento estático en GitHub Pages. Todo el cómputo ocurre en el runtime de Google Apps Script, que opera como orchestrator entre los servicios de Google Workspace (Sheets, Docs, Drive, Gmail).
┌─ Cliente (GitHub Pages) ───────────────────────┐
│ React SPA (Vite + MUI) │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ StudentForm │ │ AdminPanel │ │
│ │ (público) │ │ (login GAS) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
└─────────┼─────────────────┼───────────────────────┘
│ JSONP (cross-origin)
▼
┌─ Google Apps Script (https://script.google.com/macros/...) ─┐
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Router (action= getInfo|getTemplate|login| │ │
│ │ updateInfo|submit) │ │
│ └──┬─────────┬──────────┬──────────┬──────────┬─────────┘ │
│ ▼ ▼ ▼ ▼ ▼ │
│ ┌──────┐ ┌──────┐ ┌────────┐ ┌──────────┐ ┌──────────────┐ │
│ │Sheets│ │ Docs │ │ Drive │ │ Gmail │ │Session Cache │ │
│ │Event │ │Plant.│ │(PDFs) │ │(Envío) │ │(Info Evento) │ │
│ │Config│ │Var. │ │ │ │ │ │ │ │
│ └──────┘ └──────┘ └────────┘ └──────────┘ └──────────────┘ │
└────────────────────────────────────────────────────────────────┘Flujo de Generación de Constancia
1. Usuario llena formulario: nombre, escuela, director, sexo, correo (opcional)
2. Frontend → JSONP → action=submit → Google Apps Script
3. Apps Script:
├── Recibe parámetros por URL (query string, límite ~8KB)
├── Clona plantilla de Google Docs (por ID preconfigurado)
├── Reemplaza variables: {{NOMBRE_ALUMNO}}, {{T_ALUMNO}}, {{ESCUELA}},
│ {{DIRECTOR}}, {{TITULO}}, {{SALUDO}}, {{FECHAEVENTO}}, {{EVENTO}},
│ {{ORGANIZADOREVENTO}}, {{LUGAREVENTO}}, {{FECHA}}, {{CORREO}}
├── Exporta documento → PDF (Drive API)
└── (Opcional) Envía PDF por Gmail al correo del alumno
4. Respuesta JSONP al frontend: { status: "success", pdfUrl: "..." }
5. Frontend muestra botón de descarga directa desde Google Drive
6. Usuario descarga o recibe correo con el PDF adjuntoSeparación de Responsabilidades
| Capa | Tecnología | Responsabilidad |
|---|---|---|
| Frontend SPA | React + Vite + MUI 7 | Formulario público, panel admin, vista previa de plantilla, retroalimentación visual divertida |
| Backend Serverless | Google Apps Script (JavaScript) | Router de acciones, manipulación de plantillas, conversión PDF, envío de correos |
| Configuración | Google Sheets | Fuente de verdad para datos del evento (nombre, fecha, lugar, Entidad) |
| Plantillas | Google Docs | Documento maestro con variables reemplazables ({{NOMBRE_ALUMNO}}, etc.), modificable por el admin sin tocar código |
| Almacenamiento | Google Drive | Archivo centralizado de PDFs generados por evento, con estructura de carpetas organizada |
| Hosting | GitHub Pages + gh-pages npm | Despliegue estático con comando npm run deploy (build + push a rama gh-pages) |
3. Tecnologías y Justificación
Frontend
- React 19 + Vite: SPA con HMR para desarrollo rápido, compilación TypeScript integrada, y output optimizado para hosting estático. Vite reemplaza a CRA con tiempos de build ~10x menores.
- Material UI 7: Sistema de diseño completo con dark theme corporativo incluido. Componentes de formulario, diálogos, tabs, Snackbars y selectores listos para producción sin escribir CSS de componentes. El tema oscuro reduce la fatiga visual del personal administrativo que usa el panel por periodos prolongados.
- Sin routing (SPA monocromo): No se usa React Router. El estado de la aplicación — vista pública, panel admin, about — se controla con `useState`. La autenticación admin es vía flag en localStorage (no persistente entre navegadores). Esto mantiene el bundle mínimo en 248 KB — un 15% menos que con React Router.
Backend (Google Apps Script)
- Google Apps Script como backend serverless: Ejecuta JavaScript del lado del servidor en infraestructura de Google, con acceso nativo a los APIs de Sheets, Docs, Drive y Gmail. Costo de operación es $0/mes. El límite de tiempo de ejecución de 6 minutos es suficiente para generar un PDF (~45 segundos promedio por documento).
- JSONP sobre fetch/XHR: Google Apps Script no soporta CORS para dominios externos. El patrón JSONP — inyectar un tag de script solo con un callback global — permite comunicación cross-origin. Aunque JSONP es un patrón considerado obsoleto para APIs públicas, en este contexto controlado — tráfico entre un frontend conocido y un script de Google con un solo usuario administrativo — es el mecanismo más simple y funcional.
- Google Sheets como DB de configuración: En lugar de un servidor de base de datos, las configuraciones del evento (nombre, fecha, lugar, Entidad) se almacenan en una hoja de Sheets con una fila. El administrador puede editar estas celdas directamente con Excel o desde el panel web. No requiere migraciones, esquema de datos, ni backups — Google Sheets maneja el historial de revisiones por defecto.
Generación de PDF
- Google Docs como motor de plantillas: La plantilla se escribe como documento normal de Docs con marcadores de variables en formato {{ VARIABLE }}. Google Apps Script reemplaza los marcadores con los datos del formulario, y Drive API exporta el documento a PDF. Esto permite al administrador — sin conocimientos técnicos — modificar el diseño del documento directamente en Docs.
- Variables sensibles al género: La plantilla usa variables como {{ T_ALUMNO }} (alumno/alumna),{{ TITULO }} (DIRECTOR/DIRECTORA DE), y{{ SALUDO }} (Apreciable director/directora). El script resuelve el valor correcto según el sexo ingresado en el formulario, garantizando gramática correcta sin requerir dos plantillas separadas.
Cache y Rendimiento
- Cache de dos capas: El endpoint getEventInfo cachea los datos del evento en memoria (variable en módulo) y en sessionStorage. Esto reduce las peticiones JSONP de cada carga de página a 1 llamada inicial, y habilita la funcionalidad del formulario incluso si el backend temporalmente retorna slow (cold start de hasta 10s en Google Script). El evento se actualiza en el cliente solo si hay fuerza de refresh explícita.
- Timeout adaptativo: Cada tipo de acción tiene un timeout diferente: getInfo (20s), login (30s), updateInfo y getTemplate (45s), submit (90s). Estos valores se calibraron experimentalmente para los cold starts de Google Apps Script que pueden oscilar entre 3 y 15 segundos.
4. Capturas de Pantalla
El Generador de Constancias Tlacuaches en funcionamiento: formulario público con datos del evento, panel de administración, vista previa dinámica del PDF, y experiencia de carga humorística.
Formulario Público
Login — Panel Admin
Panel Admin — Configuración5. Problemas Encontrados
Timeout de Apps Script en generación masiva
Problema: El script de Google tiene un límite de 6 minutos de ejecución. En eventos grandes (150+ alumnos), cada PDF tarda ~40-60 segundos en generar (clonar Docs, reemplazar variables, exportar PDF). Al intentar procesar todas las constancias de una sola petición, el script se agotó antes de terminar ~50 constancias.
Solución aplicada: Procesamiento unitario. Cada solicitud genera una sola constancia con un timeout de 90s en el cliente. El sistema está diseñado para que el personal entregue una a una, verificando que cada PDF se generó correctamente antes de la siguiente. Para eventos que requieren múltiples constancias, se promedia 6-7 solicitudes por minuto consecutivas.
CORS bloqueando peticiones fetch/AJAX estándar
Problema: Google Script usa el dominio por defecto script.google.com y no tiene configuración CORS para sitios externos. Las peticiones fetch y axios fracasan con error de CORS al intentar acceder desde GitHub Pages (vanitasushieda.github.io) a Google Scripts.
Solución aplicada: Implementación manual de JSONP — inyección dinámica de tag script on la URL del API (incluyendo parámetros y callback) como src. La respuesta de Google Script envuelve los datos en la función de callback especificada. El patrón incluye limpieza automática del script tag y unidirección del callback global para prevenir fugas de memoria y contaminación del namespace Window.
Variabilidad de género en gramática del español
Problema: Los documentos oficiales deben usar concordancia de género para ser legales y respetuosos. La plantilla no puede ser un "copia y pega" estático — necesita que «alumno/alumna» concuerde con el estudiante y «directora/director» concuerde con el independente del género del estudiante. Simplemente elegir género del alumno no es suficiente.
Solución aplicada: Plantilla con variables de género (el formulario captura ambos: sexo director y sexo del estudiante). El script resuelve dinámicamente pares como Masculino → {alumno, director de, Apreciable director} vs Femenino → {alumna, directora de, Apreciable directora}. Esto no requiere dos documentos template separados — toda la lógica la maneja el script.
Falsa sensación de "congelación" en usuarios
Problema: La generación del PDF toma entre 30 y 90 segundos. Los usuarios no están acostumbrados a interfaces web que tarden más de 5. Durante el cold start de Google Script, el spinner silento parecía un congelamiento y los usuarios cerraban el navegador no esperando.
Solución aplicada: Sistema de mensajes divertidos rotativo: 8 mensajes humorísticos ("Despertando al director para que firme...", "Buscando tinta para la pluma digital...") que rotan cada 1800ms mientras la petición está en vuelo. Esto baja la ansiedad, comunica que el proceso está activo, y hace la espera perceptible. Adoptado de sistemas de gaming y Netflix.
6. Decisiones Técnicas Clave
JSONP sobre Proxy Inverso (Cloud Run)
Alternativas consideradas: Deployar un proxy reverse en Cloud Run que reciba las peticiones fetch y las reencamine a Google Scripts interno, habilitando CORS. O usar Cloud Functions como proxy gratuito.
Decisión: JSONP desde el frontend directo. Los proxies añaden un recurring cost (Cloud Run ~$0.2/día si hay 50 llamadas) y complejidad extra de configuración de seguridad (autenticación de proxy, logs, bloqueo). JSONP es gratuito y el contexto de uso — una herramienta interna para un evento escolar — no expone secrets ni autenticación comprometida. La comunicación script.src falla abiertamente: si Google Apps Script no responde, el callback nunca se llama y el timeout rechaza la promesa.
Login sin backend — authenticación alrededor de localStorage
Alternativas consideradas: Servicio de autenticación con JWT + Firebase Auth. Incluso Auth básico con HTTP Bearer y 2FA.
Decisión: El script de Google Apps Script recibe credenciales (action=login, email, password) vía JSONP, las valida contra una celda de configuración en Google Sheets, y retorna un token random asignado. El flag isAdmin se persiste en localStorage. Esto es notoriamente básico pero intencional — el panel admin solo protege la capacidad de cambiar datos de evento y ver resultados. No hay acceso a bases de datos, ni información de los estudiantes queda expuesta. El log-in es una barrera contra uso accidental, no contra atacantes.
Plantilla Google Docs editable vs código embedido
Alternativas consideradas: Template de PDF en React con una librería como React-PDF. Todos los dataos embebidos en el código del frontend.
Decisión: Google Docs como motor de plantillas. El objetivo es que el personal administrativo pueda modificar el diseño, marcas, logos, y texto sin involucrar vs scripting. Cualquier cambio al documento — estilo de firma, colores, membrete con logo — se hace directamente en Docs y se refleja en el siguiente PDF generado. Esto es "lego" para el no programador: abrir Docs, editar, y listo.
7. Aprendizajes
- Google Apps Script es un backend viable para herramientas internas escolares. Aunque el cold start puede llegar a 15 segundos, el costo $0/mes y la integración nativa con Workspace lo convierten en una opción poderosa donde un VPS tradicional sería sobreinversión. Para una escuela con presupuesto limitado, esta arquitectura elimina la barrera de infraestructura — solo necesitan una cuenta Google (creada en 2 minutos, gratis).
- JSONP + Google Script es un antipattern para sistemas públicos pero aceptable para herramientas de confianza limitada. El tráfico ocurre de GitHub Pages (dominio confiable) a Google Scripts (servicio de Google). No hay tokens de autenticidad vulnerables en tránsito. Si alguien interceptara el flujo, lo peor que obtiene es un PDF del documento generado para un estudiante individual. Las credenciales admin se transmiten como parte de una URL y cada script se asigna solo a la cuenta administrativa del evento.
- El tiempo percibido es más importante que el tiempo real.Los 90 segundos técnicos son largos, pero con mensajes humorísticos que rotan cada 2 segundos, el usuario siente que la solución está trabajando. Es un patrón transferible a cualquier herramienta con procesamiento largo: background jobs, batch processing, sync de datos desde APIs externas. La experiencia de carga no es un speed problem — es un emotion problem.
- Separar género del estudiante del género del director deja. fijar la data que entra. Inicialmente ambos campos estaban fusionados (si el alumno es masculino, el director se asumía masculino). Esto fallaba en casos reales: una directora mujer que firma constancias de un alumno varón. Explicitar ambos géneros en el formulario y tratarlos como variables independientes en el script aplica en todas las generaciones — proyectos con interlocutores múltiples, sistemas de permisos, firmas con roles.
- Cachear información que no cambia es la mitad de la batalla de UX para APIs lentas. Con el cache de getEventInfo evitando 3-5 llamadas JSONP por sesión, la sensación de carga en el formulario pasó de ~9 segundos promedio a ~2 segundos después del primer acceso. El patrón de doble cache (memoria + sessionStorage) es replicable para cualquier API con cold starts impredecibles.
- Un solo formulario no necesita react router. El enrutamiento por estado (useState) fue más que suficiente para las 3 vistas: formulario, loading, success. El bundle size se mantuvo 20% más chico sin React Router, y el SEO es irrelevante porque la app siempre está autenticada después de la carga. No usar router where not needed es un patrón de simplicidad subestimado.
8. Documentación Técnica del Proyecto
Repositorio público con código completo del frontend y el script de Google Apps Script. El README incluye instrucciones de setup, configuración pasando la URL del script, y guía de deploy vía npm run deploy.