Android · Offline-first

Tus notas no
deberían esperar
por la red.

Un caso de estudio de arquitectura offline-first: cómo una app escribe, cifra y consulta localmente, y luego hace converger varios dispositivos sin bloquear al usuario.

API 24+Android 7 o superior
100% offlineOperación local continua
Open sourceArquitectura documentada
Sincronizado
Modo offline
9:41● ◒
BUEN DÍAMis notas
JC
IDEAS•••

Próxima iteración

Probar el flujo entre dos dispositivos...

Ayer↻
▤Notas↻Sync◷Historial

El problema

La conectividad es una variable, no un requisito.

Muchas aplicaciones móviles dejan de ser útiles cuando la red falla. Notium invierte esa dependencia: cada acción se confirma localmente en milisegundos y la sincronización ocurre cuando es posible.

Diseñada para trabajo de campo, movilidad y conexiones inestables.

Capacidades

Hecha para seguir trabajando.

01

Funciona sin señal

Crear, editar, buscar y adjuntar archivos sigue disponible incluso en modo avión.

02

Datos cifrados

SQLCipher protege la base local y Android Keystore resguarda las credenciales.

03

Sincronización confiable

Push, pull, reintentos e idempotencia mantienen cada dispositivo al día.

04

Conflictos con historia

Last-Write-Wins resuelve cambios simultáneos sin borrar la trazabilidad del valor descartado.

Arquitectura

Local primero.
Red después.

La base cifrada es la única fuente de verdad para la interfaz. Un coordinador independiente mueve cambios entre el dispositivo y el servidor sin bloquear al usuario.

01

Respuesta inmediataLa UI nunca espera una petición HTTP para guardar.

02

ConvergenciaWorkManager sincroniza al recuperar conectividad.

03

Conflictos visiblesLas decisiones LWW quedan en el historial.

Dentro del cliente

La red nunca alimenta directamente la interfaz.

Cada capa tiene una sola responsabilidad. Así es posible probar la lógica sin emulador y sustituir infraestructura sin reescribir la UI.

  1. 01
    Widgets

    Renderizan el estado y convierten gestos en intenciones.

  2. 02
    Notifier · Riverpod

    Coordina el estado de presentación e inyecta dependencias.

  3. 03
    Repository

    Define las operaciones del dominio y oculta de dónde vienen los datos.

  4. 04
    Drift · SQLite

    Es la fuente única de verdad; sus streams actualizan la pantalla.

  5. 05
    SyncTask

    Lee la cola pendiente y usa Dio solo cuando existe conectividad.

Ciclo de sincronización

Una ruta predecible para cada cambio.

Los estados son explícitos. Si algo falla, la nota permanece local y el usuario conserva el control.

01 · <100 ms

Escribe local

Drift guarda el contenido, genera un UUID y marca la operación como PENDING.

02 · POST /sync/push

Envía un lote

WorkManager agrupa pendientes. La clave (uuid, version) permite reintentar sin duplicar.

03 · LWW

Resuelve

El servidor compara versiones y updated_at; responde por cada operación.

04 · GET /sync/pull

Converge

El cliente descarga cambios posteriores al último timestamp y actualiza su base local.

Máquina de estados

Cada registro explica qué está ocurriendo.

La sincronización no es un indicador global ambiguo: cada nota conserva su propio estado y puede recuperarse de manera independiente.

PENDING

Pendiente

El cambio ya está guardado localmente y espera conectividad.

SYNCED

Sincronizado

El servidor confirmó la operación y devolvió su versión autoritativa.

CONFLICT

Conflicto

Dos dispositivos editaron el mismo registro y el servidor debe resolver.

ERROR

Requiere atención

La validación falló; el dato local permanece disponible para corregirlo.

CONFLICTOS

Last-Write-Wins, pero con memoria.

Gana la escritura con el updated_at más reciente. La versión descartada no desaparece silenciosamente: se registra en HISTORIAL_CAMBIO para auditoría.

ELIMINACIONES

Un borrado también debe viajar.

Notium crea un tombstone con is_deleted = true. Solo se purga cuando el servidor confirma su propagación, evitando que otro dispositivo “resucite” la nota.

Tecnología

Cada tecnología resuelve un riesgo concreto.

El stack no se eligió por popularidad. Cada pieza responde a una restricción: trabajar sin red, proteger datos locales, reintentar sin duplicar y ser mantenible por un solo desarrollador.

Leer decisiones de arquitectura ↗
01
Cliente multiplataforma

Flutter + Dart

Un solo código base entrega Android hoy y deja abierta la evolución a iOS, web y escritorio.

Evita mantener implementaciones nativas separadas con un equipo de una persona.
02
Estado y dependencias

Riverpod

Conecta streams de la base local con la interfaz mediante estado reactivo, tipado y fácil de probar.

Menos boilerplate que BLoC y mejor separación de responsabilidades que GetX.
03
Persistencia local

Drift + SQLCipher

SQLite relacional, consultas validadas, migraciones y streams; SQLCipher cifra toda la base en reposo.

Se eligió sobre almacenes NoSQL porque notas, adjuntos e historial tienen relaciones reales.
04
Trabajo en segundo plano

WorkManager

Programa sincronizaciones con restricción de red y sobrevive al cierre del proceso Android.

Un timer o servicio en foreground consumiría más batería y dependería de que la app siga abierta.
05
Transporte

Dio + REST

Interceptores JWT, reintentos, cancelación, cargas multipart y control explícito del protocolo push/pull.

Mantiene el cliente HTTP pequeño sin añadir generación de código innecesaria.
06
Autoridad de sincronización

Node.js + Express

Una API liviana concentra autenticación, idempotencia y resolución de conflictos.

Menor costo operativo que un framework pesado para el volumen y alcance actuales.
07
Persistencia remota

PostgreSQL + Docker

Replica el modelo relacional del cliente y consolida versiones, historial y operaciones procesadas.

Docker hace reproducible el despliegue de API y base de datos en el VPS.
83pruebas Flutter
44pruebas backend
4migraciones locales
API pública comprobar ↗

Seguridad y operación

Protección en cada frontera.

Offline-first coloca más datos en el dispositivo. Por eso la seguridad no puede depender únicamente del servidor.

EN REPOSO

SQLCipher

La base SQLite completa permanece cifrada. La pérdida física del teléfono no expone directamente las notas.

IDENTIDAD

Keystore + JWT

Access y refresh tokens viven en almacenamiento seguro; la expiración puede validarse localmente durante una desconexión.

EN TRÁNSITO

HTTPS / TLS

Push, pull, autenticación y adjuntos viajan cifrados. La API valida que cada entidad pertenezca al usuario autenticado.

OPERACIÓN

Docker + healthcheck

Frontend, API y PostgreSQL se despliegan de forma reproducible; el estado de la API puede comprobarse públicamente.

La idea central

La base local da disponibilidad. El historial da trazabilidad. La idempotencia permite reintentar. El servidor hace converger dispositivos.

Código abierto

Construido para funcionar cuando internet no lo hace.

Explora el código, las pruebas, el contrato OpenAPI y las decisiones detrás de Notium.