Unread vs Total Messages: Cómo funcionan las actualizaciones de estado
Si alguna vez viste que el número de Unread (no leídos) no “cuadra” con el Total (total de mensajes), o que la app te marca notificaciones cuando “según tú” ya leíste todo, no estás solo. En México diríamos: “¿por qué se puso raro el contador?”. La respuesta casi siempre está en cómo se calculan los estados y cómo se sincroniza la información entre el servidor, la base de datos local, y lo que la interfaz decide mostrar.
En este artículo lo vamos a desmenuzar con calma: qué significa cada conteo, cómo se actualiza, por qué hay diferencias temporales, y qué buenas prácticas ayudan a que los números sean coherentes sin matar rendimiento.
Qué significa “Total” y qué significa “Unread” en la práctica
Aunque suena obvio, en sistemas reales “Total” y “Unread” no siempre representan lo mismo para todos los módulos de la app. Total suele ser la cantidad de mensajes que existen en un contexto (por ejemplo: un inbox, una carpeta, una etiqueta, un alias o una dirección temporal). En cambio, Unread es un subconjunto: los mensajes cuyo estado de lectura está en “no leído”.
Lo importante: el “Total” normalmente cambia cuando llegan correos nuevos o cuando se eliminan/archivan mensajes. El “Unread” cambia también cuando el usuario abre un correo, cuando la app marca como leído automáticamente, o cuando se aplica una regla (por ejemplo, al mostrar un preview).
Por eso, aunque “Unread” depende del “Total”, sus fuentes de actualización pueden ser distintas. Y ahí empiezan las diferencias.
Por qué a veces no cuadran (y no necesariamente es un bug)
En apps modernas, los conteos no se recalculan desde cero todo el tiempo, porque sería caro. En vez de eso, se usan estrategias como caché, conteos incrementales y sincronización por eventos. Eso hace que, por algunos segundos o minutos, puedas ver números “desfasados” sin que sea un error real.
1) Actualizaciones asincrónicas
Un patrón muy común es que el servidor confirme primero que llegó un nuevo mensaje y actualice el Total, pero el Unread se actualice después cuando se termine de procesar el estado o cuando el cliente confirme recepción. En ese lapso, el total sube, pero el unread puede tardar en reflejarse (o viceversa).
2) Diferencias entre servidor y base local
Muchas apps guardan un “snapshot” local para que la UI sea rápida. Entonces la pantalla muestra conteos locales mientras la sincronización con el backend sigue en proceso. Si tu conexión está lenta, si hay reintentos, o si el sistema está haciendo backoff, el usuario lo percibe como números inconsistentes.
3) Filtros y vistas distintas
“Total” puede estar contando mensajes de una carpeta completa, mientras que “Unread” se calcula sobre una vista filtrada: por ejemplo, solo “Primary”, o solo “inbox”, o solo mensajes que aún no expiraron. Un caso típico en correos temporales: el total incluye mensajes recibidos, pero unread ignora mensajes ya expirados o mensajes “silenciados”.
4) Estados intermedios
Hay sistemas donde “leído” no es binario. Puede haber estados como: recibido, entregado, visto, abierto, marcado como leído por el usuario, o marcado como leído automáticamente. Si el backend distingue esos estados, el cliente puede mostrar “Unread” según su propia regla.
El corazón del tema: ¿cómo se actualiza el estado?
Hay dos grandes enfoques: pull (la app pregunta) y push (el servidor avisa). En la vida real, casi siempre es una mezcla.
Pull: sincronización por consulta
La app hace requests periódicos para traer mensajes y conteos. Esto es fácil de implementar, pero tiene dos consecuencias: el estado se actualiza con “latencia” y, si preguntas demasiado seguido, puedes gastar batería y datos.
Push: sincronización por eventos
El servidor manda un evento: “llegó un nuevo mensaje” o “cambió el estado de lectura”. Esto hace que la UI reaccione más rápido, pero requiere manejar duplicados, orden de eventos, y casos donde un evento llega tarde o llega dos veces.
Mix: lo más común
Un flujo típico es: el push avisa “hay novedades”, y el cliente hace pull para traer el detalle real. En ese intervalo, la app puede actualizar Total con base en el evento, pero el Unread se confirma hasta que llega el payload completo o hasta que el cliente reconcilia su base local.
Ejemplo realista: llega un correo nuevo mientras estás viendo la lista
- El servidor recibe el mensaje y lo registra en la bandeja.
- Se actualiza el conteo de Total en backend.
- Se manda un evento o el cliente consulta y detecta “hay un mensaje más”.
- La UI sube el total casi de inmediato para que el usuario lo note.
- El contenido completo del mensaje se descarga (asunto, remitente, preview, timestamp).
- Se decide el estado “unread” según reglas: si entra como nuevo, si fue auto-abierto por preview, etc.
- Se actualiza el conteo Unread al finalizar la reconciliación.
Si el paso 5 o 6 tarda (por red, por colas, por throttle), el usuario ve “Total subió” y “Unread aún no”. Y eso se siente como bug aunque sea simplemente un proceso por etapas.
Marcar como leído: por qué no siempre baja el Unread en el instante
“Marcar como leído” se ve simple, pero puede implicar varias operaciones: actualizar en base local, mandar al servidor el cambio, esperar confirmación, y luego recalcular conteos. Para que la app se sienta ágil, muchas veces se hace una actualización optimista: la UI baja el unread en el momento y luego “corrige” si el servidor rechaza el cambio.
El problema: si hay múltiples dispositivos o pestañas, el servidor puede recibir actualizaciones en diferente orden. Por ejemplo, abres el correo en la compu, pero el teléfono aún no sincroniza. Durante un rato, el teléfono sigue marcando unread, y el total se mantiene igual. Luego sincroniza y, de golpe, el unread baja.
El rol de “expiración” y limpieza automática (muy común en correo temporal)
En servicios de correo temporal, los mensajes a menudo tienen políticas de retención: expiran a los X minutos u horas, o se limpian por lotes. Eso impacta directamente en el conteo total. El unread, en cambio, puede tratar los mensajes expirados como “ya no existen” aunque la UI aún los tenga cacheados.
Resultado típico: ves un total mayor en una pantalla y, al actualizar, baja porque se ejecutó una limpieza. O al revés: la UI conserva un listado, pero el backend ya lo eliminó, y al reconciliar “desaparecen” mensajes y cambian conteos.
Por qué los conteos pueden variar entre “badge”, lista y detalle
A veces el “badge” (el numerito rojo o el indicador en la navegación) usa un conteo distinto al de la lista. No por mala onda, sino por rendimiento. Por ejemplo:
- El badge usa un conteo global de unread en background.
- La lista muestra unread solo de la carpeta actual.
- El detalle del mensaje cambia el estado al abrir, pero el badge se recalcula después.
Por eso pasa que abres un mensaje, lo lees, regresas y el badge todavía muestra 1. Luego haces pull-to-refresh o cambias de pestaña y ya se corrige.
Buenas prácticas para que “Unread” y “Total” sean consistentes
Si estás diseñando o afinando el comportamiento de una app, estas prácticas suelen dar resultados sólidos sin complicarte la vida:
1) Definir claramente el alcance de cada contador
- Total: ¿cuenta solo inbox o incluye archive/spam/trash?
- Unread: ¿se basa en “seen” del servidor o en “opened” del cliente?
- ¿Los mensajes expirados cuentan o no cuentan?
Cuando el alcance está bien definido, las “diferencias” dejan de ser misteriosas: son decisiones.
2) Reconciliación por fuente de verdad
El servidor suele ser la fuente de verdad para conteos globales, pero el cliente necesita agilidad. Una estrategia común es: UI rápida con conteos locales y, cada cierto tiempo o evento importante, reconciliar contra el servidor y ajustar sin que el usuario lo sienta como “salto brusco”.
3) Actualización optimista con fallback
Cuando el usuario marca como leído, la UI puede bajar unread inmediatamente. Si el servidor falla, se revierte con un mensaje suave. Esto mantiene percepción de velocidad sin perder consistencia real.
4) Manejo de duplicados y orden de eventos
En push, pueden llegar eventos duplicados o fuera de orden. El cliente debe identificar mensajes por ID único y aplicar “último estado gana” con timestamps o versiones. Si no, verás conteos que suben y bajan sin sentido.
5) Evitar recalcular todo a cada cambio
Contar desde cero cada vez (recorrer toda la lista para contar unread) puede ser caro. En apps con muchos mensajes, se usan contadores incrementales: al insertar un mensaje, total++; si entra como no leído, unread++. Al marcar leído, unread--. Al borrar, total-- y, si era no leído, unread--. Suena simple, pero hay que ser cuidadoso con sincronización y estados intermedios.
Señales típicas de que sí hay un problema
No todo desfase es normal. Estas señales sí huelen a bug o a inconsistencia persistente:
- Unread se queda “atorado” en un número aunque ya no existan mensajes no leídos.
- Total aumenta pero el mensaje nunca aparece, incluso tras refrescar.
- Los conteos cambian al navegar entre pantallas sin que haya nuevos mensajes.
- Badge y lista muestran números diferentes por horas, no por segundos.
- En un dispositivo marca 0 y en otro marca 5 por mucho tiempo con la misma cuenta.
Cuando pasa esto, casi siempre hay un tema con caché no invalidado, sincronización fallida, manejo incorrecto de “read/unread”, o filtros aplicados de forma distinta en cada vista.
FAQ: dudas rápidas que siempre salen
¿Por qué sube el Total pero no sube el Unread?
Puede ser que el mensaje entró como “ya leído” por alguna regla, que el estado de lectura se confirma después, o que la UI actualizó total con un evento y unread solo se recalcula al sincronizar detalles.
¿Por qué baja el Unread cuando solo abrí la lista y ni abrí el correo?
Algunas apps marcan como leído al “previsualizar” o al cargar el contenido del mensaje en segundo plano. Otras marcan como leído cuando el item entra al viewport por cierto tiempo. Depende de la implementación.
¿Por qué el badge y la lista no muestran lo mismo?
Porque a veces calculan con alcances distintos (global vs carpeta) o con frecuencias distintas (background vs vista activa). Lo ideal es que converjan rápido, pero es normal que haya una pequeña diferencia temporal.