Saltar al contenido principal

SQLite — Formato de Almacenamiento y Versión

Documentación técnica del almacenamiento SQLite para la migración de datos offline.

Plugin y Versión

AspectoValor
Plugincordova-sqlite-storage
Versión (package.json)^6.0.0
Nombre del archivo DBaura.db
Locationdefault
Android Database Providersystem (usa android.database.sqlite nativo del OS)
iOSSQLite nativo del sistema (incluido en iOS, no embebido)
Web (dev)WebSQL via window.openDatabase('MyDatabase', '1.0', 'Data', 2MB)

Versión de SQLite según plataforma

Dado que usa androidDatabaseProvider: 'system', la versión de SQLite depende del OS del dispositivo:

PlataformaVersión SQLite
Android 10 (API 29)3.28.0
Android 11 (API 30)3.32.2
Android 12 (API 31)3.32.2
Android 13 (API 33)3.32.2
Android 14 (API 34)3.39.2
iOS 13+3.28.0+ (varía por versión de iOS)
iOS 163.39.5
iOS 173.39.5+

Nota: No se embebe una versión fija de SQLite. Se usa la del sistema operativo.

Ubicación del archivo en disco

Android

/data/data/com.auravant/databases/aura.db

(Obtenido via context.getDatabasePath("aura.db"))

iOS

Library/LocalDatabase/aura.db

(Location default en cordova-sqlite-storage para iOS)

Schema de la Base de Datos

Tablas

-- Datos iniciales (tokens, último usuario, versión de schema)
CREATE TABLE IF NOT EXISTS Initial (id TEXT PRIMARY KEY, data TEXT);

-- Datos estáticos compartidos entre usuarios
CREATE TABLE IF NOT EXISTS Static (id TEXT PRIMARY KEY, data TEXT);

-- Mapas del usuario
CREATE TABLE IF NOT EXISTS MyMaps (id TEXT PRIMARY KEY, data TEXT);

-- Datos de campos (NDVI, metadata)
CREATE TABLE IF NOT EXISTS Field (field_id TEXT PRIMARY KEY, farm_id TEXT, ndvi INTEGER, currency TEXT);

-- Tabla por usuario (se crea dinámicamente)
CREATE TABLE IF NOT EXISTS userId (id TEXT PRIMARY KEY, data TEXT);

Modelo key-value con particionamiento

Todas las tablas (excepto Field) usan un modelo key-value simple: (id TEXT, data TEXT).

Los datos grandes se particionan en chunks de 100.000 caracteres:

ROW_LIMIT = 100000 caracteres por fila

Si un JSON serializado tiene 250.000 caracteres, se guarda en 3 filas:

  • offline1 → primeros 100.000 chars
  • offline2 → siguientes 100.000 chars
  • offline3 → últimos 50.000 chars

El número de partes se trackea en una fila especial userkeyparts:

// Fila con id = "userkeyparts" en tabla userId
{"offline": 3, "scouting": 1, "farms": 2, ...}

Datos Offline — Formato exacto en SQLite

Cómo se guarda

  1. OfflineService.saveData() llama a sWrapperService.save('offline', offlineStore.data)
  2. SWrapperService.save() hace JSON.stringify(value) y llama a sqliteService.saveParts()
  3. saveParts() divide el string en chunks de 100K y los guarda como filas offline1, offline2, etc.

Cómo se lee

  1. Se lee userkeyparts para saber cuántas partes tiene la key "offline"
  2. Se leen las filas offline1, offline2, ..., offlineN
  3. Se concatenan los strings
  4. Se hace JSON.parse() del resultado

Estructura del JSON deserializado

{
"requestQueue": {
"scouting": [
{
"id": 42,
"api": "/scouting/scout",
"body": "{\"uuid\":\"abc-123\",\"created\":\"2024-01-15T10:30:00-03:00\"}",
"method": "POST",
"opts": {
"params": {},
"headers": {
"X-Version": "3.2.0",
"Content-Type": "application/json"
}
},
"eventData": {"msg": 400},
"attempts": 0,
"date": 1705312200000,
"userId": "user-uuid-123"
}
],
"field": [...],
"mzone": [...]
},
"removedQueue": {
"scouting": [
{
"id": 10,
"api": "/scouting/scout",
"body": "...",
"method": "POST",
"opts": {...},
"eventData": {...},
"attempts": 9,
"date": 1705000000000,
"userId": "user-uuid-123",
"deleted": 1705400000000,
"latestError": {
"error": {"code": -6, "info": "duplicated key"},
"timestamp": 1705399000000
}
}
]
},
"transactionId": 43
}

Queries SQL para leer los datos offline directamente

Para la migración, estas son las queries necesarias para extraer la cola offline de un usuario:

-- 1. Obtener el último usuario activo
SELECT data FROM Initial WHERE id = 'lastUser';
-- Resultado: "\"user-uuid-123\"" (JSON string)

-- 2. Obtener tokens
SELECT data FROM Initial WHERE id = 'tokens';
-- Resultado: "{\"user-uuid-123\": \"eyJ...\"}" (JSON object)

-- 3. Obtener el mapa de partes del usuario
SELECT data FROM "userId" WHERE id = 'userkeyparts';
-- Resultado: "{\"offline\": 2, \"farms\": 1, ...}"

-- 4. Leer las partes de offline (si userkeyparts.offline = 2)
SELECT data FROM "userId" WHERE id = 'offline1';
SELECT data FROM "userId" WHERE id = 'offline2';
-- Concatenar resultados y hacer JSON.parse()

Tabla Initial — Contenido

id (key)data (value)Descripción
lastUser"\"user-uuid\""UID del último usuario logueado
tokens"{\"uid1\": \"token1\", ...}"Mapa de tokens por usuario
dbscheme"1"Versión del schema de la DB

Tabla Static — Contenido

id (key)data (value)Descripción
mapkeyparts"{\"mapKey1\": 1, ...}"Mapa de partes para MyMaps
statickeyparts"{\"key1\": 1, ...}"Mapa de partes para datos estáticos
{key}{N}chunk de JSONDatos estáticos particionados

Tabla userId — Contenido relevante para sync

id (key)data (value)Descripción
userkeyparts"{\"offline\": N, ...}"Mapa de partes por key
offline1chunk 1 del JSONPrimera parte de OfflineData
offline2chunk 2 del JSONSegunda parte (si existe)
offlineNchunk N del JSONN-ésima parte

Notas importantes para la migración

  1. El JSON puede estar partido a mitad de un string/objeto — Los chunks se cortan por cantidad de caracteres (100K), no por estructura JSON. Se deben concatenar TODOS los chunks antes de parsear.

  2. El campo body es un string JSON escapado — Dentro del JSON principal, request.body es generalmente un JSON.stringify(...) del payload original. Es decir, hay doble serialización.

  3. No hay schema SQL para las requests — Todo es un blob JSON en una celda TEXT. No hay índices ni columnas por campo de Request.

  4. La tabla del usuario se llama U + userId — El userId puede contener caracteres que requieran quoting en SQL (ej: guiones).

  5. androidDatabaseProvider: 'system' — Significa que en Android NO se usa el SQLite embebido del plugin (SQLCipher/sqlcipher-evcore), sino el android.database.sqlite del sistema. No hay encriptación.

  6. No hay encriptación — La DB no usa SQLCipher ni ningún mecanismo de cifrado.

  7. El campo opts.headers NO contiene Authorization — El token se inyecta al momento de enviar, no se persiste en la cola.