La recompensa diaria es el truco más viejo y más eficaz que hay para que la gente vuelva: entras, te dan algo, y mañana te dan un poco más si vuelves. Lo que hace que funcione no es el regalo, es la racha: nadie quiere perder siete días seguidos.
Programarla parece trivial hasta que aparecen las dos preguntas incómodas: ¿qué es «un día» exactamente, y qué pasa si el jugador se cambia la hora del ordenador?
En la demo puedes avanzar el reloj día a día, saltarte uno y ver cómo se rompe la racha.
Respuesta rápida
Guarda la hora del servidor (os.time()) de la última recompensa. Al entrar, compara con la de ahora: menos de 24 h, nada; entre 24 y 48 h, siguiente día de racha; más de 48 h, la racha vuelve a empezar. El reloj del jugador no se consulta nunca.
Lo que necesitas antes de empezar
- Monedas o lo que vayas a regalar: leaderboard de puntuación.
- Guardado que funcione, porque esto tiene que persistir: DataStore sin perder datos.
- Unos 35 minutos.
Paso 1: qué guardamos
Solo dos datos por jugador:
local POR_DEFECTO = {
ultimaRecompensa = 0, -- os.time() de la ultima vez que cobro
racha = 0, -- cuantos dias seguidos lleva
}
os.time() devuelve los segundos transcurridos desde 1970 en UTC, igual en todos los servidores del mundo. Por eso vale: no depende de la zona horaria del jugador ni de la del servidor que le toque.
Paso 2: decidir qué le toca
local UN_DIA = 24 * 60 * 60 -- 86.400 segundos
local function estadoRecompensa(datos)
local ahora = os.time()
local pasado = ahora - datos.ultimaRecompensa
if datos.ultimaRecompensa == 0 then
return "primera", 1
end
if pasado < UN_DIA then
return "esperar", datos.racha -- ya cobro hoy
end
if pasado < UN_DIA * 2 then
return "sigue", datos.racha + 1 -- vuelve a tiempo
end
return "rota", 1 -- se salto un dia
end
Los tres casos, en cristiano:
- Menos de 24 h: ya ha cobrado. Le enseñas cuánto falta.
- Entre 24 y 48 h: ha vuelto dentro de la ventana. Racha +1.
- Más de 48 h: se saltó un día. Vuelve al día 1.
Esa ventana de 24 a 48 horas es más justa que el «día natural». Con días naturales, quien juega a las 23:50 puede cobrar otra vez a las 00:10; y quien juega a las 10:00 tiene que esperar catorce horas. Con la ventana, todo el mundo tiene exactamente un día.

Paso 3: la tabla de recompensas
local RECOMPENSAS = {
[1] = {monedas = 50, texto = "50 monedas"},
[2] = {monedas = 75, texto = "75 monedas"},
[3] = {monedas = 100, texto = "100 monedas"},
[4] = {monedas = 150, texto = "150 monedas"},
[5] = {monedas = 200, texto = "200 monedas"},
[6] = {monedas = 300, texto = "300 monedas"},
[7] = {monedas = 750, texto = "750 monedas + cofre", cofre = true},
}
local function recompensaDelDia(racha)
local dia = ((racha - 1) % 7) + 1 -- del 7 vuelve al 1
return RECOMPENSAS[dia], dia
end
Ese % hace que el ciclo se repita: día 8 = día 1 otra vez, pero con la racha intacta. Así puedes premiar rachas largas (un objeto especial a los 30 días) sin escribir treinta filas.
El día 7 tiene que ser claramente el gordo. Si el séptimo día da 350 en vez de 300, nadie aguanta la semana. El salto se tiene que ver.
Paso 4: entregarla
Un RemoteFunction va bien aquí, porque el cliente quiere una respuesta:
local Replicado = game:GetService("ReplicatedStorage")
local funcion = Replicado:WaitForChild("PedirRecompensa")
funcion.OnServerInvoke = function(jugador)
local datos = sesiones[jugador]
if not datos then return {ok = false, motivo = "cargando"} end
local estado, nuevaRacha = estadoRecompensa(datos)
if estado == "esperar" then
local faltan = (datos.ultimaRecompensa + UN_DIA) - os.time()
return {ok = false, motivo = "esperar", segundos = faltan}
end
local premio, dia = recompensaDelDia(nuevaRacha)
-- Se apunta ANTES de entregar
datos.racha = nuevaRacha
datos.ultimaRecompensa = os.time()
local stats = jugador:FindFirstChild("leaderstats")
local monedas = stats and stats:FindFirstChild("Monedas")
if monedas then
monedas.Value = monedas.Value + premio.monedas
end
guardar(jugador) -- esto se guarda ya, no esperamos al reloj
return {ok = true, dia = dia, racha = nuevaRacha, texto = premio.texto}
end
Dos decisiones que parecen detalles y no lo son:
- Se marca la fecha antes de entregar. Si entregas primero y algo se cae en medio, el jugador puede repetir la llamada y cobrar dos veces.
- Se guarda inmediatamente. Una recompensa diaria perdida por un servidor que se cayó a los dos minutos es una queja segura en el chat del juego.
Paso 5: la cuenta atrás en el cliente
local funcion = game:GetService("ReplicatedStorage"):WaitForChild("PedirRecompensa")
local boton = script.Parent.Boton
local etiqueta = script.Parent.Aviso
local function formatear(segundos)
local h = math.floor(segundos / 3600)
local m = math.floor((segundos % 3600) / 60)
return h .. " h " .. m .. " min"
end
boton.Activated:Connect(function()
boton.Active = false
local r = funcion:InvokeServer()
if r.ok then
etiqueta.Text = "Dia " .. r.dia .. ": " .. r.texto
else
if r.motivo == "esperar" then
etiqueta.Text = "Vuelve en " .. formatear(r.segundos)
else
etiqueta.Text = "Un momento..."
boton.Active = true
end
end
end)
La cuenta atrás la calcula el servidor y el cliente solo la enseña. Si dejas que el cliente decida cuándo han pasado 24 horas, adelantar el reloj del ordenador se lleva la recompensa.
Paso 6: enseñar la semana entera
El panel que se ve en los juegos que lo hacen bien es una fila de siete casillas: las cobradas marcadas, la de hoy resaltada, las de después en gris. No es adorno: enseñar el día 7 desde el día 1 es lo que sostiene la racha. Si el jugador no ve lo que se pierde, no vuelve.
Pruébalo aquí
Mueve el reloj y mira qué pasa con la racha
Vuelve al rato, vuelve al día siguiente o sáltate un día. La lógica es exactamente la de los pasos 2 y 3.
Cada botón adelanta el reloj y vuelve a pedir la recompensa, igual que si el jugador entrase en ese momento.
Historial
Errores habituales
El jugador cobra varias veces seguidas
No estás guardando ultimaRecompensa, o lo guardas después de entregar y algo se cae en medio. Márcalo primero.
Puede cobrar adelantando el reloj de su PC
Estás usando la hora del cliente en alguna parte. La única hora válida es os.time() en el servidor.
La racha se rompe sola aunque entre todos los días
Estás comparando días naturales con os.date y la zona horaria te está jugando una mala pasada, o la ventana es de 24 h exactas en lugar de 48. Con pasado < UN_DIA * 2 para seguir la racha, entrar «a la misma hora más o menos» siempre funciona.
Se pierde la racha al cambiar de servidor
No se está guardando en el DataStore, o se guarda solo cada dos minutos y el jugador salió antes. Guarda al entregar.
El botón se puede pulsar veinte veces mientras responde el servidor
Falta desactivarlo (boton.Active = false) al invocar. En el servidor, además, la marca de fecha ya lo impide, pero la interfaz debe acompañar.
Preguntas frecuentes
¿Y si quiero que la racha aguante un día de fallo?
Amplía la ventana a 48 horas para seguir y rompe a las 72. Es más amable y se nota en la retención. Lo que no debes hacer es permitir cobrar dos veces en el mismo día.
¿Puedo dar un objeto en vez de monedas?
Sí: cambia premio.monedas por lo que entregues. Si es un objeto único, marca que ya lo tiene para no duplicarlo, como en la tienda.
¿Cómo hago una racha de 30 días con premio final?
La tabla se queda igual (ciclo de 7) y añades una comprobación aparte: si racha == 30, además del premio del día, el objeto especial. Y guárdalo como comprado para que no se repita al llegar a 60.
¿Debería avisar al jugador de que va a perder la racha?
Si tienes notificaciones fuera del juego, es lo que más funciona. Dentro del juego, con enseñar «racha: 6 días» bien grande junto al botón ya haces la mitad del trabajo.
Última revisión: 1 de septiembre de 2026





Deja una respuesta