Hay una característica que casi todas las aplicaciones móviles comparten, pero que muy pocas implementan bien: el flujo de valoración (Review Gating).

Todos hemos vivido esa nefasta experiencia de usuario: abres una aplicación por primera vez y, antes de que puedas siquiera ver la pantalla principal o entender qué hace la app, te salta un pop-up gigante exigiendo cinco estrellas.

¿El resultado? Frustración, un cierre rápido o, peor aún, una reseña de una estrella por provocar ese malestar al usuario.

Pedir una valoración no es solo una tarea de marketing; es un desafío para integrar en tu producto. Os voy a exponer una solución que uso y seguiré usando (posiblemente mejorando) en algunas aplicaciones. El embudo de triaje.

¿Qué es un embudo de triaje?

El embudo de triaje nos va a permitir tener el control completo de las valoraciones que solicitamos en nuestra aplicación. En momentos clave, se preguntará al usuario si le gusta la aplicación.

  • Botón A: «Me encanta» -> Va a la tienda (asegurando buena valoración).
  • Botón B: «Podría mejorar» -> Iniciamos una contención de daños.
  • Botón C: «Ahora no» -> Salimos del proceso de valoración hasta otro momento.

La Contención de Daños. El usuario no está contento y no lo mandaremos a la tienda para que no nos hunda en la miseria. Pero vamos a conseguir que nos de feedback para mejorar puntos flacos.

Recibir feedback de manera efectiva

No podemos hacer al usuario perder tiempo. Para obtener el feedback rápido mi recomendación es usar una pequeña nube de tags que el usuario pueda marcar como etiquetas y una caja de texto opcional. El usuario pulsa en «bugs», «dificultad», «anuncios excesivos», deja un comentario si quiere y pulsa «Enviar».

En 10 segundos tenemos feedback, si hacemos un proceso tedioso, el usuario abandonará el proceso y posiblemente la aplicación. Esto también nos permitirá analizar mejor los puntos flacos según esas tags que ha marcado el usuario.

Creando el RatingManager: El controlador del funnel en Kotlin

Lo podemos llamar RatingManager, RatingController o como queramos. Pero si somos organizados deberíamos hacer que contenga toda la funcionalidad posible de Rating. Lo ideal sería que fuera lo más portable posible. Esta solución permite incluirlo en un paquete «rating» en nuestras apps, y copiarlo de una a otra, lo cual nos ahorrará mucho desarrollo.

Para ello, usaremos una interfaz, así podemos extender funcionalidad a futuro:

interface RatingPromptSettings {
    fun isRatingPromptEnabled(): Boolean
    fun getRatingActionsThreshold(): Long
    fun getRatingPromptCooldownDays(): Long
    fun getRatingNegativeFeedbackCooldownDays(): Long
}

A grandes rasgos vemos que tenemos un flag para controlar si está activado o no el sistema de rating (isRatingPropmptEnabled). Imagina lo útil que es si se produce un error en algún sistema, o se incluye un bug en la aplicación. Con esto podemos evitar una oleada de reseñas negativas que manden nuestra app al final de la lista del market.

La clave: Registrar las acciones útiles y comprobar tiempos

Nos basaremos en «acciones». Cada vez que el usuario realice una acción útil y que pueda satisfacer una necesidad que tenía al usar la app, incrementamos en 1 el contador. Por ejemplo, al terminar un nivel en un juego, al compartir una imagen editada.. Para ello, tendremos un método que llamaremos (RatingManager.incrementActionCount()) en cada ocasión y nos permitirá incrementar ese valor (que guardaremos en las preferencias del dispositivo)

fun incrementActionCount(): Boolean {
    if (hasRated()) return false

    val currentCount = prefs.getInt(KEY_ACTION_COUNT, 0) + 1
    prefs.edit { putInt(KEY_ACTION_COUNT, currentCount) }

    if (!settings.isRatingPromptEnabled()) return false

    val threshold = settings.getRatingActionsThreshold().toInt().coerceAtLeast(1)
    val isDivisible = (currentCount % threshold == 0)
    val isPromptCoolDownPassed = checkCoolDown(KEY_LAST_ASKED_TIME, settings.getRatingPromptCooldownDays())
    val isNegativeFeedbackCoolDownPassed = checkCoolDown(
        KEY_LAST_NEGATIVE_FEEDBACK_TIME,
        settings.getRatingNegativeFeedbackCooldownDays()
    )

    return isDivisible && isPromptCoolDownPassed && isNegativeFeedbackCoolDownPassed
}

La lógica de este método es muy sencilla:

Primero comprobamos si el usuario ya ha puntuado (y lo tenemos guardado en el sistema). Así lo dejamos tranquilo. Luego recuperamos la cuenta de acciones útiles que ha realizado el usuario.

Más adelante, salimos si el sistema está deshabilitado. Si lo hemos deshabilitado por algún motivo, no tenemos más que hacer aquí.

Si está habilitado, obtenemos el nº de acciones que hemos definido para solicitar una reseña al usuario. Si es divisible entre las acciones actuales (y hemos superado tiempo de espera de la última vez, tanto normal como el negativo) devolvemos un flag que indicará si el usuario está o no preparado para solicitarle de nuevo un rating.

Cumpliendo normativas de Google Play y App Store

Tanto Apple como Google, proporcionan en su SDK maneras nativas de solicitar Reviews a sus usuarios. Los dos proporcionan una caja negra que nos quita el control del proceso de triaje que queremos hacer. Para empezar, usar su sdk tiene restricciones muy concretas:

  • Apple limita en 3 las peticiones por año a su api nativa de valoraciones
  • No permiten preguntar al usuario si le gusta la app antes de abrir el diálogo nativo. Apple ya ni lo permite aunque no uses su SDK.
  • No puedes ofrecer recompensas a cambio de puntuaciones.
  • Google Limita el nº de veces que puedes llamar al API de rating por usuario de manera silenciosa y no te comunica que aunque lo solicites, no lo está mostrando.
  • No permite incluir iconos que influyan en la opinión de usuarios (estrellas, copa)
  • No permite comunicar al usuario que necesitamos que deje reseña.

Nuestra solución implementa «getRatingPromptCooldownDays» en su interfaz, que nos permitirá definir un valor acorde a lo que estipulan en sus normas (120 días para Apple, por ejemplo). Al no usar el sdk nativo de ninguno, podemos preguntar al usuario si le gusta o no antes de mandarlo al market o pedirle feedback.

Implementación del flujo de feedback de reseñas

Puntuar aplicación móvil

Implementar el flujo del que hablo es muy cómodo, siguiendo la estructura de la interfaz. En mi caso yo abro una modal desde abajo con la pregunta cuando se cumplen las condiciones.

RatingFunnelBottomSheet(
    onRateOnStore = { 
        ratingManager.setRated() // Marcamos como puntuado y lo dejamos
        context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=tu.app")))
    },
    onSubmitFeedback = { tags, comment ->
        enviarAAnalytics(tags, comment)
        ratingManager.onNegativeFeedbackSent() // Le damos un respiro largo y analizamos quejas
    },
    onDismiss = {
        ratingManager.postponeAsking() // No molestemos, volvemos en unos días
    }
)

Luego, nuestro RatingManager, controlará la marca de si ya ha puntuado usando hasRated y setRated.

fun setRated() {
    prefs.edit { putBoolean(KEY_HAS_RATED, true) }
}

private fun hasRated(): Boolean {
    return prefs.getBoolean(KEY_HAS_RATED, false)
}

También controlamos si el usuario pospone o si ha realizado un proceso de feedback, para actualizar las fechas oportunas y asegurarnos de que no volvemos a preguntarle hasta pasado el tiempo definido.

fun postponeAsking() {
    val now = System.currentTimeMillis()
    prefs.edit { putLong(KEY_LAST_ASKED_TIME, now) }
}

fun onNegativeFeedbackSent() {
    val now = System.currentTimeMillis()
    prefs.edit { putLong(KEY_LAST_NEGATIVE_FEEDBACK_TIME, now) }
}

//Con esta mediante el nombre de la preferencia obtenemos si el tiempo de espera ha pasado o no
private fun checkCoolDown(prefKey: String, cooldownDays: Long): Boolean {
        val lastTime = prefs.getLong(prefKey, 0L)
        if (lastTime == 0L) return true // Nunca ha ocurrido

        val diff = System.currentTimeMillis() - lastTime
        val daysDiff = TimeUnit.MILLISECONDS.toDays(diff)

        return daysDiff >= cooldownDays
}

¿Cómo gestionar los tiempos dinámicamente?

Eso es pura lógica de nuestra app, queda fuera de la responsabilidad de nuestro RatingManager. Mi apuesta ha sido siempre usar Firebase (remote config) para establecer y ajustar estos valores. Pero las opciones son muchas:

  • Una API propia o de un tercero
  • Un fichero de configuración
  • Permitir al usuario establecer los valores..

En cualquier caso, siempre hay que blindar esto con valores por defecto. Controlamos en nuestro componente el nº de aciones y si es un valor válido, mostramos el comienzo del proceso de rating.

¿Cómo gestionamos la información del usuario?

El número de acciones útiles con el que controlamos cuando mostrar o no al usuario la solicitud de reseñas. O los valores de fechas de último intento de solicitud (ya sea normal o negativa) son valores que guardamos en SharedPreferences (Android)

Actualmente no tengo implementado esto en ninguna aplicación para Apple. Cuando lo tenga puede que actualice con una solución común pero se puede usar el sistema análogo de Apple para este caso.

SharedPreferences nos permite acceder y escribrir de manera rápida y asíncrona sin bloquear el hilo y el uso general de la aplicación o la interfaz. Esto permite que todo sea transparente para el usuario sin empobrecer la experiencia que tiene en la aplicación.

Conclusión

Espero que haya ayudado a establecer un sistema ordenado y con objetivos claros a la hora de obtener reseñas para vuestras aplicaciones móviles. Y ojalá os ayude a mejorar la nota media más alta posible en vuestras aplicaciones.

¿Se os ocurren mejoras o alternativas para gestionar el funnel de valoraciones en vuestras aplicaciones?

Cómo crear un Review Gating para conseguir reseñas en una aplicación móvil

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Social media & sharing icons powered by UltimatelySocial