Mudanças entre as edições de "Módulo:Gb"

De Wiki Gla
Ir para navegação Ir para pesquisar
Linha 1: Linha 1:
--[[
Module:CalculadoraTier
Calculadora de recursos (berry e fragmentos) para evoluir um personagem por Tiers e Estrelas.
✔ Como usar na wiki (Wikitext):
  {{#invoke:CalculadoraTier|calc
    |deTier=bronze
    |deEstrelas=2
    |paraTier=diamante
    |paraEstrelas=4
  }}
Parâmetros opcionais:
  |moeda=berry            -- nome exibido para a moeda (default: "berry")
  |frag=fragmentos        -- nome exibido para os fragmentos (default: "fragmentos")
  |mostrarPassos=sim      -- mostra o passo a passo (sim/não)
📌 Importante: SUBSTITUA os valores de custo abaixo (tabela DATA.custos) pelos valores reais do seu jogo.
A estrutura já está pronta para você só trocar os números.
]]
local p = {}
local p = {}


-- === Utilidades ===
-- =======================
-- CONFIGURAÇÃO DE DADOS
-- =======================
-- Nome dos tiers em ordem de progressão.
-- Use chaves em minúsculas, sem acento, para evitar problemas.
local DATA = {
  tiers = { "bronze", "prata", "gold", "diamante" },
 
  -- Custo para subir 1 estrela DENTRO do mesmo tier.
  -- Para cada tier (chave), defina uma lista de 5 valores que representam o custo para ir:
  --  0 ➜ 1, 1 ➜ 2, 2 ➜ 3, 3 ➜ 4, 4 ➜ 5 estrelas.
  -- Exemplo abaixo usa NÚMEROS DE PLACEHOLDER! Troque pelos números reais.
  custos = {
    bronze = {
      estrelas = {
        { berry = 100, frag = 2 },  -- 0->1
        { berry = 150, frag = 3 },  -- 1->2
        { berry = 200, frag = 4 },  -- 2->3
        { berry = 250, frag = 5 },  -- 3->4
        { berry = 300, frag = 6 },  -- 4->5
      },
      -- Custo para PROMOVER do tier atual para o próximo (depois de 5★).
      promover = { berry = 500, frag = 10 },
    },


local function trim(s)
    prata = {
    if type(s) ~= "string" then
      estrelas = {
         return s
        { berry = 400, frag = 4 },
    end
        { berry = 500, frag = 5 },
     return mw.text.trim(s)
        { berry = 600, frag = 6 },
end
        { berry = 700, frag = 7 },
         { berry = 800, frag = 8 },
      },
      promover = { berry = 1200, frag = 16 },
     },


local function split_list(s)
     gold = {
     -- Tenta primeiro por ';' (recomendado quando usar vírgula decimal).
      estrelas = {
    if s:find(";") then
         { berry = 900,  frag = 9 },
         local t = {}
         { berry = 1100, frag = 10 },
         for part in mw.text.gsplit(s, ";", true) do
        { berry = 1300, frag = 11 },
            table.insert(t, part)
         { berry = 1500, frag = 12 },
         end
         { berry = 1700, frag = 13 },
         return t
      },
    end
      promover = { berry = 2500, frag = 24 },
    -- Caso não haja ';', separa por ',' (útil se números usam ponto decimal).
     },
    local t = {}
     for part in mw.text.gsplit(s, ",", true) do
        table.insert(t, part)
    end
    return t
end


local function parse_number(token)
    diamante = {
    if type(token) ~= "string" then
      estrelas = {
         return nil
         { berry = 2000, frag = 15 },
    end
        { berry = 2300, frag = 16 },
    token = trim(token)
        { berry = 2600, frag = 17 },
    if token == "" then
        { berry = 2900, frag = 18 },
         return nil
         { berry = 3200, frag = 19 },
     end
      },
      -- Se diamante for o último tier, manter promover = nil ou {}.
      promover = nil,
     },
  },
}


    -- Remove espaços
-- =======================
    token = token:gsub("%s+", "")
-- FUNÇÕES AUXILIARES
-- =======================


    -- Heurística pt-BR:
local function norm(s)
    -- 1) Se tiver vírgula e ponto, assume '.' como milhar e ',' como decimal
  if type(s) ~= "string" then return s end
    --    Ex.: "1.234,56" -> "1234.56"
  s = s:lower()
    if token:find(",") and token:find("%.") then
  s = s:gsub("[áàâã]","a"):gsub("[éê]","e"):gsub("[í]","i"):gsub("[óôõ]","o"):gsub("[ú]","u"):gsub("ç","c")
        token = token:gsub("%.", "") -- tira milhares
  return s
        token = token:gsub(",", ".") -- vírgula -> ponto decimal
end
        -- 2) Se só tiver vírgula, assume vírgula decimal
    elseif token:find(",") then
        token = token:gsub(",", ".")
        -- 3) Se só tiver ponto, deixa como está (ponto decimal)
    end


    local n = tonumber(token)
local function findTierIndex(tier)
     return n
  local t = norm(tier)
  for i, name in ipairs(DATA.tiers) do
     if norm(name) == t then return i end
  end
  return nil
end
end


local function collect_numbers(frame)
local function validateInputs(args)
    local args = frame:getParent() and frame:getParent().args or frame.args
  local deTier = args.deTier or args.de or args.fromTier
    local nums = {}
  local paraTier = args.paraTier or args.para or args.toTier
  local deEstrelas = tonumber(args.deEstrelas or args.estrelas or args.fromStars) or 0
  local paraEstrelas = tonumber(args.paraEstrelas or args.toStars) or 0


    -- 1) Lista em 'valores='
  if not deTier or not paraTier then
    local lista = args.valores or args.lista
     return nil, "Informe os parâmetros deTier e paraTier."
     if lista and trim(lista) ~= "" then
  end
        for _, part in ipairs(split_list(lista)) do
 
            local n = parse_number(part)
  local deIdx = findTierIndex(deTier)
            if n then
  local paraIdx = findTierIndex(paraTier)
                table.insert(nums, n)
  if not deIdx then return nil, "Tier inicial inválido: " .. tostring(deTier) end
            end
  if not paraIdx then return nil, "Tier de destino inválido: " .. tostring(paraTier) end
        end
 
     end
  if deEstrelas < 0 or deEstrelas > 5 then
    return nil, "deEstrelas deve estar entre 0 e 5."
  end
  if paraEstrelas < 0 or paraEstrelas > 5 then
     return nil, "paraEstrelas deve estar entre 0 e 5."
  end


    -- 2) Parâmetros posicionais 1,2,3,...
  -- Também não permitir alvo "antes" da origem.
    local i = 1
  if paraIdx < deIdx or (paraIdx == deIdx and paraEstrelas < deEstrelas) then
    while true do
    return nil, "O destino deve ser maior que a origem."
        local v = args[i]
  end
        if v == nil then
            break
        end
        v = trim(v)
        if v and v ~= "" then
            -- Pode ser lista dentro do posicional
            local parts = split_list(v)
            for _, part in ipairs(parts) do
                local n = parse_number(part)
                if n then
                    table.insert(nums, n)
                end
            end
        end
        i = i + 1
    end


     return nums
  return {
    deIdx = deIdx,
    paraIdx = paraIdx,
    deTier = DATA.tiers[deIdx],
    paraTier = DATA.tiers[paraIdx],
    deEstrelas = deEstrelas,
     paraEstrelas = paraEstrelas,
    moeda = args.moeda or "berry",
    frag = args.frag or "fragmentos",
    mostrarPassos = norm(tostring(args.mostrarPassos or "nao")) == "sim",
  }
end
end


local function round(n, casas)
-- Soma custos de estrelas dentro de um tier, do nível deEstrelas até (exclusivo) alvoEstrelas.
    if not casas or casas == "" then
local function somarEstrelas(tierKey, deEstrelas, alvoEstrelas)
        return n
  local cust = DATA.custos[tierKey]
    end
  local total = { berry = 0, frag = 0 }
    casas = tonumber(casas) or 0
  local passos = {}
     local mult = 10 ^ casas
  if not cust or not cust.estrelas then return total, passos end
     if n >= 0 then
  for s = deEstrelas, alvoEstrelas - 1 do
        return math.floor(n * mult + 0.5) / mult
     local step = cust.estrelas[s + 1] -- +1 porque tabela começa no 1
    else
     if step then
        return math.ceil(n * mult - 0.5) / mult
      total.berry = total.berry + (step.berry or 0)
      total.frag  = total.frag  + (step.frag or 0)
      table.insert(passos, { tipo = "estrela", from = s, to = s + 1, tier = tierKey, berry = step.berry or 0, frag = step.frag or 0 })
     end
     end
  end
  return total, passos
end
end


local function format_number(n, casas, formato)
-- =======================
    n = round(n, casas)
-- LÓGICA PRINCIPAL
    if formato == "pt" then
-- =======================
        -- Formata com vírgula decimal e ponto de milhar
 
        local s = string.format("%." .. tostring(tonumber(casas) or 0) .. "f", n)
local function calcular(args)
        -- s usa ponto decimal; converter para pt-BR
  local ok, errOrCfg = pcall(validateInputs, args)
        local inteiro, frac = s:match("^(-?%d+)%.(%d+)$")
  if not ok then return nil, errOrCfg end
        if not inteiro then
  if not errOrCfg then return nil, "Parâmetros inválidos." end
            inteiro = s
  if type(errOrCfg) == "string" then return nil, errOrCfg end
            frac = nil
  local cfg = errOrCfg
        end
 
        -- insere pontos nos milhares
  local total = { berry = 0, frag = 0 }
        local k
  local passos = {}
        local sign = ""
 
        if inteiro:sub(1, 1) == "-" then
  for idx = cfg.deIdx, cfg.paraIdx do
            sign = "-"
    local tierKey = DATA.tiers[idx]
            inteiro = inteiro:sub(2)
 
        end
    -- Definir início e fim das estrelas para este tier
        while true do
    local startStar = (idx == cfg.deIdx) and cfg.deEstrelas or 0
            inteiro, k = inteiro:gsub("^(-?%d+)(%d%d%d)", "%1.%2")
    local endStar  = (idx == cfg.paraIdx) and cfg.paraEstrelas or 5
            if k == 0 then
 
                break
    -- Somar estrelas do tier atual
            end
    local parc, pss = somarEstrelas(tierKey, startStar, endStar)
        end
    total.berry = total.berry + parc.berry
        if frac then
    total.frag  = total.frag  + parc.frag
            return sign .. inteiro .. "," .. frac
    for _, x in ipairs(pss) do table.insert(passos, x) end
        else
 
            return sign .. inteiro
     -- Se não for o último tier de destino e atingiu 5 estrelas, pagar promoção
        end
    if endStar == 5 and idx < cfg.paraIdx then
     else
      local promo = (DATA.custos[tierKey] and DATA.custos[tierKey].promover) or { berry = 0, frag = 0 }
        -- "en" ou padrão: ponto decimal, sem milhar
      total.berry = total.berry + (promo.berry or 0)
        local casasN = tonumber(casas) or 0
      total.frag  = total.frag  + (promo.frag or 0)
        return string.format("%." .. tostring(casasN) .. "f", n)
      table.insert(passos, { tipo = "promocao", tier = tierKey, para = DATA.tiers[idx + 1], berry = promo.berry or 0, frag = promo.frag or 0 })
     end
     end
  end
  return { total = total, passos = passos, cfg = cfg }
end
end


local function compute(nums, op)
-- =======================
    if #nums == 0 then
-- RENDERIZAÇÃO (HTML simples / Wikitext)
        return nil
-- =======================
    end
 
    if op == "soma" or op == "sum" then
local function formatNumero(n)
        local s = 0
  -- separador de milhar simples
        for _, v in ipairs(nums) do
  local s = tostring(math.floor(n or 0))
            s = s + v
  local k
        end
  while true do
        return s
    s, k = s:gsub("^(%d+)(%d%d%d)", "%1.%2")
    elseif op == "media" or op == "média" or op == "avg" then
    if k == 0 then break end
        local s = 0
  end
        for _, v in ipairs(nums) do
  return s
            s = s + v
        end
        return s / #nums
    elseif op == "min" or op == "mín" then
        local m = nums[1]
        for i = 2, #nums do
            if nums[i] < m then
                m = nums[i]
            end
        end
        return m
    elseif op == "max" then
        local m = nums[1]
        for i = 2, #nums do
            if nums[i] > m then
                m = nums[i]
            end
        end
        return m
    elseif op == "contar" or op == "count" then
        return #nums
    else
        -- padrão: soma
        local s = 0
        for _, v in ipairs(nums) do
            s = s + v
        end
        return s
    end
end
end


-- === Interface pública ===
local function renderResultado(res)
  local moeda = res.cfg.moeda
  local fragN = res.cfg.frag


function p.calc(frame)
  local linhas = {}
    local args = frame:getParent() and frame:getParent().args or frame.args
  table.insert(linhas, "{| class=\"wikitable\" style=\"width:420px\"\n! colspan=2 | Totais\n|-")
  table.insert(linhas, string.format("| %s || %s\n|-", moeda, formatNumero(res.total.berry)))
  table.insert(linhas, string.format("| %s || %s\n|}", fragN, formatNumero(res.total.frag)))


     local op = (args.op or args.operacao or args.operacao or "soma"):lower()
  if res.cfg.mostrarPassos and #res.passos > 0 then
    local casas = args.casas or args.decimais or 2
     table.insert(linhas, "\n\n{| class=\"wikitable\" style=\"width:100%%\"\n! Passo !! Detalhe !! " .. moeda .. " !! " .. fragN .. "\n|-")
    local formato = (args.formato or args.locale or "pt"):lower() -- "pt" ou "en"
    for _, pss in ipairs(res.passos) do
      if pss.tipo == "estrela" then
        table.insert(linhas, string.format("| Estrela || %s: %d★ ➜ %d★ || %s || %s\n|-",
          pss.tier, pss.from, pss.to, formatNumero(pss.berry), formatNumero(pss.frag)))
      else
        table.insert(linhas, string.format("| Promoção || %s ➜ %s || %s || %s\n|-",
          pss.tier, pss.para, formatNumero(pss.berry), formatNumero(pss.frag)))
      end
    end
    table.insert(linhas, "|}")
  end


    local nums = collect_numbers(frame)
  return table.concat(linhas, "\n")
    local result = compute(nums, op)
end


     if result == nil then
-- =======================
        return "—" -- sem dados
-- ENTRADA PÚBLICA (Scribunto)
-- =======================
 
function p.calc(frame)
  local args = frame.args
  -- permitir também Template: wrapper com parent
  if frame:getParent() then
     for k, v in pairs(frame:getParent().args) do
      if v ~= '' and args[k] == nil then args[k] = v end
     end
     end
  end
  local res, err = calcular(args)
  if not res then
    return string.format("'''Erro:''' %s", err)
  end
  return renderResultado(res)
end


    return format_number(result, casas, formato)
-- =======================
-- UTIL: DEPURAÇÃO VIA CONSOLE (opcional)
-- =======================
-- Você pode rodar no console do Scribunto para testar:
-- =p._test{deTier="bronze", deEstrelas=2, paraTier="diamante", paraEstrelas=4, mostrarPassos="sim"}
function p._test(args)
  local res, err = calcular(args)
  if not res then return "Erro: " .. err end
  return renderResultado(res)
end
end


return p
return p

Edição das 19h47min de 26 de setembro de 2025

A documentação para este módulo pode ser criada em Módulo:Gb/doc

--[[
Module:CalculadoraTier

Calculadora de recursos (berry e fragmentos) para evoluir um personagem por Tiers e Estrelas.

✔ Como usar na wiki (Wikitext):
  {{#invoke:CalculadoraTier|calc
    |deTier=bronze
    |deEstrelas=2
    |paraTier=diamante
    |paraEstrelas=4
  }}

Parâmetros opcionais:
  |moeda=berry            -- nome exibido para a moeda (default: "berry")
  |frag=fragmentos        -- nome exibido para os fragmentos (default: "fragmentos")
  |mostrarPassos=sim      -- mostra o passo a passo (sim/não)

📌 Importante: SUBSTITUA os valores de custo abaixo (tabela DATA.custos) pelos valores reais do seu jogo.
A estrutura já está pronta para você só trocar os números.
]]

local p = {}

-- =======================
-- CONFIGURAÇÃO DE DADOS
-- =======================
-- Nome dos tiers em ordem de progressão.
-- Use chaves em minúsculas, sem acento, para evitar problemas.
local DATA = {
  tiers = { "bronze", "prata", "gold", "diamante" },

  -- Custo para subir 1 estrela DENTRO do mesmo tier.
  -- Para cada tier (chave), defina uma lista de 5 valores que representam o custo para ir:
  --   0 ➜ 1, 1 ➜ 2, 2 ➜ 3, 3 ➜ 4, 4 ➜ 5 estrelas.
  -- Exemplo abaixo usa NÚMEROS DE PLACEHOLDER! Troque pelos números reais.
  custos = {
    bronze = {
      estrelas = {
        { berry = 100, frag = 2 },  -- 0->1
        { berry = 150, frag = 3 },  -- 1->2
        { berry = 200, frag = 4 },  -- 2->3
        { berry = 250, frag = 5 },  -- 3->4
        { berry = 300, frag = 6 },  -- 4->5
      },
      -- Custo para PROMOVER do tier atual para o próximo (depois de 5★).
      promover = { berry = 500, frag = 10 },
    },

    prata = {
      estrelas = {
        { berry = 400, frag = 4 },
        { berry = 500, frag = 5 },
        { berry = 600, frag = 6 },
        { berry = 700, frag = 7 },
        { berry = 800, frag = 8 },
      },
      promover = { berry = 1200, frag = 16 },
    },

    gold = {
      estrelas = {
        { berry = 900,  frag = 9 },
        { berry = 1100, frag = 10 },
        { berry = 1300, frag = 11 },
        { berry = 1500, frag = 12 },
        { berry = 1700, frag = 13 },
      },
      promover = { berry = 2500, frag = 24 },
    },

    diamante = {
      estrelas = {
        { berry = 2000, frag = 15 },
        { berry = 2300, frag = 16 },
        { berry = 2600, frag = 17 },
        { berry = 2900, frag = 18 },
        { berry = 3200, frag = 19 },
      },
      -- Se diamante for o último tier, manter promover = nil ou {}.
      promover = nil,
    },
  },
}

-- =======================
-- FUNÇÕES AUXILIARES
-- =======================

local function norm(s)
  if type(s) ~= "string" then return s end
  s = s:lower()
  s = s:gsub("[áàâã]","a"):gsub("[éê]","e"):gsub("[í]","i"):gsub("[óôõ]","o"):gsub("[ú]","u"):gsub("ç","c")
  return s
end

local function findTierIndex(tier)
  local t = norm(tier)
  for i, name in ipairs(DATA.tiers) do
    if norm(name) == t then return i end
  end
  return nil
end

local function validateInputs(args)
  local deTier = args.deTier or args.de or args.fromTier
  local paraTier = args.paraTier or args.para or args.toTier
  local deEstrelas = tonumber(args.deEstrelas or args.estrelas or args.fromStars) or 0
  local paraEstrelas = tonumber(args.paraEstrelas or args.toStars) or 0

  if not deTier or not paraTier then
    return nil, "Informe os parâmetros deTier e paraTier."
  end

  local deIdx = findTierIndex(deTier)
  local paraIdx = findTierIndex(paraTier)
  if not deIdx then return nil, "Tier inicial inválido: " .. tostring(deTier) end
  if not paraIdx then return nil, "Tier de destino inválido: " .. tostring(paraTier) end

  if deEstrelas < 0 or deEstrelas > 5 then
    return nil, "deEstrelas deve estar entre 0 e 5."
  end
  if paraEstrelas < 0 or paraEstrelas > 5 then
    return nil, "paraEstrelas deve estar entre 0 e 5."
  end

  -- Também não permitir alvo "antes" da origem.
  if paraIdx < deIdx or (paraIdx == deIdx and paraEstrelas < deEstrelas) then
    return nil, "O destino deve ser maior que a origem."
  end

  return {
    deIdx = deIdx,
    paraIdx = paraIdx,
    deTier = DATA.tiers[deIdx],
    paraTier = DATA.tiers[paraIdx],
    deEstrelas = deEstrelas,
    paraEstrelas = paraEstrelas,
    moeda = args.moeda or "berry",
    frag = args.frag or "fragmentos",
    mostrarPassos = norm(tostring(args.mostrarPassos or "nao")) == "sim",
  }
end

-- Soma custos de estrelas dentro de um tier, do nível deEstrelas até (exclusivo) alvoEstrelas.
local function somarEstrelas(tierKey, deEstrelas, alvoEstrelas)
  local cust = DATA.custos[tierKey]
  local total = { berry = 0, frag = 0 }
  local passos = {}
  if not cust or not cust.estrelas then return total, passos end
  for s = deEstrelas, alvoEstrelas - 1 do
    local step = cust.estrelas[s + 1] -- +1 porque tabela começa no 1
    if step then
      total.berry = total.berry + (step.berry or 0)
      total.frag  = total.frag  + (step.frag or 0)
      table.insert(passos, { tipo = "estrela", from = s, to = s + 1, tier = tierKey, berry = step.berry or 0, frag = step.frag or 0 })
    end
  end
  return total, passos
end

-- =======================
-- LÓGICA PRINCIPAL
-- =======================

local function calcular(args)
  local ok, errOrCfg = pcall(validateInputs, args)
  if not ok then return nil, errOrCfg end
  if not errOrCfg then return nil, "Parâmetros inválidos." end
  if type(errOrCfg) == "string" then return nil, errOrCfg end
  local cfg = errOrCfg

  local total = { berry = 0, frag = 0 }
  local passos = {}

  for idx = cfg.deIdx, cfg.paraIdx do
    local tierKey = DATA.tiers[idx]

    -- Definir início e fim das estrelas para este tier
    local startStar = (idx == cfg.deIdx) and cfg.deEstrelas or 0
    local endStar   = (idx == cfg.paraIdx) and cfg.paraEstrelas or 5

    -- Somar estrelas do tier atual
    local parc, pss = somarEstrelas(tierKey, startStar, endStar)
    total.berry = total.berry + parc.berry
    total.frag  = total.frag  + parc.frag
    for _, x in ipairs(pss) do table.insert(passos, x) end

    -- Se não for o último tier de destino e atingiu 5 estrelas, pagar promoção
    if endStar == 5 and idx < cfg.paraIdx then
      local promo = (DATA.custos[tierKey] and DATA.custos[tierKey].promover) or { berry = 0, frag = 0 }
      total.berry = total.berry + (promo.berry or 0)
      total.frag  = total.frag  + (promo.frag or 0)
      table.insert(passos, { tipo = "promocao", tier = tierKey, para = DATA.tiers[idx + 1], berry = promo.berry or 0, frag = promo.frag or 0 })
    end
  end

  return { total = total, passos = passos, cfg = cfg }
end

-- =======================
-- RENDERIZAÇÃO (HTML simples / Wikitext)
-- =======================

local function formatNumero(n)
  -- separador de milhar simples
  local s = tostring(math.floor(n or 0))
  local k
  while true do
    s, k = s:gsub("^(%d+)(%d%d%d)", "%1.%2")
    if k == 0 then break end
  end
  return s
end

local function renderResultado(res)
  local moeda = res.cfg.moeda
  local fragN = res.cfg.frag

  local linhas = {}
  table.insert(linhas, "{| class=\"wikitable\" style=\"width:420px\"\n! colspan=2 | Totais\n|-")
  table.insert(linhas, string.format("| %s || %s\n|-", moeda, formatNumero(res.total.berry)))
  table.insert(linhas, string.format("| %s || %s\n|}", fragN, formatNumero(res.total.frag)))

  if res.cfg.mostrarPassos and #res.passos > 0 then
    table.insert(linhas, "\n\n{| class=\"wikitable\" style=\"width:100%%\"\n! Passo !! Detalhe !! " .. moeda .. " !! " .. fragN .. "\n|-")
    for _, pss in ipairs(res.passos) do
      if pss.tipo == "estrela" then
        table.insert(linhas, string.format("| Estrela || %s: %d★ ➜ %d★ || %s || %s\n|-",
          pss.tier, pss.from, pss.to, formatNumero(pss.berry), formatNumero(pss.frag)))
      else
        table.insert(linhas, string.format("| Promoção || %s ➜ %s || %s || %s\n|-",
          pss.tier, pss.para, formatNumero(pss.berry), formatNumero(pss.frag)))
      end
    end
    table.insert(linhas, "|}")
  end

  return table.concat(linhas, "\n")
end

-- =======================
-- ENTRADA PÚBLICA (Scribunto)
-- =======================

function p.calc(frame)
  local args = frame.args
  -- permitir também Template: wrapper com parent
  if frame:getParent() then
    for k, v in pairs(frame:getParent().args) do
      if v ~= '' and args[k] == nil then args[k] = v end
    end
  end

  local res, err = calcular(args)
  if not res then
    return string.format("'''Erro:''' %s", err)
  end
  return renderResultado(res)
end

-- =======================
-- UTIL: DEPURAÇÃO VIA CONSOLE (opcional)
-- =======================
-- Você pode rodar no console do Scribunto para testar:
-- =p._test{deTier="bronze", deEstrelas=2, paraTier="diamante", paraEstrelas=4, mostrarPassos="sim"}
function p._test(args)
  local res, err = calcular(args)
  if not res then return "Erro: " .. err end
  return renderResultado(res)
end

return p