wandres.dev
NOTIFICACIONES · locales y push

Buenas prácticas: frecuencia, control y el coste real de molestar

Toda notificación tiene un beneficio visible y un coste invisible, y el error sistemático de la industria consiste en medir solo el primero. Esta lección formaliza la economía de atención del canal, describe el centro de preferencias que evita el apagado total, y propone un aparato de medición —incluida la tasa de desinstalación inferida desde los tokens retirados— capaz de detectar el daño que las métricas de apertura ocultan.

⏱ 20 min

Cada notificación enviada produce dos efectos: uno visible y positivo que aparece de inmediato en el panel de métricas, y otro invisible y acumulativo que solo se manifiesta semanas después como silenciamiento, desactivación o desinstalación. Como el primero se mide con facilidad y el segundo casi nunca se mide, los sistemas de decisión de la mayoría de los productos están estructuralmente sesgados hacia enviar de más. Corregir ese sesgo no es una cuestión de buen gusto ni de ética abstracta: es un problema de instrumentación y de diseño de experimentos, y esta lección lo trata como tal, con las herramientas concretas que iOS ofrece para dar control a la persona y con el aparato de medida que hace visible el coste.

🎯 Al terminar esta lección sabrás
  • Formular una política de frecuencia y relevancia explícita, con topes por categoría y ventanas horarias respetuosas.
  • Diseñar un centro de preferencias granular enlazado desde los ajustes del sistema para evitar la desactivación total.
  • Construir el cuadro de métricas completo, incluida la tasa de desactivación y la desinstalación inferida.
  • Evaluar cambios en la política mediante grupos de control mantenidos durante semanas, no días.

La economía de la atención

Conviene formular el problema con precisión. Una notificación tiene un valor esperado que decae con la frecuencia y un coste que crece con ella; el punto óptimo no está donde la apertura marginal deja de ser positiva, sino bastante antes, porque el coste no se paga en el mismo periodo en que se cobra el beneficio. La disciplina que se deriva de esto tiene tres componentes: clasificar, limitar y temporizar.

Clasificar significa separar los avisos en categorías con contratos distintos. Lo transaccional —tu pedido ha salido, tu pago ha fallado, alguien te ha escrito— es información que la persona pidió implícitamente al usar el producto y tolera prioridad alta. Lo relacional —alguien ha comentado tu publicación— depende de un vínculo social y su valor decae si se multiplica. Lo promocional —vuelve, mira esto, te echamos de menos— no fue pedido por nadie y es el que destruye el canal. Mezclarlos en una única política garantiza que lo promocional se llevará por delante lo transaccional, porque la persona no puede desactivar una categoría: solo puede desactivarlo todo.

Limitar significa aplicar topes duros por categoría y por ventana temporal, evaluados en el servidor antes de encolar el envío. Un tope típico es un aviso promocional por semana, tres relacionales por día y ninguno transaccional limitado, porque lo transaccional se autolimita por definición. Ese cálculo debe vivir en una capa que ningún equipo pueda esquivar desde su propia campaña, o dejará de aplicarse el primer trimestre en que alguien necesite un número.

enum Clase: String, Codable, CaseIterable {
    case transaccional, relacional, promocional

    var topeSemanal: Int? {
        switch self {
        case .transaccional: nil          // sin tope, se autolimita
        case .relacional: 21
        case .promocional: 1
        }
    }

    var nivel: UNNotificationInterruptionLevel {
        switch self {
        case .transaccional: .timeSensitive
        case .relacional: .active
        case .promocional: .passive
        }
    }
}

Temporizar significa respetar la zona horaria real del dispositivo y no la del centro de datos. Enviar a las tres de la madrugada por un desfase de husos es el modo más eficaz de convertir un usuario activo en una desinstalación, y su corrección es trivial: almacenar la zona horaria junto al token y encolar contra la hora local. El nivel de interrupción es la otra palanca temporal y debe usarse con severidad: passive para todo lo promocional, active por defecto, timeSensitive solo cuando un retraso de una hora cause un perjuicio concreto y verificable.

⚠️
La reactivación agresiva es una operación de saldo

Las campañas de reenganche a usuarios inactivos suelen mostrar métricas espectaculares a siete días y resultados netos negativos a noventa. La razón es que reactivan a quien iba a volver de todos modos, y expulsan definitivamente a quien estaba en un silencio reversible. Cualquier evaluación de una campaña de este tipo que no incluya un grupo de control observado durante al menos un trimestre está midiendo el ingreso y ocultando el principal.

El centro de preferencias

La granularidad que ofrece iOS es escasa por diseño: la persona puede apagar el sonido, la pantalla bloqueada, los globos numéricos y poco más. Lo que no puede hacer desde el sistema es decir quiero los avisos de envío pero no las novedades. Si tu app no ofrece esa distinción, la única herramienta disponible para reducir el ruido es el interruptor general, y su accionamiento es prácticamente irreversible.

Un centro de preferencias correcto enumera las categorías en el lenguaje del usuario —no en el del sistema de mensajería interno—, permite activar y desactivar cada una por separado, ofrece control horario y hace visible la frecuencia esperada de cada tipo. Y debe estar enlazado desde los ajustes del sistema mediante la opción providesAppNotificationSettings solicitada en el momento de la autorización, de modo que quien llegue a la pantalla de iOS con intención de apagarlo todo encuentre allí mismo una alternativa más matizada.

🎚️

Por categoría

Cada tipo con su interruptor y una frase honesta sobre cuántos avisos implica al mes. La honestidad en la estimación reduce las desactivaciones posteriores.

🌙

Horario

Ventana de silencio configurable con valor por defecto sensato. Los envíos que caen fuera se difieren, no se descartan.

🧭

Enlazado

Accesible desde los ajustes de iOS gracias a providesAppNotificationSettings, para interceptar la intención de apagar antes de que se ejecute.

📉

Con salida digna

Una opción de recibir menos, y no solo de recibir nada. La pausa temporal recupera a mucha gente que el apagado definitivo perdería.

Hay una decisión de arquitectura que conviene tomar pronto: las preferencias deben aplicarse en el servidor, no filtrando en el cliente. Filtrar al llegar significa que el aviso ya se envió, ya consumió el presupuesto de entrega silenciosa y, en el caso de las notificaciones de alerta, ya se mostró. La preferencia debe viajar hasta el sistema que decide el envío y ser condición previa de encolado; el cliente solo la edita y la sincroniza.

Existe además un detalle de la API que hace posible cerrar el círculo con elegancia. Cuando la persona pulsa el enlace que iOS coloca en sus ajustes, el delegado de notificaciones recibe una invocación específica con la notificación asociada, o sin ella si venía de la pantalla general. Interceptarla y navegar directamente al centro de preferencias, en lugar de dejar a la persona en la pantalla de inicio de la app, es la diferencia entre recuperar a alguien que iba a desactivarlo todo y perderlo.

func userNotificationCenter(_ centro: UNUserNotificationCenter,
                            openSettingsFor notificacion: UNNotification?) {
    rutas.presentar(.preferenciasDeAvisos(
        destacando: notificacion?.request.content.categoryIdentifier
    ))
}

Medir lo que duele

El cuadro de métricas habitual mide entrega y apertura, ambas sesgadas hacia lo positivo. El cuadro completo necesita las señales de daño, que son cuatro y ninguna es difícil de obtener.

La tasa de descarte sin acción se obtiene registrando la categoría con la opción de descarte personalizado, que entrega a la app el evento cuando la persona desliza el aviso sin abrirlo. Es la medida más directa del ruido. La tasa de desactivación se obtiene comparando el estado de authorizationStatus entre sesiones consecutivas y contando las transiciones desde autorizado hacia denegado, atribuidas a la última campaña recibida. La tasa de silenciamiento parcial se deduce de los ajustes finos: alguien que tenía sonido y deja de tenerlo está a medio camino de la puerta.

Y la cuarta, la más valiosa y la que casi nadie calcula, es la desinstalación inferida. Cuando una app se elimina, su token deja de ser válido y APNs responde con estado 410 al siguiente envío. Contabilizar esas respuestas, atribuirlas a la última campaña que recibió ese token y normalizar por el tamaño del segmento produce un estimador de desinstalación por campaña que no requiere ningún SDK adicional. Es ruidoso en el corto plazo y muy informativo agregado por semanas.

-- Desinstalacion inferida por campana
SELECT c.campana,
       COUNT(DISTINCT e.token) AS enviados,
       COUNT(DISTINCT b.token) AS bajas,
       ROUND(100.0 * COUNT(DISTINCT b.token) / COUNT(DISTINCT e.token), 3) AS pct
FROM envios e
JOIN campanas c ON c.id = e.campana_id
LEFT JOIN bajas_token b
       ON b.token = e.token
      AND b.detectada_en BETWEEN e.enviado_en AND e.enviado_en + INTERVAL '7 days'
      AND b.ultima_campana_id = e.campana_id
GROUP BY c.campana
ORDER BY pct DESC;
flowchart TB
a[Envio de campana] --> b[Entregada por APNs]
b --> c{Reaccion observable}
c -->|abre| d[Beneficio inmediato medido]
c -->|descarta sin accion| e[Senal de ruido]
c -->|silencio| f[Sin informacion]
e --> g[Ajustes finos apagados]
g --> h[Autorizacion revocada]
h --> i[Token retirado con estado 410]
i --> j[Desinstalacion inferida]
d --> k[Balance neto por campana]
j --> k

El experimento con grupo de control

Ninguna de esas métricas decide por sí sola, porque todas se mueven con la estacionalidad, con la mezcla de usuarios y con los cambios de producto ajenos al canal. La única forma honesta de evaluar una política de notificaciones es mantener un grupo de control permanente: una fracción pequeña y estable de la base que no recibe una categoría concreta, o que no recibe nada en absoluto, y cuya retención se compara con la del grupo tratado a lo largo de trimestres.

El resultado suele ser incómodo y por eso rara vez se busca. Un experimento bien hecho muestra con frecuencia que la mitad de las campañas no produce diferencia estadísticamente detectable en retención, y que una minoría la produce negativa. Descubrirlo permite recortar volumen sin coste, y ese recorte tiene un efecto de segundo orden que ninguna campaña individual puede conseguir: al bajar la frecuencia total sube la tasa de apertura de todo lo demás, incluido lo transaccional, porque la atención liberada se redistribuye.

Dos precauciones metodológicas evitan las conclusiones falsas más habituales. La primera es que el grupo de control debe ser estable en el tiempo, no resorteado en cada campaña: solo la exposición acumulada durante meses revela el efecto sobre la retención, y una asignación nueva cada semana promedia precisamente lo que se quiere medir. La segunda es que la variable de resultado debe ser global —retención, sesiones, ingresos totales— y nunca atribuida al canal, porque la métrica atribuida es tautológicamente favorable al grupo que recibe los avisos.

Queda un último riesgo, de naturaleza organizativa más que técnica. El coste de notificar de más se paga en un horizonte distinto al que evalúa a quien decide enviar, y en la mayoría de los equipos nadie es responsable de la salud agregada del canal. La corrección estructural consiste en asignar esa propiedad de forma explícita, con un presupuesto de envíos por persona y periodo que se reparta entre equipos como cualquier otro recurso escaso. Cuando enviar cuesta algo, las campañas sin valor dejan de proponerse solas.

La bandeja de notificaciones es un bien común y todo emisor es un ganadero

El problema de la frecuencia de notificaciones tiene la estructura exacta de una tragedia de los comunes, y verlo así explica por qué la moderación voluntaria fracasa una y otra vez. La atención de una persona es un pasto compartido por cuarenta aplicaciones. Cada una obtiene el beneficio íntegro de cada aviso que envía, pero solo soporta una cuarentava parte del deterioro que ese aviso causa al recurso común: la disposición general a mirar la pantalla de bloqueo. El equilibrio racional individual es, por tanto, enviar más de lo colectivamente óptimo, y en efecto eso es lo que ocurre en el mercado. La consecuencia observable es la erosión que todo el mundo conoce: personas que apagan todo salvo mensajería, que activan modos de concentración permanentes, que revisan por lotes dos veces al día. Y aquí está el giro que reordena la estrategia. Cuando el recurso común se degrada, el valor no desaparece: se concentra en las pocas aplicaciones que siguen dentro del círculo de confianza. El premio por ser una de ellas es desproporcionado, porque quien conserva el permiso en un dispositivo donde treinta y ocho apps lo han perdido tiene un canal sin competencia. Y ese premio no se obtiene optimizando ninguna campaña, sino sosteniendo durante años una política de la que se deriva una única promesa verificable: aquí no se envía nada que la persona no habría pedido. Es una decisión de largo plazo que ningún panel trimestral recompensa, y precisamente por eso es defendible: la mayoría de tus competidores no podrá tomarla.

📝
Lo esencial

Separa lo transaccional de lo relacional y lo promocional, aplica topes por categoría en el servidor y encola contra la hora local del dispositivo. Ofrece un centro de preferencias granular y enlázalo desde los ajustes de iOS con providesAppNotificationSettings, para que exista una alternativa al interruptor general. Mide las cuatro señales de daño —descarte sin acción, desactivación, silenciamiento parcial y desinstalación inferida desde los tokens con estado 410— junto a las de beneficio. Y mantén un grupo de control permanente, porque a escala de trimestre casi la mitad de las campañas no aporta retención y una parte la resta.

⚔️ Someter tu canal a una auditoría honesta
  1. Clasifica los envíos del último trimestre en transaccional, relacional y promocional, y calcula el volumen mensual por persona de cada grupo.
  2. Implementa topes duros por categoría en la capa de encolado del servidor y verifica que ninguna campaña puede saltárselos desde su propia configuración.
  3. Construye el centro de preferencias con interruptor por categoría, ventana de silencio y una estimación honesta de frecuencia, y enlázalo desde los ajustes del sistema.
  4. Calcula la desinstalación inferida por campaña a partir de las respuestas con estado 410 de los últimos noventa días y ordena las campañas por ese valor.
  5. Aparta un grupo de control del cinco por ciento que no reciba nada promocional, obsérvalo durante un trimestre completo y compara retención y ventas totales, no solo las atribuidas al canal.