Permitir que un huésped gaste puntos de fidelización como dinero al pagar parece una función de interfaz. En realidad es un problema de concurrencia, que se resuelve con retenciones en la base de datos, no con restas.

Un programa de fidelización para hoteles que permite a los miembros "pagar" con puntos parece una función de interfaz: mostrar un saldo, dejar que el huésped aplique una parte, restar los puntos y listo. Así se suelen construir muchos bloques básicos de los programas de recompensas hoteleras, y funciona bien hasta que dos solicitudes llegan al mismo saldo a la vez. Un huésped toca dos veces canjear porque la pantalla de un quiosco ha respondido con medio segundo de retraso. Un motor de reservas reintenta automáticamente una solicitud que ha agotado el tiempo de espera. Si "gastar los puntos" es una única resta, ambas solicitudes pueden completarse, y el miembro acaba de gastar puntos que ya no tenía cuando llegó la segunda.
No es un caso límite que pueda apartarse con un gesto. Es el comportamiento por defecto de cualquier sistema que trata el canje como una instrucción UPDATE. También es la razón por la que el crédito monetario se construye como se explica a continuación: como un ciclo que aplica la base de datos, no como un campo de descuento que confía en el cliente.
Un miembro no gasta puntos en un único lugar, así que el crédito monetario no es una sola función, sino tres, cada una activable de forma independiente. Un huésped puede canjear puntos como dinero real en una reserva directa a través del motor de reservas propio del hotel, durante la estancia mediante el portal WiFi para huéspedes, o en el check-out mediante un terminal o quiosco de recepción. Un hotel puede activar las tres opciones, o solo una, por ejemplo el canje durante la estancia en el portal WiFi sin tocar nunca el flujo de reserva.
Esa independencia importa porque las tres superficies no tienen el mismo perfil de riesgo. Un huésped que canjea desde el teléfono durante la estancia, una pestaña del navegador abandonada a mitad del pago en el motor de reservas, una pantalla de quiosco que se queda colgada mientras el miembro se marcha: cada caso puede dejar un canje sin completar de una forma distinta. No son la misma transacción con tres apariencias distintas. Necesitan tiempos y gestión de fallos diferentes, y esa es buena parte de la razón por la que el ciclo común a las tres no se reduce a una única llamada de "restar ahora".
Un campo de descuento lee un saldo, comprueba que sea suficiente y escribe un número menor. Eso sirve para un valor que nadie más puede tocar entre la lectura y la escritura. Un saldo de fidelización falla esa prueba constantemente: el teléfono de un miembro pierde la señal a mitad del pago y la aplicación reintenta, o una solicitud POST de un quiosco se queda colgada y el terminal la vuelve a lanzar al agotarse el tiempo de espera. Entonces dos solicitudes leen el mismo saldo inicial y ambas creen que están autorizadas para gastar contra él.
Una resta no recuerda que ya se ha ejecutado. Una retención sí.
El ciclo es Simulación, luego Retención, y después una de estas opciones: Confirmación, Liberación o Caducidad. Nunca un único paso de "restar los puntos". Una simulación solo muestra al miembro cómo quedaría un canje, sin tocar en absoluto el saldo. Una retención es el compromiso: reserva los puntos contra el saldo de ese miembro para que nadie más pueda gastarlos mientras tanto, pero una retención por sí sola todavía no ha gastado nada. Solo la confirmación lo hace.
La integración aporta una referencia externa estable para la retención, de modo que una solicitud repetida pueda reconocerse sin crear otra reserva sobre los mismos puntos. Reutilizar esa referencia forma parte del contrato de integración; una referencia nueva representa una solicitud distinta. Si un huésped abandona el flujo después de la retención pero antes de confirmar, los puntos permanecen reservados hasta que la retención se libera o caduca, en lugar de gastarse por la propia retención.
Nada de esto funciona si el código de la aplicación sigue encargándose de la aritmética. No lo hace. El cálculo del saldo se ejecuta íntegramente dentro de funciones SQL atómicas en la base de datos: create_loyalty_credit_hold, confirm_loyalty_credit_hold y una función loyalty_member_spendable() que calcula lo que un miembro todavía puede gastar en este momento, teniendo en cuenta todo lo que ya está retenido. El código de la aplicación llama a estas funciones, nunca resta directamente un saldo por su cuenta.
El flujo de retención comprueba el saldo disponible del miembro y crea la retención bajo un bloqueo de base de datos, en lugar de separar la comprobación y la escritura en el código de la aplicación. Las solicitudes simultáneas se evalúan frente a los puntos que siguen disponibles después de las retenciones activas existentes. Una solicitud se rechaza si pide más que ese saldo disponible; dos solicitudes pueden completarse si hay puntos suficientes para cubrir ambas.
Las retenciones sin confirmar tienen una fecha de caducidad para que una sesión abandonada no reserve puntos indefinidamente. El plazo predeterminado actual es de 15 minutos y puede configurarse en la política de saldo de fidelización del grupo hotelero. Cuando una retención activa supera su fecha de caducidad, sus puntos dejan de reducir el saldo disponible; un proceso en segundo plano también marca las retenciones vencidas como caducadas. La integración debe utilizar la fecha de caducidad devuelta con la retención en lugar de asumir un plazo fijo para el motor de reservas o el quiosco.
El crédito monetario solo se aplica a reservas cuyo margen el hotel puede ver realmente. Las reservas procedentes de Booking.com, Expedia y canales similares de OTA o turoperadores tienen bloqueado de forma estricta el uso del crédito monetario, y ese bloqueo se mantiene independientemente de cómo configure el grupo hotelero el resto de la función. No hay ningún ajuste que lo habilite para esos canales, porque no existe una forma segura de fijar el precio de "permitir que un huésped canjee puntos contra una reserva procedente de una OTA" si el hotel no sabe cuánto ingresa realmente por esa reserva. Usar puntos como pago de una reserva hotelera solo tiene sentido cuando el hotel es quien define la economía de la operación.
Un hotel no tiene que confiar a ciegas en la integración. Antes de habilitar el canje para huéspedes reales, el crédito monetario puede ejecutarse en modo de vista previa en vivo, un modo solo de simulación que muestra exactamente cómo sería un canje real sin crear nunca una retención. Es una forma de comprobar la integración frente al comportamiento real, sin riesgo de que una retención accidental quede en la cuenta de un miembro real.
Una vez activados, cinco tipos de eventos de webhook firmados con HMAC permiten actualizar las retenciones, confirmaciones, liberaciones, anulaciones y cambios de saldo. Una retención caducada se comunica como una liberación con un motivo de caducidad. La entrega incluye reintentos, por lo que los sistemas receptores deben verificar las firmas y gestionar las notificaciones repetidas, sin asumir que cada notificación llega inmediatamente o una sola vez. La tasa de canje utilizada como alternativa es de 100 puntos por 1 unidad de moneda; la tasa configurada por el grupo hotelero tiene prioridad.
Nada de esto es complicado de explicar a un huésped. La parte que merece estar bien resuelta antes de lanzar la función es lo que hay por debajo, porque el saldo de fidelización es la parte de un programa de fidelización en la que el miembro sí revisa las cuentas. Si todavía estás decidiendo cómo debería ser un programa así antes de entrar en la mecánica del pago, cómo lanzar un programa de fidelización hotelera es el lugar por donde empezar.
Treinta minutos con tus hoteles reales.