Skip to content

vanjexdev/CrowWealth

Repository files navigation

Google Apps Script + Vite

Plantilla base para construir una web app de Google Apps Script usando Vite y publicar el resultado con clasp.

Flujo básico

  1. Instala dependencias con pnpm install.
  2. Copia .clasp.json.example a .clasp.json.
  3. Reemplaza scriptId con el ID de tu proyecto de Apps Script.
  4. Inicia sesión si hace falta con npx clasp login.
  5. Ejecuta pnpm dev para desarrollo local con Vite.
  6. Ejecuta pnpm push para compilar y subir a Apps Script.

Scripts

  • pnpm dev: servidor local de Vite.
  • pnpm build: genera dist/ compatible con Apps Script.
  • pnpm push: compila y sube a Apps Script.
  • pnpm deploy: compila, sube y crea un deployment.

Google Sheets como base de datos

Una forma muy común de trabajar con Google Apps Script es usar un spreadsheet como si fuera una base de datos simple.

Modelo propuesto

  • Un spreadsheet representa la base de datos.
  • Cada hoja representa una tabla.
  • La primera fila de cada hoja contiene los nombres de las columnas.
  • Cada fila siguiente representa un registro.
  • Conviene tener una columna id como identificador único.

Ejemplo de hoja users:

id name email createdAt
usr_001 Ana ana@email.com 2026-05-08T10:00:00.000Z
usr_002 Luis luis@email.com 2026-05-08T10:30:00.000Z

Operaciones CRUD

La idea es centralizar el acceso a Sheets en funciones de servidor dentro de Apps Script.

Puedes empezar con algo como esto en Code.js o moverlo luego a otro archivo .gs.

const DB_CONFIG = {
  spreadsheetId: 'REEMPLAZA_CON_TU_SPREADSHEET_ID',
};

function getDb() {
  return SpreadsheetApp.openById(DB_CONFIG.spreadsheetId);
}

function getTable(tableName) {
  const sheet = getDb().getSheetByName(tableName);

  if (!sheet) {
    throw new Error(`La hoja "${tableName}" no existe.`);
  }

  return sheet;
}

function getHeaders(sheet) {
  const lastColumn = sheet.getLastColumn();
  if (lastColumn === 0) {
    return [];
  }

  return sheet.getRange(1, 1, 1, lastColumn).getValues()[0];
}

function getRows(sheet) {
  const lastRow = sheet.getLastRow();
  const lastColumn = sheet.getLastColumn();

  if (lastRow < 2 || lastColumn === 0) {
    return [];
  }

  return sheet.getRange(2, 1, lastRow - 1, lastColumn).getValues();
}

function mapRow(headers, row) {
  return headers.reduce((record, header, index) => {
    record[header] = row[index];
    return record;
  }, {});
}

function mapRows(headers, rows) {
  return rows.map((row) => mapRow(headers, row));
}

Create

Inserta un nuevo registro al final de la hoja.

function insertRecord(tableName, payload) {
  const sheet = getTable(tableName);
  const headers = getHeaders(sheet);

  if (!headers.length) {
    throw new Error(`La hoja "${tableName}" no tiene encabezados.`);
  }

  const record = {
    id: payload.id || Utilities.getUuid(),
    createdAt: payload.createdAt || new Date().toISOString(),
    ...payload,
  };

  const row = headers.map((header) => {
    return header in record ? record[header] : '';
  });

  sheet.appendRow(row);

  return record;
}

Ejemplo:

function createUser() {
  return insertRecord('users', {
    name: 'Ana',
    email: 'ana@email.com',
  });
}

Read all

Obtiene todos los registros de una hoja.

function getAllRecords(tableName) {
  const sheet = getTable(tableName);
  const headers = getHeaders(sheet);
  const rows = getRows(sheet);

  return mapRows(headers, rows);
}

Ejemplo:

function listUsers() {
  return getAllRecords('users');
}

Read by id

Busca un registro por su id.

function getRecordById(tableName, id) {
  const records = getAllRecords(tableName);
  return records.find((record) => String(record.id) === String(id)) || null;
}

Ejemplo:

function findUser() {
  return getRecordById('users', 'usr_001');
}

Update

Actualiza una fila existente buscando por id.

function updateRecord(tableName, id, payload) {
  const sheet = getTable(tableName);
  const headers = getHeaders(sheet);
  const rows = getRows(sheet);

  const rowIndex = rows.findIndex((row) => String(row[0]) === String(id));

  if (rowIndex === -1) {
    throw new Error(`No se encontró un registro con id "${id}".`);
  }

  const currentRecord = mapRow(headers, rows[rowIndex]);
  const nextRecord = {
    ...currentRecord,
    ...payload,
    id: currentRecord.id,
  };

  const nextRow = headers.map((header) => {
    return header in nextRecord ? nextRecord[header] : '';
  });

  sheet.getRange(rowIndex + 2, 1, 1, headers.length).setValues([nextRow]);

  return nextRecord;
}

Ejemplo:

function editUser() {
  return updateRecord('users', 'usr_001', {
    name: 'Ana Maria',
  });
}

Delete

Elimina una fila por id.

function deleteRecord(tableName, id) {
  const sheet = getTable(tableName);
  const rows = getRows(sheet);

  const rowIndex = rows.findIndex((row) => String(row[0]) === String(id));

  if (rowIndex === -1) {
    throw new Error(`No se encontró un registro con id "${id}".`);
  }

  sheet.deleteRow(rowIndex + 2);

  return { success: true, id };
}

Ejemplo:

function removeUser() {
  return deleteRecord('users', 'usr_001');
}

Recomendación de estructura de hojas

Para que el CRUD sea fácil de mantener, intenta seguir estas reglas:

  • Usa siempre la fila 1 como encabezado.
  • Deja id en la primera columna.
  • Usa nombres de columnas estables: name, email, status, createdAt, updatedAt.
  • Evita celdas combinadas, colores como fuente de datos o fórmulas mezcladas con registros.

Cómo llamarlo desde el frontend

Cuando la app ya corre dentro de Google Apps Script, puedes usar google.script.run.

Ejemplo desde el cliente:

function loadUsers() {
  google.script.run
    .withSuccessHandler((users) => {
      console.log('Usuarios:', users);
    })
    .withFailureHandler((error) => {
      console.error('Error:', error);
    })
    .getAllRecords('users');
}

Limitaciones de este enfoque

Usar Google Sheets como base de datos funciona bien para proyectos internos, MVPs y automatizaciones, pero tiene límites:

  • No reemplaza una base de datos relacional real.
  • Las búsquedas grandes pueden ser lentas si la hoja crece mucho.
  • No hay relaciones, índices ni transacciones reales.
  • Hay que cuidar la concurrencia si varios usuarios escriben al mismo tiempo.

Recomendaciones prácticas

  • Agrega una hoja por entidad: users, orders, products.
  • Crea funciones genéricas como las de arriba y luego wrappers por entidad.
  • Si habrá escrituras simultáneas, considera LockService.
  • Si necesitas auditoría, agrega columnas como createdAt, updatedAt, createdBy.

Próximo paso sugerido

Una buena evolución para esta plantilla sería mover el CRUD a un archivo como src/server/sheets.gs o Code.js dividido por responsabilidades, y crear en el frontend una capa cliente que encapsule google.script.run.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors