Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ===============================================================
- // REGISTRO DE EVENTO — Google Apps Script
- // ===============================================================
- const HOJAS = {
- REGISTROS: 'Registros',
- CONFIGURACION: 'Configuracion',
- CAMPOS: 'Campos',
- };
- // ===============================================================
- // doGet — enruta todas las peticiones
- // ===============================================================
- function doGet(e) {
- try {
- const accion = e.parameter.action;
- if (accion === 'campos') return devolverCampos();
- if (accion === 'cupos') return devolverCupos();
- if (accion === 'registrar') return registrarParticipante(e.parameter);
- return respuesta({ status: 'ok', mensaje: 'API activa.' });
- } catch (err) {
- return respuesta({ status: 'error', mensaje: 'Error interno: ' + err.message });
- }
- }
- // ===============================================================
- // LEER CONFIGURACION (uso interno — solo para correos)
- // ===============================================================
- function leerConfig() {
- const hoja = obtenerOCrearHojaConfig();
- const datos = hoja.getDataRange().getValues();
- const config = {};
- for (let i = 1; i < datos.length; i++) {
- const clave = datos[i][0] ? datos[i][0].toString().trim() : '';
- const valor = datos[i][1] !== undefined ? datos[i][1].toString().trim() : '';
- if (clave) config[clave] = valor;
- }
- return config;
- }
- // ===============================================================
- // LEER CAMPOS desde hoja "Campos"
- // ===============================================================
- function leerCampos() {
- const hoja = obtenerOCrearHojaCampos();
- const datos = hoja.getDataRange().getValues();
- const campos = [];
- if (datos.length < 2) return campos;
- for (let i = 1; i < datos.length; i++) {
- const fila = datos[i];
- const nombre = fila[0] ? fila[0].toString().trim() : '';
- if (!nombre) continue;
- campos.push({
- nombre: nombre,
- etiqueta: fila[1] ? fila[1].toString().trim() : nombre,
- tipo: fila[2] ? fila[2].toString().trim().toLowerCase() : 'text',
- placeholder: fila[3] ? fila[3].toString().trim() : '',
- requerido: fila[4] ? fila[4].toString().trim().toLowerCase() === 'true' : false,
- opciones: fila[5] ? fila[5].toString().split(',').map(o => o.trim()).filter(Boolean) : [],
- orden: fila[6] ? parseInt(fila[6]) : i,
- });
- }
- campos.sort((a, b) => a.orden - b.orden);
- return campos;
- }
- function devolverCampos() {
- return respuesta({ campos: leerCampos() });
- }
- // ===============================================================
- // CUPOS DISPONIBLES
- // ===============================================================
- function devolverCupos() {
- const config = leerConfig();
- const cupoMaximo = config.CUPO_MAXIMO ? parseInt(config.CUPO_MAXIMO) : 0;
- const hoja = obtenerOCrearHojaRegistros([]);
- const registrados = Math.max(0, hoja.getLastRow() - 1);
- return respuesta({
- registrados: registrados,
- maximo: cupoMaximo,
- ilimitado: cupoMaximo === 0,
- });
- }
- // ===============================================================
- // SINCRONIZAR COLUMNAS
- // Detecta campos nuevos en hoja Campos y los agrega a Registros
- // siempre antes de las columnas Estado y Espera #
- // ===============================================================
- function sincronizarColumnas(hoja, campos) {
- const encabezadosActuales = hoja.getRange(1, 1, 1, hoja.getLastColumn()).getValues()[0];
- // Columnas fijas que siempre van al final
- const COLS_FIJAS = ['Estado', 'Espera #'];
- // Detectar qué etiquetas de campos NO están aun en la hoja
- const etiquetasActuales = encabezadosActuales.filter(h => h !== '');
- const etiquetasFaltantes = campos
- .map(c => c.etiqueta)
- .filter(etiqueta => !etiquetasActuales.includes(etiqueta));
- if (etiquetasFaltantes.length === 0) return; // nada que hacer
- // Insertar cada columna nueva antes de "Estado"
- const colEstado = encabezadosActuales.indexOf('Estado') + 1; // base 1
- etiquetasFaltantes.forEach((etiqueta, i) => {
- // Insertar columna antes de Estado (que se va desplazando)
- hoja.insertColumnBefore(colEstado + i);
- const colNueva = colEstado + i;
- // Escribir encabezado con mismo formato que los existentes
- const celdaHeader = hoja.getRange(1, colNueva);
- celdaHeader.setValue(etiqueta);
- celdaHeader.setBackground('#0f0e0d').setFontColor('#ffffff').setFontWeight('bold');
- // Rellenar filas existentes con guion
- const ultFila = hoja.getLastRow();
- if (ultFila > 1) {
- hoja.getRange(2, colNueva, ultFila - 1, 1).setValue('—');
- }
- Logger.log('Columna nueva agregada: ' + etiqueta);
- });
- }
- // ===============================================================
- // REGISTRAR PARTICIPANTE
- // ===============================================================
- function registrarParticipante(datos) {
- const config = leerConfig();
- const campos = leerCampos();
- const hoja = obtenerOCrearHojaRegistros(campos);
- // Sincronizar columnas antes de escribir
- // Si hay campos nuevos en Sheets, se agregan automaticamente
- sincronizarColumnas(hoja, campos);
- const cupoMaximo = config.CUPO_MAXIMO ? parseInt(config.CUPO_MAXIMO) : 0;
- const registrados = Math.max(0, hoja.getLastRow() - 1);
- // 1. Validar campos requeridos
- for (const campo of campos) {
- if (campo.requerido && !datos[campo.nombre]) {
- return respuesta({ status: 'error', mensaje: 'El campo "' + campo.etiqueta + '" es requerido.' });
- }
- }
- // 2. Validar email
- const campEmail = campos.find(c => c.tipo === 'email');
- if (campEmail) {
- const emailVal = datos[campEmail.nombre] || '';
- if (!esEmailValido(emailVal)) {
- return respuesta({ status: 'error', mensaje: 'El correo no es valido.' });
- }
- if (emailYaRegistrado(hoja, emailVal, campos)) {
- return respuesta({ status: 'error', mensaje: 'Este correo ya esta registrado.' });
- }
- }
- // 3. Verificar cupo
- const sinLimite = cupoMaximo === 0;
- const estado = sinLimite || registrados < cupoMaximo ? 'Confirmado' : 'Lista de espera';
- let posicionEspera = 0;
- if (estado === 'Lista de espera') {
- posicionEspera = contarEspera(hoja) + 1;
- }
- // 4. Armar fila leyendo encabezados reales del Sheet
- // Esto respeta el orden exacto de columnas incluyendo las nuevas
- const encabezados = hoja.getRange(1, 1, 1, hoja.getLastColumn()).getValues()[0];
- const timestamp = new Date().toLocaleString('es-MX', { timeZone: 'America/Mexico_City' });
- const filaDatos = encabezados.map(header => {
- if (header === 'Timestamp') return timestamp;
- if (header === 'Estado') return estado;
- if (header === 'Espera #') return posicionEspera > 0 ? '#' + posicionEspera : '—';
- const campo = campos.find(c => c.etiqueta === header);
- return campo ? (datos[campo.nombre] || '—') : '—';
- });
- hoja.appendRow(filaDatos);
- // Colorear celda de estado
- const filaEscrita = hoja.getLastRow();
- const colEstado = encabezados.indexOf('Estado') + 1;
- if (colEstado > 0) {
- if (estado === 'Confirmado') {
- hoja.getRange(filaEscrita, colEstado).setBackground('#e8f5e9').setFontColor('#2e7d32');
- } else {
- hoja.getRange(filaEscrita, colEstado).setBackground('#fff8e1').setFontColor('#f57f17');
- }
- }
- // 5. Enviar correos
- const enviarEmail = config.ENVIAR_EMAIL !== 'false';
- const notifOrganizador = config.NOTIF_ORGANIZADOR !== 'false';
- if (enviarEmail && campEmail) {
- enviarConfirmacion(datos, campos, config, estado, posicionEspera);
- }
- if (notifOrganizador && config.EMAIL_ORGANIZADOR) {
- notificarOrganizador(datos, campos, config, estado, registrados + 1, cupoMaximo);
- }
- // 6. Responder
- if (estado === 'Confirmado') {
- return respuesta({ status: 'ok', mensaje: 'Registro confirmado.' });
- } else {
- return respuesta({ status: 'espera', posicion: posicionEspera, mensaje: 'Cupo completo. Agregado a lista de espera.' });
- }
- }
- // ===============================================================
- // EMAIL — Confirmacion al participante
- // ===============================================================
- function enviarConfirmacion(datos, campos, config, estado, posicionEspera) {
- try {
- const campEmail = campos.find(c => c.tipo === 'email');
- if (!campEmail) return;
- const emailDestino = datos[campEmail.nombre];
- const campNombre = campos.find(c => c.nombre === 'nombre');
- const nombreVal = campNombre ? (datos[campNombre.nombre] || '') : '';
- let asunto, cuerpo;
- if (estado === 'Confirmado') {
- asunto = 'Tu registro esta confirmado — ' + (config.NOMBRE_EVENTO || 'Evento');
- let filasDetalle = '';
- if (config.FECHA_EVENTO) filasDetalle += '<p style="margin:0 0 8px;"><strong>Fecha:</strong> ' + config.FECHA_EVENTO + '</p>';
- if (config.HORA_EVENTO) filasDetalle += '<p style="margin:0 0 8px;"><strong>Horario:</strong> ' + config.HORA_EVENTO + '</p>';
- if (config.LINK_ACCESO) filasDetalle += '<p style="margin:0;"><strong>Enlace:</strong> <a href="' + config.LINK_ACCESO + '">' + config.LINK_ACCESO + '</a></p>';
- cuerpo = '<div style="font-family:Arial,sans-serif;max-width:560px;margin:auto;color:#1a1a1a;">'
- + '<h2 style="margin-bottom:4px;">Hola' + (nombreVal ? ', ' + nombreVal : '') + '!</h2>'
- + '<p style="color:#555;margin-bottom:24px;">Tu registro al <strong>' + (config.NOMBRE_EVENTO || 'evento') + '</strong> fue confirmado con exito.</p>'
- + '<div style="background:#f5f5f5;border-radius:12px;padding:20px 24px;margin-bottom:24px;">' + filasDetalle + '</div>'
- + '<p style="color:#777;font-size:13px;">Guarda este correo. El enlace de acceso es solo para uso personal.</p>'
- + '</div>';
- } else {
- asunto = 'En lista de espera — ' + (config.NOMBRE_EVENTO || 'Evento');
- cuerpo = '<div style="font-family:Arial,sans-serif;max-width:560px;margin:auto;color:#1a1a1a;">'
- + '<h2>Hola' + (nombreVal ? ', ' + nombreVal : '') + '</h2>'
- + '<p>El evento esta lleno, pero quedaste en la <strong>lista de espera #' + posicionEspera + '</strong>.</p>'
- + '<p>Si se libera un lugar, te notificaremos de inmediato.</p>'
- + '</div>';
- }
- MailApp.sendEmail({
- to: emailDestino,
- subject: asunto,
- htmlBody: cuerpo,
- name: config.NOMBRE_REMITENTE || 'Registro de Evento',
- });
- } catch (err) {
- Logger.log('Error email confirmacion: ' + err.message);
- }
- }
- // ===============================================================
- // EMAIL — Notificacion al organizador
- // ===============================================================
- function notificarOrganizador(datos, campos, config, estado, totalRegistrados, cupoMaximo) {
- try {
- const asunto = '[Nuevo registro] ' + estado + ' — ' + (config.NOMBRE_EVENTO || 'Evento');
- let filasDatos = '';
- campos.forEach(campo => {
- filasDatos += '<p><strong>' + campo.etiqueta + ':</strong> ' + (datos[campo.nombre] || '—') + '</p>';
- });
- const totalStr = cupoMaximo > 0
- ? totalRegistrados + ' / ' + cupoMaximo
- : totalRegistrados + ' (sin limite)';
- const cuerpo = '<div style="font-family:Arial,sans-serif;color:#1a1a1a;">'
- + '<h3>Nuevo registro recibido</h3>'
- + filasDatos
- + '<p><strong>Estado:</strong> ' + estado + '</p>'
- + '<hr/>'
- + '<p style="color:#777;">Total registrados: ' + totalStr + '</p>'
- + '</div>';
- MailApp.sendEmail({
- to: config.EMAIL_ORGANIZADOR,
- subject: asunto,
- htmlBody: cuerpo,
- });
- } catch (err) {
- Logger.log('Error email organizador: ' + err.message);
- }
- }
- // ===============================================================
- // CREAR HOJAS
- // ===============================================================
- function obtenerOCrearHojaRegistros(campos) {
- const ss = SpreadsheetApp.getActiveSpreadsheet();
- let hoja = ss.getSheetByName(HOJAS.REGISTROS);
- if (!hoja) {
- hoja = ss.insertSheet(HOJAS.REGISTROS);
- const headers = ['Timestamp'];
- campos.forEach(c => headers.push(c.etiqueta));
- headers.push('Estado', 'Espera #');
- hoja.getRange(1, 1, 1, headers.length).setValues([headers]);
- hoja.getRange(1, 1, 1, headers.length)
- .setBackground('#0f0e0d').setFontColor('#ffffff').setFontWeight('bold');
- hoja.setFrozenRows(1);
- }
- return hoja;
- }
- function obtenerOCrearHojaConfig() {
- const ss = SpreadsheetApp.getActiveSpreadsheet();
- let hoja = ss.getSheetByName(HOJAS.CONFIGURACION);
- if (!hoja) {
- hoja = ss.insertSheet(HOJAS.CONFIGURACION);
- hoja.getRange(1, 1, 1, 3).setValues([['Clave', 'Valor', 'Descripcion']]);
- hoja.getRange(1, 1, 1, 3)
- .setBackground('#0f0e0d').setFontColor('#ffffff').setFontWeight('bold');
- hoja.setFrozenRows(1);
- const defaults = [
- ['CUPO_MAXIMO', '30', 'Numero maximo de registros. Pon 0 para ilimitado.'],
- ['ENVIAR_EMAIL', 'true', 'true = enviar confirmacion por correo al participante'],
- ['NOTIF_ORGANIZADOR', 'true', 'true = notificar al organizador en cada registro'],
- ['EMAIL_ORGANIZADOR', '[email protected]', 'Correo del organizador para notificaciones'],
- ['NOMBRE_REMITENTE', 'Registro de Evento', 'Nombre que aparece como remitente en los correos'],
- ['NOMBRE_EVENTO', 'Nombre de tu evento', 'Aparece en el asunto y cuerpo del correo'],
- ['FECHA_EVENTO', 'Sabado 17 de Mayo, 2025', 'Aparece en el correo de confirmacion'],
- ['HORA_EVENTO', '10:00 AM - 2:00 PM', 'Aparece en el correo de confirmacion'],
- ['LINK_ACCESO', 'https://zoom.us/j/XXXXXXXXX', 'Enlace Zoom, Meet u otro. Aparece en el correo'],
- ];
- hoja.getRange(2, 1, defaults.length, 3).setValues(defaults);
- hoja.setColumnWidth(1, 180);
- hoja.setColumnWidth(2, 240);
- hoja.setColumnWidth(3, 360);
- hoja.getRange(2, 3, defaults.length, 1)
- .setFontColor('#888888').setFontStyle('italic');
- }
- return hoja;
- }
- function obtenerOCrearHojaCampos() {
- const ss = SpreadsheetApp.getActiveSpreadsheet();
- let hoja = ss.getSheetByName(HOJAS.CAMPOS);
- if (!hoja) {
- hoja = ss.insertSheet(HOJAS.CAMPOS);
- const headers = ['nombre', 'etiqueta', 'tipo', 'placeholder', 'requerido', 'opciones', 'orden'];
- hoja.getRange(1, 1, 1, headers.length).setValues([headers]);
- hoja.getRange(1, 1, 1, headers.length)
- .setBackground('#0f0e0d').setFontColor('#ffffff').setFontWeight('bold');
- hoja.setFrozenRows(1);
- const ejemplos = [
- ['nombre', 'Nombre', 'text', 'Tu nombre', 'true', '', '1'],
- ['apellido', 'Apellido', 'text', 'Tu apellido', 'true', '', '2'],
- ['email', 'Correo electronico', 'email', '[email protected]', 'true', '', '3'],
- ['telefono', 'WhatsApp', 'tel', '+52 55 1234 5678', 'false', '', '4'],
- ['sesion', 'Sesion de interes', 'select', '', 'true', 'Opcion 1, Opcion 2, Opcion 3', '5'],
- ];
- hoja.getRange(2, 1, ejemplos.length, 7).setValues(ejemplos);
- hoja.setColumnWidth(1, 130);
- hoja.setColumnWidth(2, 180);
- hoja.setColumnWidth(3, 100);
- hoja.setColumnWidth(4, 200);
- hoja.setColumnWidth(5, 90);
- hoja.setColumnWidth(6, 280);
- hoja.setColumnWidth(7, 70);
- hoja.getRange(1, 3).setNote('Tipos validos: text, email, tel, textarea, select');
- hoja.getRange(1, 5).setNote('Escribe true o false');
- hoja.getRange(1, 6).setNote('Solo para tipo "select". Separa las opciones con coma.');
- }
- return hoja;
- }
- // ===============================================================
- // INICIALIZAR — ejecutar manualmente una sola vez
- // ===============================================================
- function ovReInicializarHojas() {
- obtenerOCrearHojaConfig();
- obtenerOCrearHojaCampos();
- obtenerOCrearHojaRegistros([]);
- Logger.log('Hojas creadas correctamente. Ya puedes editar Configuracion y Campos.');
- }
- // ===============================================================
- // HELPERS
- // ===============================================================
- function emailYaRegistrado(hoja, email, campos) {
- const ultFila = hoja.getLastRow();
- if (ultFila < 2) return false;
- const campEmail = campos.find(c => c.tipo === 'email');
- if (!campEmail) return false;
- // Buscar columna por encabezado en lugar de posicion fija
- const encabezados = hoja.getRange(1, 1, 1, hoja.getLastColumn()).getValues()[0];
- const colEmail = encabezados.indexOf(campEmail.etiqueta) + 1;
- if (colEmail < 1) return false;
- const emails = hoja.getRange(2, colEmail, ultFila - 1, 1).getValues().flat();
- return emails.some(e => e.toString().toLowerCase() === email.toLowerCase());
- }
- function contarEspera(hoja) {
- const ultFila = hoja.getLastRow();
- if (ultFila < 2) return 0;
- const encabezados = hoja.getRange(1, 1, 1, hoja.getLastColumn()).getValues()[0];
- const colEstado = encabezados.indexOf('Estado') + 1;
- if (colEstado < 1) return 0;
- const estados = hoja.getRange(2, colEstado, ultFila - 1, 1).getValues().flat();
- return estados.filter(s => s === 'Lista de espera').length;
- }
- function esEmailValido(email) {
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
- }
- function respuesta(obj) {
- return ContentService
- .createTextOutput(JSON.stringify(obj))
- .setMimeType(ContentService.MimeType.JSON);
- }
Advertisement
Add Comment
Please, Sign In to add comment