Cómo montar una tienda donde gastar monedas

Claim Your Loot

·

Portada del artículo «Una tienda donde gastar monedas», de la sección Sistemas de botín

Un sistema de botín sin tienda es una colección de números que no sirven para nada. La tienda es lo que convierte «llevo 4.300 monedas» en «me faltan 700 para la espada», y ahí es donde el jugador se engancha.

Montarla tiene una parte fácil (los botones) y una parte que hay que hacer bien a la primera: la compra se decide en el servidor. Si el cliente decide, cualquiera se regala el catálogo entero.

En la demo tienes una tienda funcionando con tres artículos: gasta, quédate sin monedas y prueba a pulsar dos veces seguidas.

Respuesta rápida

El catálogo vive en un ModuleScript que ve el servidor. El cliente solo pide comprar por un RemoteEvent. El servidor comprueba que el artículo existe, que el precio es el suyo, que al jugador le llega, resta y entrega. El cliente nunca manda el precio.

Lo que necesitas antes de empezar

Paso 1: el catálogo en un ModuleScript

En ReplicatedStorage, un ModuleScript llamado Catalogo:

local Catalogo = {
	espada_hierro = {
		nombre = "Espada de hierro",
		precio = 250,
		descripcion = "Hace mas dano que la de madera.",
	},
	pocion_vida = {
		nombre = "Pocion de vida",
		precio = 75,
		descripcion = "Recupera 50 puntos de vida.",
	},
	capa_rara = {
		nombre = "Capa rara",
		precio = 1200,
		descripcion = "No hace nada. Es bonita.",
	},
}

return Catalogo

Está en ReplicatedStorage porque el cliente necesita leerlo para dibujar la tienda: nombres, precios, descripciones. Que el cliente vea los precios no es un problema. El problema sería que los decidiera.

Paso 2: el RemoteEvent

En ReplicatedStorage, un RemoteEvent llamado ComprarObjeto.

Y aquí la regla que se salta media internet:

El cliente manda qué quiere comprar. Nunca cuánto cuesta.

Si el cliente manda el precio, un exploit manda 0 y se lo lleva gratis. Si manda solo el identificador, el servidor busca el precio en su copia del catálogo y no hay nada que falsear.

Paso 3: el servidor decide

Un Script en ServerScriptService:

local Replicado = game:GetService("ReplicatedStorage")
local Catalogo = require(Replicado:WaitForChild("Catalogo"))
local evento = Replicado:WaitForChild("ComprarObjeto")

local ocupado = {}      -- para que no compre dos veces a la vez

local function darObjeto(jugador, id, articulo)
	-- Aqui entregas de verdad: clonar una Tool, subir una stat, lo que sea
	local mochila = jugador:FindFirstChild("Backpack")
	local modelo = game.ServerStorage.Objetos:FindFirstChild(id)
	if modelo then
		if mochila then
			modelo:Clone().Parent = mochila
		end
	end
end

evento.OnServerEvent:Connect(function(jugador, id)
	-- 1. Que lo que llega sea lo que esperamos
	if type(id) ~= "string" then return end

	-- 2. Que el articulo exista
	local articulo = Catalogo[id]
	if not articulo then return end

	-- 3. Que no este ya comprando
	if ocupado[jugador] then return end
	ocupado[jugador] = true

	-- 4. Que le llegue
	local stats = jugador:FindFirstChild("leaderstats")
	local monedas = stats and stats:FindFirstChild("Monedas")

	if monedas then
		if monedas.Value >= articulo.precio then
			monedas.Value = monedas.Value - articulo.precio
			darObjeto(jugador, id, articulo)
			evento:FireClient(jugador, id, true, "Comprado")
		else
			evento:FireClient(jugador, id, false, "No te llegan las monedas")
		end
	end

	ocupado[jugador] = nil
end)

Las cuatro comprobaciones están numeradas a propósito. Ninguna sobra:

  1. El tipo. Un exploit puede mandar una tabla, un número o nil. Si no compruebas, el Catalogo[id] puede petar el script.
  2. Que exista. Un identificador inventado tiene que caerse solo, no crear objetos fantasma.
  3. La bandera ocupado. Sin ella, pulsar dos veces muy rápido puede entrar dos veces antes de que se reste. Es el clásico duplicado.
  4. El saldo. Y el orden importa: restar antes de entregar. Si entregas primero y falla el resto, has regalado el objeto.

Paso 4: la interfaz

Un LocalScript dentro del ScreenGui de la tienda:

local Replicado = game:GetService("ReplicatedStorage")
local Catalogo = require(Replicado:WaitForChild("Catalogo"))
local evento = Replicado:WaitForChild("ComprarObjeto")

local marco = script.Parent:WaitForChild("Marco")
local plantilla = marco:WaitForChild("Plantilla")

for id, articulo in pairs(Catalogo) do
	local fila = plantilla:Clone()
	fila.Name = id
	fila.Visible = true
	fila.Titulo.Text = articulo.nombre
	fila.Precio.Text = articulo.precio .. " monedas"
	fila.Parent = marco

	fila.Boton.Activated:Connect(function()
		fila.Boton.Text = "..."
		evento:FireServer(id)
	end)
end

evento.OnClientEvent:Connect(function(id, exito, mensaje)
	local fila = marco:FindFirstChild(id)
	if fila then
		fila.Boton.Text = mensaje
		task.wait(1.5)
		fila.Boton.Text = "Comprar"
	end
end)

Lo que hace bien este cliente: enseña que está pasando algo ("...") y espera la respuesta del servidor para decir si salió bien. Lo que no hace: restar monedas por su cuenta. Las monedas las baja el servidor y el cambio llega solo, porque un IntValue del jugador se replica.

El cliente manda el identificador y el servidor comprueba, resta y entrega

Paso 5: que no se pueda comprar dos veces lo que es único

Para artículos de un solo uso (una capa, una mascota) hay que llevar la cuenta:

local function yaLoTiene(jugador, id)
	local comprados = jugador:FindFirstChild("datos")
	if not comprados then return false end

	local marca = comprados:FindFirstChild(id)
	if marca then return true end
	return false
end

Y en el servidor, antes de cobrar:

if articulo.unico then
	if yaLoTiene(jugador, id) then
		evento:FireClient(jugador, id, false, "Ya lo tienes")
		ocupado[jugador] = nil
		return
	end
end

Esas marcas hay que guardarlas en el DataStore junto con el resto del progreso, o al volver a entrar la capa se paga dos veces: cómo guardar los datos con DataStore.

Paso 6: poner precios que tengan sentido

Un catálogo con precios inventados aburre en diez minutos. La cuenta que hay que hacer es cuántos minutos de juego cuesta cada cosa:

minutos para comprarlo = precio ÷ monedas que gana por minuto

Si tu jugador gana unas 60 monedas por minuto:

ArtículoPrecioTiempo real
Poción75poco más de un minuto
Espada250unos 4 minutos
Capa1.200unos 20 minutos

Ese escalón (algo cada minuto, algo cada cinco, algo cada veinte) es lo que hace que siempre haya un objetivo cerca y otro lejos. Si todo cuesta veinte minutos, el jugador se va antes de comprar nada.

Pruébalo aquí

La tienda, con el servidor decidiendo

Gana monedas, compra, quédate sin saldo. Y si quitas la bandera del paso 3, pulsa dos veces muy rápido a ver qué pasa.

🪙 300 monedas

Con la bandera quitada, pulsa dos veces seguidas muy rápido en un artículo caro.

Lo que hace el servidor

Mochila vacía

Errores habituales

El jugador se compra cosas gratis

El cliente está mandando el precio, o está restando él las monedas. El precio lo pone el servidor leyendo su catálogo, siempre.

Se duplica el objeto al pulsar rápido

Falta la bandera ocupado. Es exactamente ese síntoma.

Compra, se le resta, pero no recibe nada

darObjeto falló (el modelo no está donde crees) después de haber restado. Comprueba que la entrega funciona antes de cobrar, o devuelve las monedas si falla.

Las monedas bajan en su pantalla pero vuelven a subir

El cliente restó por su cuenta y luego llegó el valor real del servidor. Quita esa resta del LocalScript.

attempt to index nil with 'Monedas'

El jugador entró y la tienda se usó antes de que existiera leaderstats. En el servidor comprueba con FindFirstChild, como en el ejemplo.

El objeto único se puede comprar otra vez tras reconectar

No estás guardando las compras en el DataStore. La marca tiene que sobrevivir a la sesión.

Preguntas frecuentes

¿Puedo vender por Robux en la misma tienda?

Sí, pero es otro mecanismo: eso pasa por MarketplaceService y por la comisión de la plataforma. Lo tienes en cómo crear un gamepass y venderlo en tu juego.

¿Dónde pongo el catálogo si no quiero que el cliente lo vea entero?

En ServerStorage, y mandas al cliente solo lo que toque enseñar. Se hace cuando tienes artículos secretos o que se desbloquean; para una tienda normal no compensa la complicación.

¿Cómo hago rebajas o precios por tiempo limitado?

Un campo más en el catálogo (descuento) y que el servidor calcule el precio final. Nunca dejes que el cliente mande el precio con descuento aplicado.

¿Puedo dejar que vendan objetos de vuelta?

Sí, y devuelve menos de lo que costó (la mitad es lo habitual). Si devuelves el precio completo, comprar y vender en bucle no cuesta nada y cualquier bug de entrega se convierte en una máquina de monedas.


Última revisión: 1 de septiembre de 2026

Claim Your Loot

Explora otras secciones

Deja una respuesta

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