Migracions de schema¶
Guia per afegir, provar i diagnosticar migracions d'estructura de la base de dades (Google Sheets).
Conceptes bàsics¶
El sistema de migracions garanteix que totes les instal·lacions —noves i existents— tinguin sempre l'estructura de fulls correcta, sense perdre dades.
| Element | Ubicació | Descripció |
|---|---|---|
CURRENT_SCHEMA_VERSION |
Migration.gs |
Versió objectiu. Font de veritat. |
SCHEMA_VERSION |
PropertiesService |
Versió actual de la instal·lació. |
checkAndMigrate() |
Migration.gs |
S'executa a cada doGet(). Aplica les migracions pendents. |
MIGRATIONS |
Migration.gs |
Array ordenat de migracions registrades. |
Quan s'executa¶
checkAndMigrate() és cridat per doGet() a cada càrrega de la webapp. El cost habitual és mínim: una lectura de PropertiesService i una comparació d'enters. Només executa codi quan SCHEMA_VERSION < CURRENT_SCHEMA_VERSION.
Tres tipus d'instal·lació¶
Instal·lació nova → Setup.gs estableix SCHEMA_VERSION = CURRENT_SCHEMA_VERSION
checkAndMigrate() no fa res.
Instal·lació existent → SCHEMA_VERSION és null (anterior al sistema de migracions)
(pre-migracions) checkAndMigrate() la marca com a v1 i comprova migracions posteriors.
Instal·lació desactualitzada → SCHEMA_VERSION < CURRENT_SCHEMA_VERSION
checkAndMigrate() aplica les migracions pendents en ordre.
Quan cal una migració¶
Cal crear una migració quan el canvi afecta l'estructura del Sheets:
| Canvi | Cal migració? |
|---|---|
| Nova columna a un full existent | ✅ |
| Nou full (nova pestanya al Sheets) | ✅ |
Nova clau al full Config |
✅ |
Canvi de codi .gs o .html |
❌ |
| Canvi d'estil o traducció | ❌ |
| Modificació de dades existents | ❌ * |
* Les migracions han de ser additives: mai modifiquen ni eliminen dades o columnes existents. Si cal reorganitzar dades, documenta el procediment manual a les notes de la versió.
Com afegir una migració — pas a pas¶
Pas 1 — Escriu la funció de migració¶
A Migration.gs, afegeix una nova funció privada al final del fitxer:
function _migrate_v2() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
// Usa les utilitats disponibles (idempotents):
_ensureColumn(ss.getSheetByName('Usuarios'), 'Departament');
_ensureConfigKey(ss, 'nova_clau', 'valor_defecte', 'Descripció de la clau');
// _ensureSheet(ss, 'NouFull', ['Col1', 'Col2'], '#4f46e5');
}
Regles obligatòries per a la funció
- Additiva: usa
_ensureColumn,_ensureSheet,_ensureConfigKey— maideleteSheet,deleteColumnni sobreescriptures de dades. - Idempotent: si s'executa dues vegades, el resultat ha de ser idèntic. Les utilitats ja ho garanteixen.
- Aïllada: no crida altres funcions de negoci (
getReservas, etc.). Treballa directament amb l'objecteSpreadsheet.
Pas 2 — Registra la migració a l'array MIGRATIONS¶
const MIGRATIONS = [
{ version: 2,
description: 'Afegeix columna Departament a Usuarios i clau nova_clau a Config',
fn: _migrate_v2 },
];
Ordre és obligatori
Les entrades han d'estar ordenades per version de menor a major. Mai eliminis ni reordenis entrades existents.
Pas 3 — Incrementa CURRENT_SCHEMA_VERSION¶
Pas 4 — Actualitza el comentari d'historial¶
/**
* Historial:
* v1 — Schema inicial: Recursos, Tramos, Disponibilidad...
* v2 — Afegeix columna Departament a Usuarios; nova clau a Config
*/
const CURRENT_SCHEMA_VERSION = 2;
Pas 5 — Puja el codi¶
La migració s'aplicarà automàticament la propera vegada que qualsevol usuari carregui la webapp. No cal cap acció manual a les instal·lacions existents.
Pas 6 — Verifica l'execució als logs¶
A l'editor d'Apps Script: Execucions (menú lateral) → filtra per doGet → busca les entrades [Migration]:
[Migration] Schema actual: v1 → objectiu: v2
[Migration] Aplicant v2: Afegeix columna Departament a Usuarios...
[Migration] v2 completada.
Si la migració s'ha aplicat correctament, les execucions posteriors no mostraran res relacionat amb migracions (retorn immediat).
Referència: utilitats disponibles¶
_ensureColumn(sheet, columnName)¶
Afegeix una columna al final del full si la capçalera no existeix.
const sheet = ss.getSheetByName('Usuarios');
_ensureColumn(sheet, 'Departament');
// Si 'Departament' ja existeix → no fa res
// Si no existeix → l'afegeix a la primera columna buida
_ensureSheet(ss, sheetName, headers, color)¶
Crea un full nou amb capçaleres formatades si el full no existeix.
_ensureSheet(ss, 'NouFull', ['ID', 'Nom', 'Estat'], '#4f46e5');
// Si 'NouFull' ja existeix → no fa res
// Si no existeix → el crea amb les capçaleres formatades
| Paràmetre | Tipus | Descripció |
|---|---|---|
ss |
Spreadsheet |
SpreadsheetApp.getActiveSpreadsheet() |
sheetName |
string |
Nom de la pestanya |
headers |
string[] |
Noms de les columnes |
color |
string (opcional) |
Color hex de la capçalera (p. ex. '#4f46e5') |
_ensureConfigKey(ss, key, defaultValue, description)¶
Afegeix una nova clau al full Config si no existeix.
_ensureConfigKey(ss, 'nova_funcionalitat', 'FALSE', 'Activa la nova funcionalitat');
// Si la clau ja existeix → no fa res (no sobreescriu el valor de l'usuari)
// Si no existeix → afegeix una fila nova al final del full Config
Per a valors booleans
Usa strings 'TRUE' / 'FALSE' (no booleans JS), perquè el full Config llegeix tot com a text.
Exemple complet¶
Escenari: la versió 3 afegeix una nova pestanya Avisos per a comunicats als usuaris i una nova clau de configuració avisos_activats.
// ── Migration.gs ──────────────────────────────────────────────────────────
/**
* Historial:
* v1 — Schema inicial
* v2 — Afegeix columna Departament a Usuarios
* v3 — Nou full Avisos; nova clau avisos_activats a Config
*/
const CURRENT_SCHEMA_VERSION = 3;
const MIGRATIONS = [
{ version: 2,
description: 'Afegeix columna Departament a Usuarios',
fn: _migrate_v2 },
{ version: 3,
description: 'Nou full Avisos i clau avisos_activats a Config',
fn: _migrate_v3 },
];
function _migrate_v2() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
_ensureColumn(ss.getSheetByName('Usuarios'), 'Departament');
}
function _migrate_v3() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
_ensureSheet(ss, 'Avisos',
['ID_Avis', 'Titol', 'Missatge', 'Data_Inici', 'Data_Fi', 'Actiu'],
'#0891b2'
);
_ensureConfigKey(ss, 'avisos_activats', 'FALSE',
'Mostrar avisos a la pantalla principal'
);
}
Resultat per a cada tipus d'instal·lació:
| Instal·lació | SCHEMA_VERSION inicial |
Migracions executades |
|---|---|---|
| Nova (crea des de zero) | 3 (Setup.gs) |
Cap |
| Existent a v2 | 2 |
_migrate_v3 |
| Existent a v1 | 1 |
_migrate_v2, _migrate_v3 |
| Pre-migracions (null) | Detectat com 1 → 3 |
_migrate_v2, _migrate_v3 |
Si la migració falla¶
Diagnòstic ràpid¶
- A l'editor Apps Script: Execucions → troba l'execució fallida → expandeix el log
- Busca
[Migration] ERROR a vN:— el missatge d'error és el punt de partida
La migració s'ha aplicat parcialment¶
Si _migrate_vN() ha fallat a meitat, SCHEMA_VERSION no s'ha actualitzat (el runner actualitza la versió després d'una execució reeixida). La propera càrrega de la webapp reintentarà la migració des del principi — per això les funcions han de ser idempotents.
Forçar una re-execució manual¶
Des de l'editor Apps Script: Executar → checkAndMigrate directament. Els logs apareixeran a la consola.
Resetar la versió manualment (últim recurs)¶
Si cal forçar la re-execució d'una migració específica:
// Executar des de l'editor Apps Script (no des del codi)
function _resetSchemaVersion() {
PropertiesService.getScriptProperties().setProperty('SCHEMA_VERSION', '1');
// Canvia '1' per la versió des de la qual vols tornar a migrar
}
Ús exclusivament de diagnòstic
No incloguis _resetSchemaVersion al flux normal. Executar-la en una instal·lació de producció pot re-aplicar migracions ja fetes.
Checklist per a cada migració¶
Afegeix això a la revisió del Merge Request:
- [ ]
CURRENT_SCHEMA_VERSIONincrementat en 1 - [ ] Funció
_migrate_vN()escrita i registrada aMIGRATIONS - [ ] Comentari d'historial actualitzat
- [ ] Funció és additiva (no elimina ni modifica dades existents)
- [ ] Funció és idempotent (segura de re-executar)
- [ ] Provada en una còpia de test del Sheets
- [ ] Logs verificats a l'editor Apps Script
- [ ] Notes de versió actualitzades (si el canvi afecta usuaris no tècnics)
Veure també: Instal·lació amb clasp · Restriccions de GAS · Model de dades