Cómo construí una app que se sincroniza con Supabase sin sobreescribir datos - 02/03/2026
Una de las partes más desafiantes cuando construyes una app colaborativa es lograr que los datos se mantengan sincronizados sin que una actualización sobreescriba los cambios de otro usuario. En este post explico cómo resolví ese problema usando Supabase.
App en producción: capturioo.com
Una de las partes más desafiantes cuando construyes una app colaborativa o en tiempo real es lograr que los datos se mantengan sincronizados entre múltiples usuarios o sesiones, sin que una actualización de un cliente sobreescriba los cambios de otro. En este post, explico cómo resolví ese problema usando Supabase como backend.
El problema: sincronización vs. sobreescritura
En una app tradicional con fetch o axios, el flujo típico es:
- El cliente obtiene los datos del servidor.
- El usuario modifica algo.
- El cliente envía el estado completo de vuelta al servidor.
El problema es que en el paso 3, si otro usuario modificó el mismo recurso mientras tanto, esos cambios se pierden. Esto se llama sobreescritura ciega (blind overwrite).
La solución: actualizaciones granulares + tiempo real
En vez de enviar el objeto completo, la clave está en enviar solo el campo que cambió. A esto lo llamo patch semántico.
Ejemplo:
// ❌ Forma incorrecta — sobreescribe todo
await supabase
.from('items')
.update({ ...fullObject })
.eq('id', item.id);
// ✅ Forma correcta — solo actualiza el campo modificado
await supabase
.from('items')
.update({ title: newTitle })
.eq('id', item.id);
Supabase Realtime: escuchar cambios sin polling
Con supabase.channel() me suscribo a los cambios de la tabla en tiempo real. Cuando otro usuario modifica un registro, mi cliente recibe el evento y solo actualiza ese campo en el estado local, sin reemplazar todo el objeto:
supabase
.channel('items-channel')
.on(
'postgres_changes',
{ event: 'UPDATE', schema: 'public', table: 'items' },
(payload) => {
// Solo fusiono el campo que cambió, no reemplazo el objeto completo
setState((prev) =>
prev.map((item) =>
item.id === payload.new.id
? { ...item, ...payload.new }
: item
)
);
}
)
.subscribe();
Row Level Security (RLS): proteger sin bloquear
Supabase usa PostgreSQL con RLS. Configuré las políticas para que cada usuario solo pueda modificar sus propios registros, pero pueda leer los de otros en contextos colaborativos. Esto garantiza que aunque la sincronización sea abierta, los datos privados estén protegidos.
Lecciones aprendidas
- Granularidad en los updates es la clave para evitar conflictos.
- Realtime + estado local fusionado elimina la necesidad de hacer polling.
- RLS bien configurado permite colaboración sin sacrificar seguridad.
- Supabase hace que implementar esto sea sorprendentemente accesible.
Puedes ver esta arquitectura en funcionamiento en capturioo.com.