> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tablas de botín y botín aleatorio en Economy v2

> Cree botines aleatorios ponderados en PlayFab Economy v2 usando Title Data, Azure Functions y ExecuteInventoryOperations para otorgar objetos de forma atómica.

<Info>
  Economy v2 ya está disponible con carácter general. Para soporte y comentarios, vaya al [Foro de PlayFab](https://community.playfab.com).
</Info>

Economy v1 (heredado) tenía una característica integrada de [tablas de botín](/services/playfab/economy-monetization/economy/tutorials/drop-tables) que le permitía definir distribuciones aleatorias ponderadas de objetos directamente en Game Manager. Economy v2 no incluye las tablas de botín como característica nativa, pero puede implementar el mismo comportamiento —y con más flexibilidad— mediante [Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview) en combinación con la API [ExecuteInventoryOperations](https://learn.microsoft.com/en-us/rest/api/playfab/economy/inventory/execute-inventory-operations).

Este tutorial le muestra cómo crear un sistema completo de botín aleatorio que:

* Define tablas de botín con grupos de objetos ponderados en [Title Data](/services/playfab/live-service-management/game-configuration/titledata).
* Usa una función de Azure para sortear objetos aleatorios de una tabla de botín.
* Resta de forma atómica un token de contenedor y otorga los objetos seleccionados aleatoriamente en una sola llamada.

## Cómo funciona

1. **Definir tablas de botín en Title Data**: almacene las configuraciones de sus tablas de botín (identificadores de objetos, pesos, cantidades) como JSON en Title Data para poder actualizarlas sin volver a implementar código.
2. **Representar los contenedores como moneda virtual**: use el mismo [patrón de token de contenedor](/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents) que los contenedores de contenido fijo: una moneda virtual que actúa como una caja de botín sin abrir.
3. **Llamar a una función de Azure para abrir el contenedor**: la función lee la tabla de botín, ejecuta una selección aleatoria ponderada y luego llama a `ExecuteInventoryOperations` para restar de forma atómica 1 token de contenedor y agregar los objetos seleccionados aleatoriamente.

## Requisitos previos

* Una [cuenta de desarrollador de PlayFab](https://developer.playfab.com/en-us/sign-up).
* Un título creado en [Game Manager](https://developer.playfab.com/).
* Elementos de catálogo publicados que pueda otorgar como recompensas de botín.
* Una moneda virtual que represente el contenedor (consulte [Contenedores con contenido fijo](/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents#step-1-create-a-virtual-currency-for-the-container) para ver las instrucciones de configuración).
* Un proyecto de [Azure Functions](/services/playfab/live-service-management/service-gateway/automation/cloudscript-af/quickstart) conectado a su título de PlayFab.

## Paso 1: definir tablas de botín en Title Data

Almacene las definiciones de sus tablas de botín en [Title Data](/services/playfab/live-service-management/game-configuration/titledata) para que los diseñadores puedan actualizar las tasas de caída sin cambios de código.

### Estructura de ejemplo de una tabla de botín

Cree una clave de Title Data llamada `LootTables` con un valor JSON:

```json theme={null}
{
  "TreasureChest": {
    "Rolls": 1,
    "Items": [
      { "ItemId": "{{CommonSwordID}}", "Weight": 1000, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{UncommonSwordID}}", "Weight": 100, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{RareSwordID}}", "Weight": 10, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{LegendarySwordID}}", "Weight": 1, "MinQuantity": 1, "MaxQuantity": 1 }
    ]
  },
  "SupplyCrate": {
    "Rolls": 3,
    "Items": [
      { "ItemId": "{{HealthPotionID}}", "Weight": 50, "MinQuantity": 1, "MaxQuantity": 5 },
      { "ItemId": "{{ManaPotionID}}", "Weight": 50, "MinQuantity": 1, "MaxQuantity": 3 },
      { "ItemId": "{{GoldCurrencyID}}", "Weight": 80, "MinQuantity": 10, "MaxQuantity": 100 },
      { "ItemId": "{{RareGemID}}", "Weight": 5, "MinQuantity": 1, "MaxQuantity": 1 }
    ]
  }
}
```

Cada tabla de botín incluye las siguientes propiedades:

| Propiedad                     | Descripción                                                                             |
| ----------------------------- | --------------------------------------------------------------------------------------- |
| **Rolls**                     | Cuántas veces se sortea en la tabla cuando se abre el contenedor.                       |
| **Items**                     | El grupo de recompensas posibles, cada una con un peso y un intervalo de cantidad.      |
| **Weight**                    | Probabilidad relativa. Un peso mayor significa que es más probable que el objeto caiga. |
| **MinQuantity / MaxQuantity** | Intervalo de cuántas unidades de ese objeto se otorgan por sorteo.                      |

### Establecer Title Data

#### Game Manager

1. Vaya a **Content** > **Title Data**.
2. Cree una clave llamada `LootTables`.
3. Pegue su configuración JSON de tablas de botín como valor.
4. Seleccione **Save**.

#### API

Use [SetTitleData](https://learn.microsoft.com/en-us/rest/api/playfab/server/title-wide-data-management/set-title-data):

```json theme={null}
{
  "Key": "LootTables",
  "Value": "{\"TreasureChest\":{\"Rolls\":1,\"Items\":[...]}}"
}
```

***

## Paso 2: crear la función de Azure

Cree una función de Azure a la que llame el cliente de su juego cuando un jugador quiera abrir un contenedor. La función:

1. Comprueba que el jugador tiene al menos un token de contenedor.
2. Lee la tabla de botín desde Title Data.
3. Ejecuta la selección aleatoria ponderada.
4. Llama a `ExecuteInventoryOperations` para restar de forma atómica el token y otorgar las recompensas.

### Modelos de datos

```csharp theme={null}
public class OpenContainerRequest
{
    public string LootTableId { get; set; }  // e.g., "TreasureChest"
}

public class LootTable
{
    public int Rolls { get; set; }
    public List<LootTableEntry> Items { get; set; }
}

public class LootTableEntry
{
    public string ItemId { get; set; }
    public int Weight { get; set; }
    public int MinQuantity { get; set; }
    public int MaxQuantity { get; set; }
}
```

### Selección aleatoria ponderada

```csharp theme={null}
public static LootTableEntry RollOnTable(LootTable table, Random rng)
{
    int totalWeight = table.Items.Sum(e => e.Weight);
    int roll = rng.Next(0, totalWeight);

    int cumulative = 0;
    foreach (var entry in table.Items)
    {
        cumulative += entry.Weight;
        if (roll < cumulative)
            return entry;
    }

    // Fallback (shouldn't reach here)
    return table.Items.Last();
}
```

### Función de Azure completa

```csharp theme={null}
[FunctionName("OpenLootContainer")]
public static async Task<dynamic> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    // 1. Parse the PlayFab context and request
    var context = JsonConvert.DeserializeObject<FunctionExecutionContext<dynamic>>(
        await req.ReadAsStringAsync());
    var args = context.FunctionArgument != null
        ? JsonConvert.DeserializeObject<OpenContainerRequest>(
            context.FunctionArgument.ToString())
        : null;

    if (args == null || string.IsNullOrEmpty(args.LootTableId))
        return new { Error = "LootTableId is required." };

    var playerEntity = context.CallerEntityProfile.Entity;
    var settings = new PlayFabApiSettings
    {
        TitleId = context.TitleAuthenticationContext.Id,
        DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY")
    };
    var authContext = new PlayFabAuthenticationContext
    {
        EntityToken = context.TitleAuthenticationContext.EntityToken
    };

    var serverApi = new PlayFabServerInstanceAPI(settings);
    var economyApi = new PlayFabEconomyInstanceAPI(settings, authContext);

    // 2. Read the loot table from Title Data
    var titleDataResult = await serverApi.GetTitleDataAsync(new GetTitleDataRequest
    {
        Keys = new List<string> { "LootTables" }
    });

    if (!titleDataResult.Result.Data.ContainsKey("LootTables"))
        return new { Error = "LootTables not found in Title Data." };

    var allTables = JsonConvert.DeserializeObject<Dictionary<string, LootTable>>(
        titleDataResult.Result.Data["LootTables"]);

    if (!allTables.ContainsKey(args.LootTableId))
        return new { Error = $"Loot table '{args.LootTableId}' not found." };

    var lootTable = allTables[args.LootTableId];

    // 3. Roll for rewards
    var rng = new Random();
    var rewards = new Dictionary<string, int>(); // ItemId -> total quantity

    for (int i = 0; i < lootTable.Rolls; i++)
    {
        var entry = RollOnTable(lootTable, rng);
        int quantity = rng.Next(entry.MinQuantity, entry.MaxQuantity + 1);

        if (rewards.ContainsKey(entry.ItemId))
            rewards[entry.ItemId] += quantity;
        else
            rewards[entry.ItemId] = quantity;
    }

    // 4. Build the atomic operation: subtract 1 container token + add rewards
    var operations = new List<InventoryOperation>();

    // Subtract the container token
    // The container currency ID must match the LootTableId mapping
    // Store this mapping in Title Data or use a naming convention
    operations.Add(new InventoryOperation
    {
        Subtract = new SubtractInventoryItemsOperation
        {
            Item = new InventoryItemReference
            {
                Id = GetContainerCurrencyId(args.LootTableId) // Your mapping logic
            },
            Amount = 1
        }
    });

    // Add each reward
    foreach (var reward in rewards)
    {
        operations.Add(new InventoryOperation
        {
            Add = new AddInventoryItemsOperation
            {
                Item = new InventoryItemReference { Id = reward.Key },
                Amount = reward.Value
            }
        });
    }

    // 5. Execute atomically
    var executeResult = await economyApi.ExecuteInventoryOperationsAsync(
        new ExecuteInventoryOperationsRequest
        {
            Entity = new EntityKey
            {
                Id = playerEntity.Id,
                Type = playerEntity.Type
            },
            Operations = operations,
            IdempotencyId = Guid.NewGuid().ToString()
        });

    if (executeResult.Error != null)
    {
        log.LogError($"Failed to execute operations: {executeResult.Error.ErrorMessage}");
        return new { Error = executeResult.Error.ErrorMessage };
    }

    log.LogInformation($"Player {playerEntity.Id} opened {args.LootTableId}, received: " +
        string.Join(", ", rewards.Select(r => $"{r.Value}x {r.Key}")));

    return new { Rewards = rewards, TransactionIds = executeResult.Result.TransactionIds };
}
```

<Note>
  La función auxiliar `GetContainerCurrencyId` asigna el nombre de una tabla de botín al identificador de la moneda de contenedor correspondiente. Puede almacenar esta asignación en Title Data junto con las tablas de botín, o usar una convención de nomenclatura.
</Note>

## Paso 3: otorgar tokens de contenedor a los jugadores

Otorgue tokens de contenedor a los jugadores mediante el propio juego, compras en la tienda o recompensas. Use el mismo enfoque descrito en [Contenedores con contenido fijo](/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents#step-3-grant-container-tokens-to-players).

## Paso 4: llamar a la función desde el cliente del juego

Cuando el jugador quiera abrir un contenedor, llame a la función de Azure mediante [ExecuteFunction](https://learn.microsoft.com/en-us/rest/api/playfab/cloudscript/server-side-cloud-script/execute-function):

```json theme={null}
{
    "FunctionName": "OpenLootContainer",
    "FunctionParameter": {
        "LootTableId": "TreasureChest"
    },
    "GeneratePlayStreamEvent": true
}
```

La respuesta contiene las recompensas seleccionadas aleatoriamente:

```json theme={null}
{
    "FunctionResult": {
        "Rewards": {
            "{{UncommonSwordID}}": 1
        },
        "TransactionIds": ["txn-abc-123"]
    }
}
```

## Patrones de diseño de tablas de botín

### Niveles de rareza

El clásico sistema de rareza con pesos que decrecen exponencialmente:

```json theme={null}
{
  "Items": [
    { "ItemId": "CommonItem",     "Weight": 1000, "MinQuantity": 1, "MaxQuantity": 1 },
    { "ItemId": "UncommonItem",   "Weight": 100,  "MinQuantity": 1, "MaxQuantity": 1 },
    { "ItemId": "RareItem",       "Weight": 10,   "MinQuantity": 1, "MaxQuantity": 1 },
    { "ItemId": "LegendaryItem",  "Weight": 1,    "MinQuantity": 1, "MaxQuantity": 1 }
  ]
}
```

Probabilidades de caída:

* Común: \~90,0 %
* Poco común: \~9,0 %
* Raro: \~0,9 %
* Legendario: \~0,09 %

### Sorteo múltiple con recompensas mixtas

Una caja de suministros que da tres objetos aleatorios de un grupo mixto:

```json theme={null}
{
  "Rolls": 3,
  "Items": [
    { "ItemId": "{{HealthPotionID}}", "Weight": 40, "MinQuantity": 1, "MaxQuantity": 3 },
    { "ItemId": "{{ManaPotionID}}",   "Weight": 40, "MinQuantity": 1, "MaxQuantity": 3 },
    { "ItemId": "{{GoldCurrencyID}}", "Weight": 30, "MinQuantity": 10, "MaxQuantity": 50 },
    { "ItemId": "{{RareGemID}}",      "Weight": 5,  "MinQuantity": 1, "MaxQuantity": 1 }
  ]
}
```

### Garantizado + aleatorio (sistema de compasión)

Para un patrón de "raro o mejor garantizado cada 10 aperturas", registre el recuento de aperturas del jugador en [Display Properties](/services/playfab/economy-monetization/economy-v2/inventory#display-properties) o en [Player Data](/services/playfab/player-progression/player-data/quickstart), y anule la selección aleatoria en la función de Azure cuando se alcance el umbral.

### Tablas de botín anidadas

Una tabla de botín puede hacer referencia a otra tabla de botín por su identificador. Su función de Azure resuelve la referencia de forma recursiva:

```json theme={null}
{
  "BossDropTable": {
    "Rolls": 2,
    "Items": [
      { "ItemId": "TABLE:WeaponDropTable", "Weight": 50, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "TABLE:ArmorDropTable",  "Weight": 30, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{GoldCurrencyID}}",    "Weight": 80, "MinQuantity": 50, "MaxQuantity": 200 }
    ]
  }
}
```

La función comprueba si un `ItemId` empieza por `TABLE:` y, si es así, sortea en la tabla referenciada en lugar de otorgar el objeto directamente.

## Consideraciones de seguridad

* **Ejecute siempre la lógica de botín en el lado del servidor** en una función de Azure. Nunca confíe en el cliente para determinar qué objetos se sortearon. Este enfoque evita manipulaciones.
* **Use `IdempotencyId`** en la llamada a `ExecuteInventoryOperations` para evitar otorgamientos duplicados si la función se reintenta. Para obtener más información, consulte [Transacciones idempotentes y reintentos](/services/playfab/economy-monetization/economy-v2/tutorials/idempotent-transactions-and-retries).
* **Valide la propiedad del contenedor** comprobando el inventario del jugador antes de restar. La operación `Subtract` de `ExecuteInventoryOperations` falla si el jugador no tiene suficientes tokens, lo que revierte todo el lote.
* **Registre todos los sorteos** con fines de auditoría. El valor devuelto de la función y los eventos de PlayStream proporcionan un rastro de lo que recibió cada jugador.

## Consulte también

* [Contenedores con contenido fijo](/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents)
* [Referencia de la API ExecuteInventoryOperations](https://learn.microsoft.com/en-us/rest/api/playfab/economy/inventory/execute-inventory-operations)
* [Transacciones idempotentes y reintentos](/services/playfab/economy-monetization/economy-v2/tutorials/idempotent-transactions-and-retries)
* [Title Data](/services/playfab/live-service-management/game-configuration/titledata)
* [Inicio rápido de Azure Functions](/services/playfab/live-service-management/service-gateway/automation/cloudscript-af/quickstart)
* [Tablas de botín (Economy heredado)](/services/playfab/economy-monetization/economy/tutorials/drop-tables)
* [Información general de Economy v2](/services/playfab/economy-monetization/economy-v2/overview)


## Related topics

- [Tablas de botín en Economy (heredada)](/es/services/playfab/economy-monetization/economy/tutorials/drop-tables.md)
- [Información general de Economy v2](/es/services/playfab/economy-monetization/economy-v2/overview.md)
- [Contenedores con contenido fijo en Economy v2](/es/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents.md)
- [Tiendas y ventas en Economy (heredado)](/es/services/playfab/economy-monetization/economy/tutorials/stores-and-sales.md)
- [Catálogos (Economía heredada)](/es/services/playfab/economy-monetization/economy/items/catalogs.md)
