jueves, 25 de junio de 2026

Sincronización y persistencia de odontograma

 Quiero que me preguntes qué requerimientos estoy necesitando cubrir para recomendarme una solución de sincronización frontend-backend, con lectura y grabado en base de datos.

Perfecto. Antes de recomendar una arquitectura (REST, Inertia, Server Actions, WebSockets, polling, CQRS, cache local, sincronización optimista, etc.), necesito entender algunos aspectos de tu caso de uso.

Respondé lo que puedas:

1. Frecuencia de actualización

  • ¿Los datos cambian sólo cuando el usuario hace acciones?
  • ¿O pueden cambiar por otros usuarios/procesos mientras la pantalla está abierta?

2. Tiempo real

  • ¿Necesitás que los cambios aparezcan casi instantáneamente?
  • ¿Cuántos segundos de demora serían aceptables?

3. Volumen de datos

  • ¿Cuántos registros suele manejar una pantalla?
    • decenas
    • cientos
    • miles
    • decenas de miles

4. Patrón de uso

  • ¿Hay muchas más lecturas que escrituras?
  • ¿O las escrituras son frecuentes?

5. Consistencia

  • ¿Es crítico que el usuario vea siempre el estado más reciente de la base de datos?
  • ¿O tolerás que vea datos levemente desactualizados durante algunos segundos?

6. Conflictos

  • ¿Puede ocurrir que dos usuarios modifiquen simultáneamente la misma entidad?
  • Si ocurre, ¿qué debería pasar?

7. UX

  • ¿Querés que las modificaciones se reflejen instantáneamente en la UI (optimistic updates)?
  • ¿O preferís esperar la confirmación del backend?

8. Navegación

  • ¿Tu aplicación es principalmente:
    • formularios
    • CRUD administrativo
    • dashboard
    • editor complejo
    • tiempo real colaborativo
    • otra cosa?

9. Stack actual

  • ¿Qué usás exactamente?
    • React
    • Inertia
    • Laravel
    • Vite
    • TypeScript
    • otra cosa

10. Estado frontend

  • ¿Actualmente usás algo como:
    • Context
    • Zustand
    • Redux
    • React Query / TanStack Query
    • SWR
    • nada

11. Offline

  • ¿La aplicación debe seguir funcionando sin conexión y sincronizar después?

12. Prioridades

Ordená estas prioridades:

  • simplicidad
  • rendimiento
  • consistencia
  • tiempo real
  • facilidad de mantenimiento
  • escalabilidad

13. Caso concreto

Describime una pantalla representativa:

"Tengo un odontograma. El usuario modifica piezas dentales, se guardan en Laravel y otros usuarios pueden verlo."

o el caso que corresponda.

Con esas respuestas puedo recomendarte una estrategia concreta de sincronización frontend ↔ backend ↔ base de datos en lugar de una lista genérica de tecnologías.



1. Los datos cambian sólo cuando el usuario hace acciones. 
2. Puedo tolerar 5 segundos de demora, siempre y cuando haya un indicador en pantalla mientras todavía no están los nuevoa datos listos para mostrar. 
3. El volumen de datos de una pantalla es un JSON como el siguiente: 
export default
{
version: 1,
/* protesis: dentadura, enlaces, soluciones
* extraccion: dentadura, soluciones
* implante: dentadura, soluciones
* ortodoncia: enlaces, soluciones
*/
dentadura: [
/* tipo: protesis | muela | muela-leche | diente | diente-leche | edentulismo | extraccion |
* | implante |
*/
{ tipo: 'protesis', numero: '17', casillero: 4 },
{ tipo: 'protesis', numero: '16', casillero: 6 },
{ tipo: 'protesis', numero: '15', casillero: 8 },
{ tipo: 'muela', numero: '14(10)', casillero: 10 },
{ tipo: 'muela', casillero: 11},
//...
],
marcas_dentales: [
/* codigo: caries | obturacion-pendiente | obturacion-realizada
*
*/
{ codigo: 'obturacion-pendiente', casillero: 10, superficie: 1, presupuestado: false },
{ codigo: 'obturacion-realizada', casillero: 12, superficie: 2, presupuestado: true },
//...
],
problemas : [
/* tipo: desgaste | desplazamiento | discromia | ectopia | erupcion | extrusion
* | fractura | impacto | intrusion | macrodoncia | microdoncia | necrosis
* | remanente-radicular | rotacion | semi-impacto |
*/
{ tipo: 'remanente-radicular', casillero: 10 },
{ tipo: 'extrusion', casillero: 10 },
{ tipo: 'macrodoncia', casillero: 10 },
//...
],
// Array ordenado por primer_casillero
// de enlaces entre casilleros
enlaces : [
/* tipo: diastema | fusion | ortodoncia | protesis | transposicion
*
*/
{ tipo: 'protesis', primer_casillero: 4, ultimo_casillero: 8 },
{ tipo: 'protesis', primer_casillero: 12, ultimo_casillero: 16 },
// ...
],
soluciones: [
/* tipo: extraccion | implante | ortodoncia | perno-corona | protesis | tratamiento-conducto
*
*/
{ tipo: 'protesis', casillero: 4, presupuestado: true, realizado: true },
{ tipo: 'extraccion', casillero: 10, presupuestado: true, realizado: false },
// ...
],
}
4. Hay una lectura inicial y una baja frecuencia de escrituras. 
5. Es crítico que el usuario vea siempre el estado más reciente de la base de datos. 
6. Si 2 usuarios intentan modificar la misma entidad no hay problema en bloquear al segundo en ingresar. 
7. Prefiero esperar a la confirmación del backend. 
8. Editor complejo. Es un odontograma interactivo. Adjunto imagen. 
9. React, Inertia, Laravel, Vite, Typescript. 
10. Nada. 
11. No hace falta que siga funcionando offline. 
12. 1) simplicidad; 2) facilidad de mantenimiento; 3); consistencia; 4) rendimiento; 5) escalabilidad. 
13. Tengo un odontograma. El usuario modifica piezas dentales, patologías, tratamientos. Lo hace mediante un diálogo interactivo en pantalla. En el frontend todo esto se representa en un JSON como el que te compartí en mi respuesta #3.

Con lo que describís, yo no usaría nada sofisticado (WebSockets, Zustand, Redux, CQRS, sincronización offline, etc.). Estarías pagando complejidad sin obtener beneficios.

Tu caso tiene varias características muy favorables:

  • El odontograma completo es un único agregado lógico.
  • El JSON es pequeño (decenas o cientos de KB como mucho).
  • Las escrituras son poco frecuentes.
  • No necesitás tiempo real.
  • Priorizás simplicidad y mantenimiento.
  • Preferís esperar la confirmación del backend.
  • Querés consistencia fuerte.

Lo que implementaría

Backend

Una tabla:

odontogramas
-----------
id
version
contenido_json
updated_at

donde contenido_json es esencialmente el JSON que mostraste.


Frontend

Estado local:

const [odontograma, setOdontograma] =
useState<Odontograma>(props.odontograma)

const [guardando, setGuardando] =
useState(false)

Guardado

Cuando el usuario confirma una modificación:

setGuardando(true)

await router.post(
route('odontograma.guardar'),
odontograma
)

await recargar()

setGuardando(false)

donde:

async function recargar() {
router.reload({
only: ['odontograma']
})
}

o equivalente con fetch.


Lo importante: versionado optimista

Veo un posible problema:

"si dos usuarios intentan modificar la misma entidad no hay problema en bloquear al segundo"

Entonces agregaría:

{
version: 37,
...
}

al JSON.


Al guardar:

if ($request->version !== $odontograma->version) {
abort(409);
}

Si alguien guardó antes:

Usuario A abre v37
Usuario B abre v37

Usuario A guarda
→ v38

Usuario B guarda
→ 409 Conflict

y mostrás:

El odontograma fue modificado por otro usuario.
Recargue la pantalla.

Mejor todavía: lock pesimista

Como dijiste:

"no hay problema en bloquear al segundo"

haría esto.

Al abrir:

odontograma_locks
-----------------
odontograma_id
usuario_id
expires_at

El primer usuario adquiere el lock.

El segundo ve:

El odontograma está siendo editado por Juan Pérez.

y entra en modo lectura.


Qué NO usaría

No usaría:

  • Redux
  • Zustand
  • MobX
  • WebSockets
  • SSE
  • React Query
  • SWR
  • Event Sourcing

porque ninguna de esas herramientas resuelve un problema que realmente tengas.


Lo que sí me preocuparía

Al mirar el JSON veo que el odontograma es básicamente un documento.

Yo evitaría hacer:

POST agregar-caries
POST borrar-caries
POST agregar-fusion
POST agregar-diastema
POST agregar-protesis

etc.

Terminarías con decenas de endpoints.

Preferiría:

PUT /odontograma/:id

enviando el documento completo.

await axios.put(
`/odontograma/${id}`,
odontograma
)

El backend valida y persiste.

La complejidad queda concentrada en un único lugar.


Para tu stack (Laravel + Inertia + React + TS) mi recomendación concreta sería:

  1. Mantener el odontograma completo como un único documento JSON.
  2. Estado local React simple (useState o useRef según corresponda).
  3. Guardado explícito al confirmar cambios.
  4. Recarga desde backend después del guardado exitoso.
  5. Lock pesimista de edición por usuario.
  6. Campo version para detectar inconsistencias si el lock falla.

Eso te da una solución muy consistente y extremadamente simple de mantener.

No hay comentarios: