> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/all-out-docs/docs-es/datos-y-persistencia/save.md).

# Sistema de guardado

El sistema de guardado es un simple **almacén de pares clave-valor**. Le das una clave de cadena (como `"xp"`), y almacena un valor que seguirá ahí la próxima vez que el jugador se conecte.

Hay dos tipos de datos de guardado:

* **Guardado del jugador**: datos personales de un jugador (XP, mejoras, ajustes, inventario, etc.)
* **Guardado global del juego**: compartido por todos en el juego (récords del mundo, ajustes del servidor, contadores globales, etc.)

{% hint style="warning" %}
No uses el sistema de guardado para almacenar compras que los jugadores hicieron con chispas. Usa el [API de compras/productos](/all-out-docs/docs-es/conceptos-basicos-del-motor/purchasing-product-apis.md) en su lugar.
{% endhint %}

### Inicio rápido (guardar + cargar una estadística)

El patrón más común es:

* Carga los valores guardados en `ao_start`
* Guarda de nuevo cada vez que cambie el valor

```go
Player :: class : Player_Base {
    xp: s64;
    level: s64;

    ao_start :: method() {
        // Los valores predeterminados se usan para jugadores nuevos
        xp    = Save.get_int(this, "xp", 0);
        level = Save.get_int(this, "level", 1);
    }
}

add_xp :: proc(player: Player, amount: s64) {
    player.xp += amount;

    // ... tu lógica de subida de nivel aquí ...

    // Guarda inmediatamente cuando cambie
    Save.set_int(player, "xp", player.xp);
    Save.set_int(player, "level", player.level);
}
```

### Guardado del jugador (datos por jugador)

El guardado del jugador está limitado a un solo jugador. Cada jugador tiene sus propios datos aislados de clave/valor.

Tipos compatibles:

* **Cadena**: `Save.set_string` / `Save.get_string`
* **Entero (`s64`)**: `Save.set_int` / `Save.get_int` (nota: actualmente los valores se truncan internamente a 32 bits)
* **Flotante (`f64`)**: `Save.set_f64` / `Save.get_f64`
* **JSON (avanzado)**: `Save.set_json` / `Save.try_get_json`

```go
// Preferencias
Save.set_string(player, "selected_skin", "knight");
music_volume := Save.get_f64(player, "music_volume", 0.8);

// Eliminar una clave (útil al migrar/eliminar datos antiguos)
Save.delete_key(player, "old_key_name");
```

{% hint style="info" %}
Proporciona siempre un valor predeterminado sensato al leer. Los jugadores nuevos aún no tendrán claves, y `get_*` devolverá tu valor predeterminado.
{% endhint %}

### Guardar datos “más grandes” (JSON)

Si tienes un pequeño conjunto de campos (como progreso + elementos desbloqueados), a menudo es mejor guardarlo como un único bloque JSON.

Solo los campos marcados con `@ao_serialize` se guardan.

```go
Player_Progress :: class {
    version: s64 = 1 @ao_serialize;
    max_health: s64 = 100 @ao_serialize;
    unlocked_skins: [..]string @ao_serialize;
}

save_progress :: proc(player: Player, progress: Player_Progress) {
    Save.set_json(player, "progress", ref progress);
}

load_progress :: proc(player: Player) -> Player_Progress {
    progress := new(Player_Progress);
    if !Save.try_get_json(player, "progress", ref progress) {
        // La clave faltante (o el análisis fallido) ya tiene los valores predeterminados de la clase.
    }
    return progress;
}
```

{% hint style="info" %}
`Save.try_get_json` devuelve `false` si la clave no existe o la cadena JSON está mal formada. Asigna memoria para los datos de la clase antes de cargar, porque la ruta falsa no la asigna. Los campos faltantes conservan los valores predeterminados de su clase, y los campos desconocidos se ignoran.
{% endhint %}

### Versionado del guardado (migrar datos antiguos de forma segura)

Si alguna vez cambias el formato de guardado, conserva una `versión` clave y migra los guardados antiguos hacia adelante.

```go
Player :: class : Player_Base {
    hp: f64;

    ao_start :: method() {
        save_version := Save.get_int(this, "version", 0);

        if save_version < 6 {
            // Ejemplo de migración: hp antes era un entero, ahora es un flotante
            save_version = 6;
            old_hp := Save.get_int(this, "hp", 100);
            Save.delete_key(this, "hp");
            Save.set_f64(this, "hp", old_hp.(f64));
        }

        Save.set_int(this, "version", save_version);

        // Carga el formato actual
        hp = Save.get_f64(this, "hp", 100);
    }
}
```

### Guardado global del juego (compartido por todos)

El guardado global del juego se comparte en todo el juego, no por jugador.

```go
// Titular del récord global
Save.set_game_string("world_record_holder", player.get_username());
holder := Save.get_game_string("world_record_holder", "nadie todavía");

// Contadores globales (incremento atómico, seguro cuando muchos jugadores lo actualizan)
Save.increment_game_int("total_games_played", 1);
total := Save.get_game_int("total_games_played", 0);
```

{% hint style="info" %}
Usa `Save.increment_game_int` para contadores que varios jugadores podrían actualizar al mismo tiempo (bajas, uniones, rondas jugadas, etc.). Su `optimistic_update` parámetro tiene el valor predeterminado de `true`, así que el valor local en caché cambia inmediatamente.
{% endhint %}

### Patrones comunes

#### Booleanos

Guarda los booleanos como `0/1`:

```go
// Guardar
Save.set_int(player, "tutorial_complete", tutorial_complete ? 1 : 0);

// Cargar
tutorial_complete = Save.get_int(player, "tutorial_complete", 0) != 0;
```

#### Nomenclatura de claves

Las claves son solo cadenas, así que elige nombres que no entren en conflicto más adelante:

* `"xp"`, `"level"`, `"selected_skin"`
* `"tycoon.cash"`, `"tycoon.upgrades.mouth_level"`

Si tienes varios juegos conectados mediante game parenting (hub + minijuegos), consulta [Productos/datos entre juegos](/all-out-docs/docs-es/datos-y-persistencia/cross-game-products-data.md) para ver cómo se pueden compartir los datos guardados.

### APIs adicionales

```go
Save :: struct {
    // Eliminar claves
    delete_key      :: proc(player: Player, key: string);
    delete_all_keys :: proc(player: Player);

    // Enumerar claves
    get_all_keys :: proc(player: Player) -> []string;

    // Enumeración global
    get_all_game_strings :: proc() -> []Save_Game_String;
    get_all_game_ints    :: proc() -> []Save_Game_Int;
    get_all_game_keys    :: proc() -> []Save_Game_Key;

    // Datos ordenados/clasificados (tablas de clasificación)
    ordered_set     :: proc(document: string, key: string, value: f64);
    ordered_get     :: proc(document: string, key: string, default: f64,
                            userdata: Object,
                            callback: proc(entry: Ordered_Save_Entry, userdata: Object));
    ordered_get_all :: proc(document: string, offset: s64, limit: s64,
                            userdata: Object,
                            callback: proc(entries: []Ordered_Save_Entry, userdata: Object));
}
```

`ordered_get` y `ordered_get_all` son asíncronos y se ejecutan en el servidor. Llámalos desde la ruta normal compartida del juego; no añadas una `Game.is_server()` protección.

Los arreglos de devolución de llamada solo son válidos durante la devolución de llamada, así que copia cualquier entrada que necesites conservar:

```go
Ranking_State :: class : Component {
    entries: [..]Ordered_Save_Entry;

    refresh :: method() {
        Save.ordered_get_all(
            "weekly_score",
            0,
            100,
            this,
            proc(results: []Ordered_Save_Entry, userdata: Object) {
                state := userdata.(Ranking_State);
                state.entries.clear();
                for result: results {
                    state.entries.append(result);
                }
            }
        );
    }
}
```

Cada solicitud completada `ordered_get` invoca su devolución de llamada una vez. Una entrada faltante o un fallo de la solicitud devuelve el valor predeterminado proporcionado. Cada solicitud completada `ordered_get_all` también invoca su devolución de llamada una vez; un resultado vacío o un fallo de la solicitud proporciona un arreglo vacío. Los fallos de la solicitud se registran.
