diff --git a/_layouts/default.html b/_layouts/default.html
index 84ce616..2a3bcc3 100644
--- a/_layouts/default.html
+++ b/_layouts/default.html
@@ -5,7 +5,7 @@
{% assign lang = site.data.lang[page.lang] %}
- {% assign clean_path = page.url | replace: '/ru/', '/' | replace: '/en/', '/' %}
+ {% assign clean_path = page.url | replace: '/ru/', '/' | replace: '/en/', '/' | replace: '/es/', '/' %}
{% assign page_url = site.domains[page.lang] | append: clean_path %}
{%- comment -%}
@@ -32,8 +32,16 @@
{% assign page_description = content | strip_html | normalize_whitespace | strip | truncate: 160 %}
{% endif %}
- {% if page.lang == 'ru' %}{% assign og_locale = 'ru_RU' %}{% else %}{% assign og_locale = 'en_US' %}{% endif %}
- {% assign og_image = site.domains[page.lang] | append: '/images/gameap.png' %}
+ {% if page.lang == 'ru' %}{% assign og_locale = 'ru_RU' %}{% elsif page.lang == 'es' %}{% assign og_locale = 'es_ES' %}{% else %}{% assign og_locale = 'en_US' %}{% endif %}
+
+ {%- comment -%}
+ Images are served from the host root, not from under the language path.
+ The Spanish site lives in a subdirectory of the English domain, so its
+ assets resolve against that domain — `domains.es` would point the crawler
+ at /es/images/, where nothing is published.
+ {%- endcomment -%}
+ {% if page.lang == 'es' %}{% assign image_domain = site.domains.en %}{% else %}{% assign image_domain = site.domains[page.lang] %}{% endif %}
+ {% assign og_image = image_domain | append: '/images/gameap.png' %}
{{ page_title }}
@@ -47,6 +55,7 @@
+
diff --git a/css/main.css b/css/main.css
index 4f6ad47..4e714bc 100755
--- a/css/main.css
+++ b/css/main.css
@@ -205,6 +205,81 @@ pre:hover .code-copy-btn {
color: #0366d6;
}
+.lang-menu {
+ position: relative;
+}
+
+.lang-menu summary {
+ list-style: none;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 13px;
+ color: #24292e;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ padding: 4px 8px;
+ cursor: pointer;
+ user-select: none;
+}
+
+.lang-menu summary::-webkit-details-marker {
+ display: none;
+}
+
+.lang-menu summary:hover,
+.lang-menu[open] summary {
+ color: #0366d6;
+ border-color: #0366d6;
+}
+
+.lang-menu[open] .chevron {
+ transform: rotate(180deg);
+}
+
+.lang-menu ul {
+ position: absolute;
+ right: 0;
+ top: calc(100% + 6px);
+ margin: 0;
+ padding: 4px;
+ list-style: none;
+ min-width: 140px;
+ background: #fff;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+ z-index: 1030;
+}
+
+.lang-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 6px 10px;
+ border-radius: 6px;
+ font-size: 14px;
+ color: #24292e;
+ text-decoration: none;
+ white-space: nowrap;
+}
+
+a.lang-item:hover {
+ background: #f6f8fa;
+ color: #24292e;
+ text-decoration: none;
+}
+
+.lang-item.is-current {
+ color: #999;
+}
+
+.lang-item.is-current::after {
+ content: "✓";
+ color: #1a7f37;
+}
+
#footer {
margin: 20px 0;
font-size: 0.85em;
diff --git a/en/daemon/process_managers.md b/en/daemon/process_managers.md
index c788aa6..a85784a 100644
--- a/en/daemon/process_managers.md
+++ b/en/daemon/process_managers.md
@@ -24,8 +24,8 @@ process_manager:
```
**Available managers:**
-- Linux: `systemd` (default), `docker`, `podman`, `tmux`
-- Windows: `shawl` (default), `winsw`
+- Linux: `systemd` (default), `docker`, `podman`, `tmux`, `simple`
+- Windows: `shawl` (default), `winsw`, `simple`
## Linux
@@ -216,3 +216,38 @@ WinSW does not require additional configuration.
process_manager:
name: winsw
```
+
+## Linux and Windows
+
+### Simple
+
+Simple does not delegate to any system service. It starts the game server through the command
+templates configured for the dedicated server and knows nothing about the process beyond what those
+commands report.
+
+On Linux the daemon falls back to it automatically when neither systemd nor tmux is available.
+On Windows it can be selected explicitly.
+
+| Feature | |
+|----------------------------------------|----|
+| Start, stop, restart servers | ✅ |
+| Statistics | ❌ |
+| Resource limits (CPU / RAM) | ❌ |
+| Console reading | ✅ |
+| Sending commands to console | ✅ |
+| Isolation | ❌ |
+
+The daemon does not track the process ID, so only the "server is running" flag reaches the panel —
+there are no CPU, memory or network charts, and resource limits cannot be applied. Choose Simple
+only when nothing else is available on the system.
+
+#### Simple Configuration
+
+Simple does not require additional configuration.
+
+##### Configuration Example
+
+```yaml
+process_manager:
+ name: simple
+```
diff --git a/en/database.md b/en/database.md
index ffbd050..7497760 100644
--- a/en/database.md
+++ b/en/database.md
@@ -71,11 +71,15 @@ The directory with the database file must be writable by the user the panel runs
```dotenv
DATABASE_DRIVER=inmemory
+DATABASE_URL=inmemory
```
Data is kept in RAM only and is lost on restart. Meant for tests; not suitable for a production
installation.
+`DATABASE_URL` has to be set even here: the panel checks it for emptiness before it learns the
+driver, and will not start without it. The value itself is not used.
+
## Migrations
The panel applies migrations itself at startup — there is no separate command. The schema
diff --git a/en/gameap_configure/games.md b/en/gameap_configure/games.md
index f91b9d8..cabb1ce 100644
--- a/en/gameap_configure/games.md
+++ b/en/gameap_configure/games.md
@@ -107,7 +107,7 @@ Example game server working directory `/srv/gameap/servers/example-server`
Each game can have many mods, each has its own features, settings,
startup parameters, configuration files, etc.
-To add a new game, go to **"Administration"** → **"Games"**, then select
+To add a new mod, go to **"Administration"** → **"Games"**, then select
**"Add Mod"**
### Fields
diff --git a/en/get_started.md b/en/get_started.md
index 06005e5..21e98ab 100644
--- a/en/get_started.md
+++ b/en/get_started.md
@@ -28,7 +28,7 @@ bash <(curl -s https://gameap.com/install.sh) --with-daemon
## Adding a Dedicated Server
Add a new dedicated server (VDS) on which you will then install game servers.
-After installing the panel, log in and select **"Administration"** **"Dedicated servers"** → **"Create"** from the menu. After
+After installing the panel, log in and select **"Administration"** → **"Dedicated servers"** → **"Create"** from the menu. After
that, a window with instructions will open, follow them.

diff --git a/en/https.md b/en/https.md
index c865b1e..5faf7fc 100644
--- a/en/https.md
+++ b/en/https.md
@@ -210,6 +210,11 @@ Certificates, the ACME account key, and housekeeping data are stored in the `ACM
directory inside the panel's file storage. With `FILES_DRIVER=s3` they end up in S3 — this is
what lets several panel instances share one certificate.
+Shared storage alone is not enough for several instances: the lock that keeps them from requesting
+a certificate at the same time works through Redis and is active only with `CACHE_DRIVER=redis`.
+With the in-memory cache the lock is local to each instance, and they will get in each other's way.
+See [Multiple Panel Instances](/en/multi_instance.html).
+
## Certificate status
The current status is available to an administrator at `GET /api/admin/letsencrypt/status`:
diff --git a/en/install/install_docker.md b/en/install/install_docker.md
index 26fccd6..612098a 100644
--- a/en/install/install_docker.md
+++ b/en/install/install_docker.md
@@ -20,8 +20,8 @@ docker run -d \
-p 31718:31718 \
-e DATABASE_DRIVER=sqlite \
-e 'DATABASE_URL=file:/var/lib/gameap/db.sqlite?_busy_timeout=5000&_journal_mode=WAL&cache=shared' \
- -e AUTH_SECRET=$(openssl rand -hex 16) \
- -e ENCRYPTION_KEY=$(openssl rand -hex 16) \
+ -e AUTH_SECRET=$(openssl rand -base64 24) \
+ -e ENCRYPTION_KEY=$(openssl rand -hex 32) \
-e GRPC_EXTERNAL_HOST=panel.example.com \
-v gameap-data:/var/lib/gameap \
gameap/gameap:latest
diff --git a/en/install/install_on_linux.md b/en/install/install_on_linux.md
index 5f0530c..aa504a0 100644
--- a/en/install/install_on_linux.md
+++ b/en/install/install_on_linux.md
@@ -76,11 +76,16 @@ additional input from you.
```shell
bash <(curl -s https://gameap.com/install.sh) \
--non-interactive \
- --host=127.0.0.1 \
+ --host=panel.example.com \
--port=8025 \
--database=sqlite
```
+> Put the address the panel is reached at into `--host` — a domain name or the server's external
+> IP. It becomes `HTTP_HOST`, and the panel derives the listening address from it: with
+> `--host=127.0.0.1` the HTTP and gRPC listeners come up on loopback only, and neither remote
+> administrators nor daemons on other machines will be able to connect.
+
Main flags:
| Flag | Purpose |
diff --git a/en/plugins/development.md b/en/plugins/development.md
index a338d86..d2d0367 100644
--- a/en/plugins/development.md
+++ b/en/plugins/development.md
@@ -47,7 +47,7 @@ In the Rust SDK the interface is represented by the `Plugin` trait with neutral
| `author` | Author |
| `license` | License (for example, `MIT`) |
| `homepage` | Link to the plugin page |
-| `required_permissions` | Declared permissions (not checked in the current version of the panel) |
+| `required_permissions` | Permissions the plugin declares: recorded on install and checked on every host function call (see below) |
| `api_version` | Plugin API version, must be `"1"` |
**Requirements for `id`.** Use a stable identifier made of base32 alphabet characters `a-z2-7`, without hyphens. The panel normalizes the id: a string with hyphens or other characters is replaced by a hash, which breaks the `/api/plugins/{id}/...` and `/plugins/{id}/...` paths. Avoid purely numeric ids as well: such an identifier is treated as a decimal numeric ID (id parsing first tries to parse the string as a number). Examples of valid ids from real plugins: `hexeditor4jm2`, `ezvdsxmlu6fbk`, `dshdabjp2l73a`.
@@ -100,16 +100,24 @@ All calls from a plugin to the panel and the outside world go through host funct
| `gameap-gamemods` | `find_game_mods`, `get_game_mod` | Game mods |
| `gameap-daemontasks` | `find_daemon_tasks`, `create_daemon_task` | Daemon tasks |
| `gameap-serversettings` | `find_server_settings`, `save_server_setting` | Game server settings |
-| `gameap-nodefs` | `read_dir`, `mk_dir`, `copy`, `move`, `download`, `upload`, `remove`, `get_file_info`, `chmod` | File operations on a node |
+| `gameap-nodefs` | `read_dir`, `mk_dir`, `copy`, `move`, `download`, `upload`, `remove`, `get_file_info`, `chmod`, `hash`, `create_archive`, `extract_archive`, `start_create_archive`, `start_extract_archive`, `cancel_archive`, `get_archive_operation` | File operations on a node |
| `gameap-nodecmd` | `execute_command` | Executing a command on a node |
Details:
* `gameap-http` proxies requests through the panel with SSRF protection: by default only the `https` scheme is allowed, private and service IPs are blocked, and the response body is limited to 10 MB. The policy is configured with the `PLUGIN_HTTP_*` environment variables (see [Installation and management](/en/plugins/management.html)).
* `gameap-storage` is the plugin's persistent storage, isolated by `plugin_id`, with optional binding of records to an entity (`entity_type`, `entity_id`). Use it for plugin settings: the separate configuration mechanism (`config` in `InitializeRequest`) is not used in the current version of the panel.
-* `gameap-nodefs` and `gameap-nodecmd` work with files and commands on the dedicated server (node) through GameAP Daemon.
+* `gameap-nodefs` and `gameap-nodecmd` work with files and commands on the dedicated server (node) through GameAP Daemon. Reads (`read_dir`, `download`, `get_file_info`, `hash`, `get_archive_operation`) need the `files_read` permission; everything that writes — `chmod` and the archive functions included — needs `files`, which includes `files_read`.
+* `gameap-nodefs.download` without `offset`/`length` returns the whole file in one message and is capped by `PLUGIN_NODEFS_MAX_INLINE`; a larger file is refused with an error naming both sizes. Naming an `offset`/`length` window makes such a file readable one piece at a time: only the window has to fit the cap, and a `length` above the cap is refused rather than quietly clamped. The answer echoes `offset` and carries `total_size`, and `length: 0` together with an `offset` reads as much as the cap allows — so a paging loop advances by the length of `content` until it reaches `total_size`. Reading at or past the end of the file is not an error: the answer is empty `content`.
-**Important:** host functions run with the panel's own privileges, without additional checks. That is why installing plugins is entrusted to administrators only — install plugins only from sources you trust.
+**Checks on a host call.** Host functions run with the panel's own privileges, but a call is not passed through unchecked:
+
+* **Permissions.** Privileged modules are gated on the grants recorded for the plugin: `files_read`, `files`, `manage_servers`, `node_commands`, `listen_events`, `manage_rbac`, `secrets`, `ssh`, `manage_nodes` (`manage_games`, `manage_game_mods` and `manage_users` are reserved for write operations that do not exist yet). A wider grant satisfies a narrower one: `files` includes `files_read`. Enforcement is transitional — `PLUGIN_PERMISSIONS_ENFORCE` defaults to `false`, so grants are recorded, displayed and editable while every check still passes; a future release will default it to `true`, so declare the permissions your plugin needs now. The mutating `gameap-nodes` calls check their grant regardless of that setting. A refused call answers `plugin permission required`.
+* **Paths.** Every path handed to `gameap-nodefs`, the `work_dir` of `gameap-nodecmd` and a node file referenced by an HTTP response is checked on the panel before it reaches the daemon. A path with a `..` segment or a NUL byte is always refused. `PLUGIN_NODEFS_PATH_POLICY` may additionally confine paths to the node's `work_path` or to the game server directories on it; in every restricted mode the plugin's own service directory, `/.plugins/`, stays open — that is where node-side working files belong. A refused call answers `path policy: : `.
+* **Rate limits.** Each plugin has its own token bucket per class: `gameap-nodefs` 50 calls/s (burst 200), `gameap-http` 20/s (50), `gameap-ssh` 20/s (60), `gameap-rbac` 10/s (50), `gameap-nodecmd` and server control 5/s (20). A refused call answers `rate limited: ...`; the plugin is never disabled for it.
+* **Audit log.** Privileged operations — server control, node commands, file writes, SSH, RBAC changes — are recorded with the plugin as the actor, refusals included.
+
+Installing plugins is nevertheless entrusted to administrators only — install plugins only from sources you trust.
## Runtime limits
@@ -121,6 +129,7 @@ Details:
| Size of an uploaded `.wasm` file | 100 MB |
| Body of an HTTP request to a plugin | 1 MB |
| Body of a `gameap-http` response | 10 MB |
+| One `gameap-nodefs` `download` / `upload` message | 32 MB (`PLUGIN_NODEFS_MAX_INLINE`); a windowed `download` is measured by the window |
When a call timeout is exceeded, the panel disables the plugin until a restart. The file system and the network are not available from WASM — only through host functions.
diff --git a/en/plugins/frontend.md b/en/plugins/frontend.md
index 220e0c6..3326d57 100644
--- a/en/plugins/frontend.md
+++ b/en/plugins/frontend.md
@@ -57,7 +57,9 @@ The `sidebar-sections` and `admin-pages` slots are declared in the SDK but are n
Details:
* For `server-tabs`, a permission check is available: `checkPermission: { type: 'hasServerPermissions', permissions: [...] }` — the tab is shown only if the user has all the listed permissions for the server (plugin permissions have the form `plugin:{id}:...`, for example `plugin:ezvdsxmlu6fbk:manage`).
-* File editors are registered with match rules (`fileName`, `extensions`, `pathContains`, `fullPath`, `gameCode` and others): the editor with the highest specificity becomes the default editor for the file. Files larger than 1 MB are not opened by plugin editors. The editor component receives the `content`, `filePath`, `fileName`, `extension`, `pluginId`, `gameCode` and `gameName` props and emits the `save` and `close` events; saving the file to the server is done by the panel itself.
+* File editors are registered with match rules (`fileName`, `extensions`, `pathContains`, `fullPath`, `gameCode` and others): the most specific matching editor becomes the default editor for the file and opens on a double click. An editor marked `contextMenuOnly: true` never becomes the default and is offered in the context menu only — which is what an editor matching every file (`allFiles: true`) needs, so that it does not take over every preview. `menuLabel` replaces the "Edit with …" wording of the menu item, and `checkPermission: { type: 'hasServerPermissions', permissions: [...] }` hides the item from users without the listed server permissions.
+* The editor component receives the `content`, `filePath`, `fileName`, `extension`, `fileSize`, `fileMtime`, `disk`, `pluginId`, `gameCode` and `gameName` props and emits the `save` and `close` events; saving the file to the server is done by the panel itself.
+* The panel downloads the file before it mounts the editor, and it does not do that for files larger than 1 MB. An editor that declares `contentType: 'none'` is handed no `content` prop at all and loads whatever it needs itself, so the size limit does not apply to it and a file of any size can be opened this way; `fileSize` and `fileMtime` come from the directory listing, so such an editor can describe the file without downloading it. With `contentType: 'text'` (the default) the content arrives as a string, with `'binary'` as an `ArrayBuffer`.
## Translations
diff --git a/en/security.md b/en/security.md
index 9431980..c35a568 100644
--- a/en/security.md
+++ b/en/security.md
@@ -147,8 +147,20 @@ In PostgreSQL, where this field is of type `JSONB`:
UPDATE users SET metadata = metadata - 'mfa_first_shown_at' WHERE login = 'admin';
```
-In MySQL and SQLite the field is stored as JSON text — the easiest way is to clear it entirely:
-`UPDATE users SET metadata = NULL WHERE login = 'admin';`
+In MySQL the field is stored as JSON text, and the key is removed like this:
+
+```sql
+UPDATE users SET metadata = JSON_REMOVE(metadata, '$.mfa_first_shown_at') WHERE login = 'admin';
+```
+
+In SQLite — starting with version 3.38:
+
+```sql
+UPDATE users SET metadata = json_remove(metadata, '$.mfa_first_shown_at') WHERE login = 'admin';
+```
+
+> Do not clear the `metadata` field entirely (`SET metadata = NULL`): besides the 2FA countdown it
+> may hold other information about the user, and that would be lost.
After that, start the panel and enable 2FA again.
@@ -343,12 +355,29 @@ automatically.
| `AUTH_SECRET` | yes | Signing key for session tokens. Without it the panel will not start |
| `ENCRYPTION_KEY` | no | Encryption key for secrets in the database |
-Both values must be **exactly 32 random bytes**, not a passphrase:
+Both values must be random rather than a passphrase. Their length requirements, however, are
+**different** — the panel handles them differently.
+
+**`AUTH_SECRET` is used as is and coerced to exactly 32 bytes:** a shorter value is padded, a longer
+one is **truncated**, and a warning goes to the log. So give it exactly 32 characters:
```bash
-openssl rand -hex 16
+openssl rand -base64 24
```
+Do not use `openssl rand -hex 32` here: it produces 64 characters, the panel discards half of them,
+and the strength stays the same.
+
+**`ENCRYPTION_KEY` is hashed in full with SHA-256**, its length is not limited and nothing is lost.
+A longer value can be used here:
+
+```bash
+openssl rand -hex 32
+```
+
+Hashing preserves the entropy of the original value but does not increase it, so the key still has
+to be random. A passphrase is unsafe here: it can be brute-forced if the encrypted value leaks.
+
> `AUTH_SECRET` is silently coerced to 32 bytes: a shorter value is padded, a longer one is
> truncated, and only a warning goes to the log. A short or predictable `AUTH_SECRET` means session
> tokens can be forged.
diff --git a/en/sitemap.xml b/en/sitemap.xml
index 3c93640..3ceefbe 100644
--- a/en/sitemap.xml
+++ b/en/sitemap.xml
@@ -8,11 +8,12 @@
{%- for p in pages -%}
{%- assign head = p.url | slice: 0, 4 -%}
{%- if head == prefix and p.layout == 'default' and p.hidden != true -%}
-{%- assign clean_path = p.url | replace: '/en/', '/' | replace: '/ru/', '/' %}
+{%- assign clean_path = p.url | replace: '/en/', '/' | replace: '/ru/', '/' | replace: '/es/', '/' %}
{{ site.domains.en }}{{ clean_path }}
+
{%- endif -%}
diff --git a/en/tutorials/hytale.md b/en/tutorials/hytale.md
index c1e0b56..768a56a 100644
--- a/en/tutorials/hytale.md
+++ b/en/tutorials/hytale.md
@@ -39,6 +39,10 @@ Navigate to **Administration** → **Game Servers** → **Create**
* In the IP field, select the desired address for your server, then you can choose an available port or use the suggested one.
* Enter the game server port, default is 5520. You don't need to enter rcon and query ports.
+> Hytale communicates over QUIC, that is **UDP**. Open and forward the selected game port as UDP
+> in the firewall and on NAT — with TCP-only forwarding the server starts, but clients cannot
+> connect to it.
+
### Configuration After First Launch
After the first launch, you need to complete several authorization steps.
diff --git a/en/tutorials/rust.md b/en/tutorials/rust.md
index 6523d61..f92b2dc 100644
--- a/en/tutorials/rust.md
+++ b/en/tutorials/rust.md
@@ -73,6 +73,7 @@ The map on the server.
By default, the procedurally generated map Procedural Map.
Possible values:
+* Procedural Map
* Barren
* Craggy Island
* Hapis
diff --git a/en/upgrade_from_v3_to_v4.md b/en/upgrade_from_v3_to_v4.md
index 4c07cac..172e3d4 100644
--- a/en/upgrade_from_v3_to_v4.md
+++ b/en/upgrade_from_v3_to_v4.md
@@ -165,7 +165,8 @@ ENCRYPTION_KEY=replace_with_32_random_bytes
HTTP_PORT=8025
```
-The keys are easy to generate with `openssl rand -hex 16`.
+Generate the keys with `openssl rand -base64 24` for `AUTH_SECRET` and `openssl rand -hex 32` for
+`ENCRYPTION_KEY`: the first is coerced to exactly 32 bytes, the second is hashed in full.
Run:
diff --git a/en/websocket.md b/en/websocket.md
index dc2b94e..e532c00 100644
--- a/en/websocket.md
+++ b/en/websocket.md
@@ -32,8 +32,13 @@ wss://panel.example.com:8025/api/ws/servers/1/console?token=
```
**Only short-lived tokens** with the `glst_` prefix **are accepted in the `token` parameter**.
-A personal access token cannot be passed there — this keeps it out of web server logs and
-browser history.
+A personal access token cannot be passed there — a long-lived access key will not end up in the
+URL even by mistake.
+
+> The short-lived token itself does stay in the address and can settle in the logs of a reverse
+> proxy, a web server or a monitoring system. Being single-use and living for 10 seconds makes such
+> a record worthless, but if logs are kept for a long time, it is better to strip the `token`
+> parameter out of them.
Getting a short-lived token:
diff --git a/es/404.md b/es/404.md
new file mode 100644
index 0000000..9d8c687
--- /dev/null
+++ b/es/404.md
@@ -0,0 +1,20 @@
+---
+title: Página no encontrada
+layout: default
+lang: es
+hidden: true
+noindex: true
+description: "No se encontró la página de documentación de GameAP solicitada."
+---
+
+Esta página no existe en la documentación. Es posible que la dirección haya cambiado o que el enlace contenga un error tipográfico.
+
+A dónde ir a continuación:
+
+* [Primeros pasos](/es/get_started.html) — instalación del panel y del primer servidor dedicado
+* [Instalación en Linux](/es/install/install_on_linux.html) y [en Windows](/es/install/install_on_windows.html)
+* [Referencia de config.env](/es/config.html) — todas las opciones de configuración
+* [Solución de problemas](/es/troubleshooting.html) — problemas comunes y sus soluciones
+* [Documentación de GameAP](/es/) — índice de contenidos
+
+Si llegó aquí a través de un enlace del sitio de GameAP, [por favor, infórmenos](https://github.com/gameap/gameap.github.io/issues).
diff --git a/es/api.md b/es/api.md
new file mode 100644
index 0000000..a44d75e
--- /dev/null
+++ b/es/api.md
@@ -0,0 +1,161 @@
+---
+title: API y tokens
+layout: default
+lang: es
+category: Administración
+order: 336
+---
+
+El panel se controla completamente a través de la API HTTP: la interfaz funciona a través de la misma API.
+Una descripción completa de los métodos con los esquemas de solicitud y respuesta está disponible en
+[openapi.gameap.io](https://openapi.gameap.io/).
+
+Esta página explica cómo autenticarse en la API.
+
+## Métodos de autenticación
+
+| Método | Para qué sirve | Duración |
+|---------------------------------|------------------------------------------------------|---------------------|
+| Token de sesión | La interfaz | 24 horas o 7 días |
+| Token de acceso personal (PAT) | Scripts, integraciones, automatización | Indefinida |
+| Token de corta duración | WebSocket y descarga de archivos | 10 segundos |
+
+El token se pasa en una cabecera:
+
+```http
+Authorization: Bearer
+```
+
+## Tokens de acceso personal
+
+Este es el método principal para la automatización: el token no está vinculado a una sesión, no caduca
+y tiene su propio conjunto de permisos.
+
+### Creación
+
+Un token se puede crear en el perfil o con una solicitud:
+
+```bash
+curl -X POST https://panel.example.com:8025/api/tokens \
+ -H "Authorization: Bearer " \
+ -H "Content-Type: application/json" \
+ -d '{"name": "ci-deploy", "abilities": ["server:list", "server:restart"]}'
+```
+
+La respuesta contiene el token completo:
+
+```json
+{"token": "12|kJ3n8sQm..."}
+```
+
+> El token se muestra **una sola vez**. En la base de datos solo se almacena su hash SHA-256, y el
+> valor no se puede recuperar: si lo pierde, emita uno nuevo.
+
+El formato del token es `{id}|{secret}`. El separador `|` es obligatorio: pase el valor completo,
+exactamente como fue emitido.
+
+### Permisos (abilities) del token
+
+Los permisos se especifican en el momento de la creación y restringen el token independientemente de los
+permisos del usuario: el token no puede hacer más de lo que se le permite, ni más de lo que se le permite
+a su propietario.
+
+| Permiso | Qué permite |
+|---------------------------|------------------------------------------|
+| `server:list` | Ver la lista de servidores |
+| `server:start` | Iniciar un servidor |
+| `server:stop` | Detener un servidor |
+| `server:restart` | Reiniciar un servidor |
+| `server:update` | Actualizar un servidor |
+| `server:console` | Leer y escribir en la consola |
+| `server:rcon-console` | Consola RCON |
+| `server:rcon-players` | Gestionar jugadores a través de RCON |
+| `server:tasks-manage` | Gestionar las tareas del servidor |
+| `server:settings-manage` | Gestionar la configuración del servidor |
+| `admin:server:create` | Crear servidores |
+| `admin:gdaemon-task:read` | Leer las tareas del daemon |
+
+Los permisos con el prefijo `admin:` solo los puede conceder un administrador: el intento de un usuario
+normal de añadirlos falla.
+
+La lista actual está disponible con:
+
+```http
+GET /api/tokens/abilities
+```
+
+### Listado y revocación
+
+```http
+GET /api/tokens — lista de sus tokens
+DELETE /api/tokens/{id} — revocar un token
+```
+
+La lista muestra el nombre, los permisos y la hora del último uso, lo que resulta útil para encontrar
+tokens sin utilizar.
+
+> **Cambiar la contraseña revoca los tokens.** Todos los tokens personales creados antes del cambio de
+> contraseña dejan de funcionar. Después de cambiar la contraseña, emita los tokens de nuevo.
+
+### Ejemplo de uso
+
+```bash
+TOKEN='12|kJ3n8sQm...'
+
+# lista de servidores
+curl -H "Authorization: Bearer $TOKEN" \
+ https://panel.example.com:8025/api/servers
+
+# reiniciar un servidor
+curl -X POST -H "Authorization: Bearer $TOKEN" \
+ https://panel.example.com:8025/api/servers/1/restart
+```
+
+## Token de sesión
+
+Se emite al iniciar sesión con nombre de usuario y contraseña:
+
+```bash
+curl -X POST https://panel.example.com:8025/api/auth/login \
+ -H "Content-Type: application/json" \
+ -d '{"login": "admin", "password": "..."}'
+```
+
+El formato del token es PASETO v4.local. Una sesión normal dura 24 horas, o 7 días con la opción
+«recordarme» activada. Al cerrar sesión (`POST /api/auth/logout`), el token se añade a la lista de
+revocación, que se comprueba en cada solicitud.
+
+Si el usuario tiene activada la autenticación de dos factores, el inicio de sesión devuelve
+`two_factor_required` junto con un `challenge_token` en lugar de un token; el segundo factor se confirma
+con `POST /api/auth/2fa/verify`. Consulte [Seguridad](/es/security.html) para más detalles.
+
+Los tokens de sesión son incómodos para la automatización: caducan, y el inicio de sesión está protegido
+por limitación de velocidad (rate limiting) y posiblemente por un CAPTCHA. Utilice tokens personales.
+
+## Tokens de corta duración
+
+Se necesitan donde el token tiene que pasarse en la dirección de la página: conexiones WebSocket y
+descargas de archivos. Se emiten con `POST /api/auth/short-lived-token`, llevan el prefijo `glst_`,
+son de un solo uso y viven no más de 10 segundos independientemente de la configuración.
+
+Solo estos tokens se aceptan en el parámetro de consulta `?token=`; un token personal no se puede pasar
+allí: esto evita que aparezca en los registros del servidor web y en el historial del navegador.
+
+## Límites y códigos de respuesta
+
+| Código | Motivo |
+|--------|-------------------------------------------------------------------------|
+| `401` | El token falta, no es válido o está revocado |
+| `403` | El token o el usuario no tienen permisos |
+| `422` | Error de validación de la solicitud |
+| `429` | Se superó el límite de intentos de inicio de sesión |
+
+La limitación de velocidad se aplica solo al inicio de sesión y a la verificación del segundo factor:
+20 intentos fallidos por dirección y 5 por nombre de usuario en 15 minutos. Las solicitudes con un token
+personal no están sujetas a limitación de velocidad.
+
+## CORS
+
+Si la API se llama desde un navegador en un origen diferente, indique los orígenes permitidos en
+`HTTP_ALLOWED_ORIGINS`, en forma completa, incluido el esquema. El comodín `*` no está soportado.
+Consulte la [referencia de config.env](/es/config.html).
diff --git a/es/architecture.md b/es/architecture.md
new file mode 100644
index 0000000..8293613
--- /dev/null
+++ b/es/architecture.md
@@ -0,0 +1,94 @@
+---
+title: Cómo funciona GameAP
+layout: default
+lang: es
+category: General
+order: 5
+---
+
+GameAP consta de dos aplicaciones: el **panel**, con el que trabaja el administrador, y el
+**daemon**, que se ejecuta en cada servidor dedicado y gestiona los servidores de juego.
+
+
+
+## Tres capas
+
+El **panel** es una aplicación única con una interfaz web integrada. Almacena todos los datos:
+usuarios, servidores dedicados, juegos, servidores de juego, tareas. No inicia los servidores
+de juego por sí mismo ni toca sus archivos — solo envía comandos a los daemons.
+
+**GameAP Daemon** se ejecuta en cada servidor dedicado. Inicia y detiene los servidores de
+juego, supervisa su estado, los instala y actualiza, trabaja con archivos y recopila métricas.
+
+Los **servidores de juego** son procesos que el daemon gestiona a través de un gestor de
+procesos: systemd, Docker, Podman, tmux y otros.
+
+El panel y el daemon pueden instalarse en un mismo servidor — en ese caso las tres capas se
+encuentran en la misma máquina.
+
+## Quién se conecta a quién
+
+La conexión siempre la establece el **daemon**: se conecta al panel por sí mismo y mantiene
+una única conexión persistente por la que pasa todo el tráfico.
+
+El panel no se conecta al daemon. El servidor dedicado no necesita puertos entrantes abiertos
+ni una dirección IP externa — basta con el acceso saliente al panel.
+
+Esto difiere de GameAP 3, donde el panel se conectaba al daemon.
+
+| Dirección | Puerto | Protocolo |
+|-----------------------------------------|-----------------|------------------|
+| Navegador del administrador → panel | `8025` | HTTP, HTTPS |
+| Daemon → panel | `31718` | gRPC |
+| Panel → servidor de juego | puerto del juego | Query, RCON |
+
+Los puertos `8025` y `31718` son **listeners independientes**, no un solo puerto con detección
+de protocolo.
+
+El panel realiza las solicitudes Query y RCON a los servidores de juego por sí mismo,
+directamente — no pasan por el daemon.
+
+## Qué ocurre al iniciar un servidor
+
+1. El administrador hace clic en un botón del panel.
+2. El panel crea una tarea y la guarda en la base de datos.
+3. La tarea llega al daemon a través de la conexión establecida.
+4. El daemon inicia el servidor de juego a través del gestor de procesos.
+5. El daemon envía el progreso y la salida del comando; el panel los muestra en tiempo real a
+ través de WebSocket.
+6. A partir de entonces, el daemon informa periódicamente del estado del servidor y las métricas.
+
+Si la conexión con el panel se pierde, los servidores de juego siguen funcionando — solo el
+control desde el panel no está disponible. Una vez restablecida la conexión, el daemon se
+reconecta y recibe el estado actual completo del panel.
+
+## Dónde se almacena cada cosa
+
+| Datos | Dónde |
+|----------------------------------------------|---------------------------------------------------|
+| Usuarios, servidores, juegos, tareas | Base de datos del panel |
+| Certificados gRPC, datos ACME | Almacenamiento de archivos del panel |
+| Sesiones, contadores, clave de instalación | Caché del panel |
+| Configuración del panel | `config.env` |
+| Archivos del servidor de juego | El servidor dedicado, en el directorio de trabajo del daemon |
+| Configuración del daemon | `gameap-daemon.yaml` en el servidor dedicado |
+| Métricas | La RAM del daemon, durante no más de una hora |
+
+Los archivos de los servidores de juego no se copian al panel: el gestor de archivos trabaja
+con ellos a través del daemon.
+
+## Ampliación
+
+Los **plugins** se ejecutan dentro del panel en un entorno aislado de WebAssembly. Añaden
+páginas, pestañas e integraciones, pero no tienen acceso propio al sistema — solo a través de
+la interfaz controlada del panel. Consulte [Plugins](/es/plugins/index.html).
+
+**API** — todo lo que hace la interfaz está disponible a través de la API HTTP; la propia
+interfaz funciona a través de ella. Consulte [API y tokens](/es/api.html).
+
+## A continuación
+
+* [Requisitos](/es/requirements.html) — lo necesario para la instalación
+* [Primeros pasos](/es/get_started.html) — instalación del panel y del primer servidor dedicado
+* [API gRPC](/es/daemon/grpc.html) — detalles del canal de comunicación panel–daemon
+* [Múltiples instancias del panel](/es/multi_instance.html) — una configuración tolerante a fallos
diff --git a/es/config.md b/es/config.md
new file mode 100644
index 0000000..07c10d2
--- /dev/null
+++ b/es/config.md
@@ -0,0 +1,385 @@
+---
+title: Referencia de config.env
+layout: default
+lang: es
+category: Administración
+order: 334
+---
+
+El panel se configura mediante variables de entorno. Los valores se leen del archivo `config.env`:
+
+* Linux — `/etc/gameap/config.env`
+* Windows — `C:\gameap\web\config.env`
+
+El formato del archivo es un par `NAME=value` por línea, sin comillas y sin `export`. Las líneas
+que comienzan con `#` se ignoran. Después de modificar el archivo, hay que reiniciar el panel:
+
+```bash
+gameapctl panel restart
+```
+
+Las variables definidas en el entorno del proceso tienen prioridad sobre el archivo.
+
+Solo dos variables son obligatorias: `DATABASE_URL` y `AUTH_SECRET`. El panel no arranca sin
+ellas. Todo lo demás tiene valores predeterminados adecuados para una instalación típica de un solo servidor.
+
+Notación utilizada en las tablas: *duration* — un número con sufijo `s`, `m` u `h` (`30s`, `5m`,
+`12h`); *list* — valores separados por comas; *size* — un número con sufijo binario `K`, `M`, `G`, `T`
+(`8M`, `100G`); un número sin sufijo se interpreta como bytes.
+
+## HTTP
+
+| Variable | Tipo | Predeterminado | Propósito |
+|------------------------|--------|----------------|----------------------------------------------------------------------------------|
+| `HTTP_HOST` | string | `0.0.0.0` | Nombre de host del panel. Se usa para CORS y como nombre en los certificados autofirmados |
+| `HTTP_BIND_IP` | string | `""` | Dirección IP de escucha. Vacío — todas las interfaces |
+| `HTTP_PORT` | number | `8025` | Puerto de la interfaz web y de la API |
+| `HTTPS_PORT` | number | `443` | Puerto HTTPS. Solo escucha cuando hay un certificado configurado |
+| `HTTP_ALLOWED_ORIGINS` | list | `""` | Orígenes autorizados a hacer solicitudes desde el navegador. Vacío — un único origen derivado de `HTTP_HOST` |
+
+`HTTP_ALLOWED_ORIGINS` acepta orígenes completos incluyendo el esquema: `https://panel.example.com`.
+El comodín `*` no está soportado.
+
+## Base de datos
+
+| Variable | Tipo | Predeterminado | Propósito |
+|-------------------|--------|----------------|--------------------------------------------|
+| `DATABASE_DRIVER` | string | `mysql` | `mysql`, `postgres`, `sqlite`, `inmemory` |
+| `DATABASE_URL` | string | — | **Obligatoria.** Cadena de conexión |
+
+Los nombres del driver de PostgreSQL son intercambiables: `postgres`, `postgresql`, `pgsql`, `pg`, `pgx`.
+
+Formatos de la cadena de conexión:
+
+```text
+# PostgreSQL
+DATABASE_DRIVER=postgres
+DATABASE_URL=postgres://gameap:password@localhost:5432/gameap?sslmode=disable
+
+# MySQL / MariaDB
+DATABASE_DRIVER=mysql
+DATABASE_URL=gameap:password@tcp(localhost:3306)/gameap?parseTime=true
+
+# SQLite
+DATABASE_DRIVER=sqlite
+DATABASE_URL=file:/var/lib/gameap/db.sqlite?_busy_timeout=5000&_journal_mode=WAL&cache=shared
+```
+
+El driver `inmemory` mantiene los datos solo en RAM y está pensado para pruebas: todo se pierde al
+reiniciar.
+
+## TLS y certificados
+
+| Variable | Tipo | Predeterminado | Propósito |
+|-------------------|--------|----------------|-----------------------------------------------------|
+| `TLS_CERT_FILE` | string | `""` | Ruta al archivo del certificado |
+| `TLS_KEY_FILE` | string | `""` | Ruta al archivo de la clave privada |
+| `TLS_CERT` | string | `""` | Certificado directamente en la variable, PEM o base64 |
+| `TLS_KEY` | string | `""` | Clave privada directamente en la variable, PEM o base64 |
+| `TLS_FORCE_HTTPS` | bool | `false` | Redirigir HTTP a HTTPS |
+
+HTTPS se activa cuando al menos una fuente de certificados está configurada: archivos, valores en
+variables o ACME. Si ninguna está configurada, el panel sirve solo HTTP.
+
+Con `TLS_FORCE_HTTPS=true`, toda solicitud HTTP recibe una redirección `301`, excepto
+`/.well-known/acme-challenge/` — de lo contrario sería imposible emitir un certificado de Let's Encrypt.
+
+### ACME (Let's Encrypt)
+
+| Variable | Tipo | Predeterminado | Propósito |
+|-------------------------------|----------|-----------------|-----------------------------------------------------------------|
+| `ACME_ENABLED` | bool | `false` | Emisión automática de certificados |
+| `ACME_EMAIL` | string | `""` | Dirección para notificaciones. **Obligatoria** cuando ACME está activado |
+| `ACME_DOMAINS` | list | `""` | Dominios del certificado. **Obligatoria** cuando ACME está activado |
+| `ACME_CHALLENGE_TYPE` | string | `http-01` | `http-01` o `dns-01` |
+| `ACME_DNS_PROVIDER` | string | `""` | Proveedor DNS para `dns-01`, por ejemplo `cloudflare` |
+| `ACME_DIRECTORY_URL` | string | ACME de producción | URL del directorio ACME. Para pruebas, apúntela al staging de Let's Encrypt |
+| `ACME_RENEWAL_THRESHOLD` | duration | `720h` | Con cuánta antelación al vencimiento renovar. 30 días por defecto |
+| `ACME_RENEWAL_CHECK_INTERVAL` | duration | `12h` | Con qué frecuencia comprobar la fecha de vencimiento |
+| `ACME_PROPAGATION_TIMEOUT` | duration | `180s` | Cuánto tiempo esperar la propagación del registro DNS con `dns-01` |
+| `ACME_STORAGE_PATH` | string | `acme` | Directorio para almacenar los certificados y la clave de la cuenta ACME |
+
+ACME solo se activa cuando `ACME_ENABLED=true`, `ACME_EMAIL` y `ACME_DOMAINS` están definidos.
+Si falta algo, el panel continúa silenciosamente sin ACME.
+
+El valor predeterminado es el directorio de producción de Let's Encrypt, con sus estrictos límites
+sobre el número de intentos. Mientras depura su configuración, use staging:
+`ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory`.
+
+## Autenticación
+
+| Variable | Tipo | Predeterminado | Propósito |
+|---------------------------------|----------|----------------|---------------------------------------------------------------------------|
+| `AUTH_SECRET` | string | — | **Obligatoria.** Clave de firma de tokens, exactamente 32 bytes aleatorios |
+| `ENCRYPTION_KEY` | string | `""` | Clave de cifrado de los secretos en la base de datos, exactamente 32 bytes aleatorios |
+| `AUTH_SERVICE` | string | `paseto` | Formato de token: `paseto` o `jwt`. Cualquier otro valor — el panel no arrancará |
+| `AUTH_BCRYPT_COST` | number | `13` | Coste de bcrypt, de 10 a 14 |
+| `AUTH_ALLOW_WEAK_PASSWORDS` | bool | `false` | Desactiva la comprobación contra la lista de contraseñas comprometidas |
+| `AUTH_REQUIRE_MFA_FOR_ADMINS` | bool | `true` | Exigir 2FA a los administradores |
+| `AUTH_MFA_HARD_FAIL_DAYS` | number | `30` | Días hasta el bloqueo. `0` — solo recordatorio |
+| `AUTH_MFA_ENROLLMENT_TOKEN_TTL` | duration | `15m` | Duración de la sesión restringida emitida tras la fecha límite |
+| `AUTH_SHORT_LIVED_TOKEN_TTL` | duration | `10s` | Duración de los tokens de un solo uso `glst_`. Limitada efectivamente a 10 segundos |
+
+Consulte la página [Seguridad](/es/security.html) para más detalles.
+
+> `AUTH_SECRET` se ajusta silenciosamente a 32 bytes: un valor corto se rellena, uno largo se trunca.
+> Defina exactamente 32 bytes aleatorios, por ejemplo `openssl rand -hex 16`.
+
+### Primer administrador
+
+Estas tres variables se leen solo durante la inicialización de una base de datos vacía:
+
+| Variable | Propósito |
+|------------------|---------------------------------------------------------------------------------|
+| `ADMIN_LOGIN` | Nombre de usuario del primer administrador |
+| `ADMIN_EMAIL` | Dirección de correo electrónico |
+| `ADMIN_PASSWORD` | Contraseña. Si no se define, se genera una aleatoria y se muestra en el log en el primer arranque |
+
+> La contraseña de `ADMIN_PASSWORD` **no** se comprueba contra la política de contraseñas: ni la
+> longitud ni la lista de contraseñas comprometidas. Elija con cuidado.
+
+## Control de acceso y caché
+
+| Variable | Tipo | Predeterminado | Propósito |
+|-----------------------------|----------|------------------|------------------------------------------------|
+| `RBAC_CACHE_TTL` | duration | `30s` | Duración de la caché de comprobación de permisos |
+| `CACHE_DRIVER` | string | `memory` | `memory`, `redis`, `postgres`, `mysql`. Véase más abajo |
+| `CACHE_REDIS_ADDR` | string | `localhost:6379` | Dirección de Redis |
+| `CACHE_REDIS_PASSWORD` | string | `""` | Contraseña de Redis |
+| `CACHE_REDIS_DB` | number | `0` | Número de la base de datos de Redis |
+| `CACHE_TTL_RBAC` | duration | `24h` | Duración de la caché de permisos |
+| `CACHE_TTL_GAMES` | duration | `48h` | Duración de la caché de juegos |
+| `CACHE_TTL_NODES` | duration | `24h` | Duración de la caché de servidores dedicados |
+| `CACHE_TTL_USERS` | duration | `6h` | Duración de la caché de usuarios |
+| `CACHE_TTL_PERSONAL_TOKENS` | duration | `24h` | Duración de la caché de tokens personales |
+| `CACHE_TTL_SERVER_SETTINGS` | duration | `12h` | Duración de la caché de la configuración de servidores |
+
+`CACHE_DRIVER` acepta `memory` (alias `inmemory`), `redis`, `mysql` (alias `database`) y
+`postgres` (alias `postgresql`, `pgsql`, `pg`). Un valor desconocido hace que el panel falle al
+arrancar.
+
+La caché contiene más que datos de referencia: ahí residen la clave de configuración del daemon,
+la lista de tokens revocados y los contadores de intentos de inicio de sesión. Con
+`CACHE_DRIVER=memory` todo eso se pierde al reiniciar y no se comparte entre varias instancias del
+panel. Para una instalación con múltiples instancias, use `redis`.
+
+## Archivos
+
+| Variable | Tipo | Predeterminado | Propósito |
+|------------------------------|--------|----------------|-----------------------------------------|
+| `FILES_DRIVER` | string | `local` | `local` o `s3` |
+| `FILES_LOCAL_BASE_PATH` | string | `""` | Directorio base para el driver `local` |
+| `FILES_S3_ENDPOINT` | string | `""` | Dirección del almacenamiento compatible con S3 |
+| `FILES_S3_USE_SSL` | bool | `true` | Acceder al almacenamiento por HTTPS |
+| `FILES_S3_ACCESS_KEY_ID` | string | `""` | ID de la clave de acceso |
+| `FILES_S3_SECRET_ACCESS_KEY` | string | `""` | Clave de acceso secreta |
+| `FILES_S3_BUCKET` | string | `""` | Nombre del bucket |
+
+### Subida de archivos
+
+| Variable | Tipo | Predeterminado | Propósito |
+|---------------------------------|----------|----------------|------------------------------------------------------|
+| `FILES_UPLOAD_CHUNK_SIZE` | size | `8M` | Tamaño de los fragmentos en las subidas por partes |
+| `FILES_UPLOAD_MAX_CHUNKS` | number | `100000` | Número máximo de fragmentos por archivo |
+| `FILES_UPLOAD_SESSION_TTL` | duration | `24h` | Cuánto tiempo vive una subida sin terminar |
+| `FILES_UPLOAD_DISPATCH_TIMEOUT` | duration | `2m` | Tiempo de espera para entregar el archivo ensamblado al daemon |
+| `FILES_UPLOAD_JANITOR_INTERVAL` | duration | `12h` | Con qué frecuencia limpiar las subidas expiradas |
+| `FILES_UPLOAD_ALLOWED_MIMES` | list | `""` | Amplía la lista de tipos permitidos, no la reemplaza |
+| `FILES_UPLOAD_ALLOW_ARCHIVES` | bool | `false` | Permitir archivos comprimidos: zip, tar, gzip, bzip2, 7z, xz |
+| `FILES_UPLOAD_ALLOW_BINARY` | bool | `false` | Permitir archivos binarios arbitrarios |
+
+El tamaño máximo de archivo es `FILES_UPLOAD_CHUNK_SIZE` multiplicado por `FILES_UPLOAD_MAX_CHUNKS`.
+Con los valores predeterminados eso es unos 780 GB. Una subida normal de una sola solicitud tiene un
+límite aparte de 100 MB; no es configurable.
+
+### Archivos comprimidos
+
+| Variable | Tipo | Predeterminado | Propósito |
+|----------------------------------------|--------|----------------|-------------------------------------------------|
+| `FILES_ARCHIVE_MAX_BYTES` | size | `100G` | Tamaño máximo de un archivo comprimido creado |
+| `FILES_ARCHIVE_MAX_FILES` | number | `500000` | Número máximo de archivos en un comprimido |
+| `FILES_ARCHIVE_CONCURRENT_PER_SERVER` | number | `2` | Operaciones de compresión concurrentes por servidor |
+
+## gRPC
+
+| Variable | Tipo | Predeterminado | Propósito |
+|-------------------------------|--------|----------------|-----------------------------------------------------|
+| `GRPC_PORT` | number | `31718` | Puerto del servidor gRPC para los daemons |
+| `GRPC_TLS_ENABLED` | bool | `true` | Cifrado de las conexiones de los daemons |
+| `GRPC_REQUIRE_MTLS` | bool | `false` | Exigir un certificado de cliente al daemon |
+| `GRPC_EXTERNAL_HOST` | string | `""` | Dirección del panel comunicada al daemon |
+| `GRPC_EXTERNAL_PORT` | number | `0` | Puerto comunicado al daemon. `0` — se usa `GRPC_PORT` |
+| `GRPC_MAX_RECV_MSG_SIZE` | number | `10485760` | Tamaño máximo del mensaje entrante, en bytes |
+| `GRPC_MAX_SEND_MSG_SIZE` | number | `10485760` | Tamaño máximo del mensaje saliente, en bytes |
+| `GRPC_MAX_CONCURRENT_STREAMS` | number | `100` | Flujos concurrentes por conexión |
+| `GRPC_ENABLE_REFLECTION` | bool | `false` | Reflexión del esquema para herramientas de depuración |
+| `DAEMON_SETUP_KEY` | string | `""` | Clave permanente de configuración del daemon en lugar de una temporal |
+
+Consulte la página [GRPC API](/es/daemon/grpc.html) para más detalles.
+
+> `DAEMON_SETUP_KEY` define una clave que nunca expira. Es cómodo para despliegues automatizados,
+> pero cualquiera que conozca esa clave puede registrar un nuevo servidor dedicado en el panel.
+> Para una instalación normal, deje esta variable sin definir — el panel emitirá una clave temporal
+> válida durante una hora.
+
+## Seguridad
+
+La descripción completa está en la página [Seguridad](/es/security.html).
+
+| Variable | Tipo | Predeterminado | Propósito |
+|------------------------------------|--------|---------------------------------|--------------------------------------------|
+| `SECURITY_HEADERS_ENABLED` | bool | `true` | Interruptor maestro de las cabeceras de seguridad |
+| `SECURITY_CONTENT_TYPE_OPTIONS` | bool | `true` | `X-Content-Type-Options: nosniff` |
+| `SECURITY_FRAME_OPTIONS` | string | `SAMEORIGIN` | `X-Frame-Options` |
+| `SECURITY_REFERRER_POLICY` | string | `strict-origin-when-cross-origin` | `Referrer-Policy` |
+| `SECURITY_HSTS_ENABLED` | bool | `true` | HSTS. Solo se envía por HTTPS |
+| `SECURITY_HSTS_MAX_AGE` | number | `31536000` | Duración de HSTS en segundos |
+| `SECURITY_HSTS_INCLUDE_SUBDOMAINS` | bool | `false` | Extender HSTS a los subdominios |
+| `SECURITY_HSTS_PRELOAD` | bool | `false` | Añadir `preload` |
+| `SECURITY_CSP_ENABLED` | bool | `true` | Content Security Policy |
+| `SECURITY_CSP_REPORT_ONLY` | bool | `false` | Solo informes, sin bloqueo |
+| `SECURITY_CSP_POLICY` | string | `""` | Reemplaza por completo la política generada |
+| `SECURITY_CSP_REPORT_URI` | string | `""` | Dirección para los informes de CSP |
+| `SECURITY_CSP_EXTRA_SCRIPT_SRC` | list | `""` | Amplía `script-src` |
+| `SECURITY_CSP_EXTRA_STYLE_SRC` | list | `""` | Amplía `style-src` |
+| `SECURITY_CSP_EXTRA_CONNECT_SRC` | list | `""` | Amplía `connect-src` |
+| `SECURITY_CSP_EXTRA_IMG_SRC` | list | `""` | Amplía `img-src` |
+| `SECURITY_CSP_EXTRA_FRAME_SRC` | list | `""` | Amplía `frame-src` |
+| `SECURITY_CSP_EXTRA_FONT_SRC` | list | `""` | Amplía `font-src` |
+| `SECURITY_SENSITIVE_PATH_PREFIXES` | list | véase más abajo | Rutas cuyas respuestas nunca deben almacenarse en caché |
+
+El valor predeterminado de `SECURITY_SENSITIVE_PATH_PREFIXES`:
+`/api/auth/,/api/profile/,/api/users/,/api/tokens/`.
+
+### CAPTCHA
+
+| Variable | Tipo | Predeterminado | Propósito |
+|----------------------|--------|----------------|-----------------------------------------------------------------|
+| `CAPTCHA_PROVIDER` | string | `""` | `recaptcha_v2`, `recaptcha_v3` o `turnstile`. Vacío — desactivado |
+| `CAPTCHA_SITE_KEY` | string | `""` | Clave pública |
+| `CAPTCHA_SECRET_KEY` | string | `""` | Clave secreta |
+| `CAPTCHA_MIN_SCORE` | float | `0.5` | Umbral, solo reCAPTCHA v3 |
+| `CAPTCHA_FAIL_OPEN` | bool | `false` | Dejar pasar los inicios de sesión cuando el servicio de verificación no está disponible |
+| `CAPTCHA_VERIFY_URL` | string | `""` | Dirección de verificación personalizada |
+
+### Registro de auditoría
+
+| Variable | Tipo | Predeterminado | Propósito |
+|--------------------------|--------|----------------|----------------------------------------|
+| `AUDIT_ENABLED` | bool | `true` | Registro de eventos de seguridad |
+| `AUDIT_CLIENT_IP_HEADER` | string | `""` | Cabecera que transporta la IP real del cliente |
+
+## Plugins
+
+| Variable | Tipo | Predeterminado | Propósito |
+|----------------------------|--------|--------------------------------|-------------------------------------------|
+| `PLUGINS_DISABLED` | bool | `false` | Desactivar los plugins por completo |
+| `PLUGINS_AUTOLOAD` | list | `""` | Plugins cargados en el arranque |
+| `PLUGINS_CACHE_ENABLED` | bool | `true` | Caché de los módulos WebAssembly compilados |
+| `PLUGINS_CACHE_DIR` | string | `""` | Directorio de esa caché |
+| `PLUGIN_STORE_URL` | string | `https://plugins.gameap.dev/api` | Dirección del catálogo de plugins |
+| `PLUGIN_STORE_LICENSE_KEY` | string | `""` | Clave de licencia para los plugins de pago |
+
+### Solicitudes de red de los plugins
+
+| Variable | Tipo | Predeterminado | Propósito |
+|-----------------------------------------|--------|----------------|---------------------------------------------------|
+| `PLUGIN_HTTP_BLOCK_PRIVATE_IPS` | bool | `true` | Bloquear solicitudes a direcciones de redes privadas |
+| `PLUGIN_HTTP_ALLOWED_SCHEMES` | list | `https` | Esquemas permitidos |
+| `PLUGIN_HTTP_ALLOWED_HOSTS` | list | `""` | Hosts exentos del bloqueo de direcciones privadas |
+| `PLUGIN_HTTP_MAX_TIMEOUT_SECONDS` | number | `30` | Tiempo máximo de una solicitud |
+| `PLUGIN_HTTP_MAX_REDIRECTS` | number | `5` | Número máximo de redirecciones |
+| `PLUGIN_HTTP_RESPONSE_HEADER_ALLOWLIST` | list | `""` | Amplía la lista de cabeceras que se pasan al plugin |
+
+Las direcciones del servicio de metadatos de los proveedores de nube están siempre bloqueadas;
+`PLUGIN_HTTP_ALLOWED_HOSTS` no tiene efecto sobre ellas.
+
+Un grupo aparte de ajustes gobierna los protocolos RCON y Query personalizados implementados por los plugins:
+
+| Variable | Tipo | Predeterminado | Propósito |
+|----------------------------------|--------|----------------|----------------------------------------------------------|
+| `PLUGIN_NET_ENABLED` | bool | `true` | Permitir que los plugins se comuniquen con los servidores de juego por la red |
+| `PLUGIN_NET_BLOCK_PRIVATE_IPS` | bool | `false` | Bloquear direcciones privadas. Desactivado: los servidores de juego suelen estar en la red interna |
+| `PLUGIN_NET_ALLOWED_HOSTS` | list | `""` | Hosts exentos |
+| `PLUGIN_NET_MAX_TIMEOUT_SECONDS` | number | `10` | Tiempo máximo de una operación individual |
+| `PLUGIN_NET_READ_BUFFER_BYTES` | number | `65536` | Tamaño máximo de una lectura individual |
+| `PLUGIN_NET_MAX_CONNECTIONS` | number | `8` | Conexiones concurrentes por plugin |
+
+### Planificador de plugins
+
+| Variable | Tipo | Predeterminado | Propósito |
+|-----------------------------------------|----------|----------------|------------------------------------------------|
+| `PLUGIN_SCHEDULER_MIN_INTERVAL` | duration | `1s` | Intervalo de tarea mínimo permitido |
+| `PLUGIN_SCHEDULER_MAX_TASKS_PER_PLUGIN` | number | `32` | Número máximo de tareas por plugin |
+| `PLUGIN_SCHEDULER_CALL_TIMEOUT` | duration | `60s` | Tiempo de espera predeterminado de la llamada al manejador |
+| `PLUGIN_SCHEDULER_MAX_CALL_TIMEOUT` | duration | `5m` | Tiempo de espera máximo que un plugin puede solicitar |
+| `PLUGIN_SCHEDULER_MAX_RETRIES` | number | `10` | Número máximo de reintentos |
+| `PLUGIN_SCHEDULER_MAX_RETRY_DELAY` | duration | `10m` | Retraso máximo entre reintentos |
+| `PLUGIN_SCHEDULER_MAX_JITTER` | duration | `30s` | Variación aleatoria máxima del inicio |
+| `PLUGIN_SCHEDULER_REFRESH_INTERVAL` | duration | `30s` | Con qué frecuencia releer las tareas de la base de datos |
+
+## Intercambio de eventos entre instancias
+
+Necesario solo cuando se ejecutan varias instancias del panel. Con una sola instancia los valores
+predeterminados son suficientes. Un valor desconocido de `PUBSUB_DRIVER` hace que el panel falle al arrancar.
+
+| Variable | Tipo | Predeterminado | Propósito |
+|------------------------------|----------|----------------|-----------------------------------------------------|
+| `PUBSUB_DRIVER` | string | `memory` | `memory`, `redis`, `postgres` |
+| `PUBSUB_INSTANCE_ID` | string | `""` | Identificador de la instancia, debe ser único |
+| `PUBSUB_REDIS_ADDR` | string | `""` | Dirección de Redis |
+| `PUBSUB_REDIS_PASSWORD` | string | `""` | Contraseña de Redis |
+| `PUBSUB_REDIS_DB` | number | `1` | Número de la base de datos de Redis |
+| `PUBSUB_RETRY_ENABLED` | bool | `true` | Reintentar la entrega en caso de error |
+| `PUBSUB_RETRY_MAX_RETRIES` | number | `3` | Número máximo de reintentos |
+| `PUBSUB_RETRY_INITIAL_DELAY` | duration | `100ms` | Retraso inicial antes de un reintento |
+| `PUBSUB_RETRY_MAX_DELAY` | duration | `5s` | Retraso máximo antes de un reintento |
+| `PUBSUB_RETRY_MULTIPLIER` | float | `2.0` | Factor por el que crece el retraso |
+| `PUBSUB_DLQ_ENABLED` | bool | `false` | Enviar los eventos no entregados a una cola aparte |
+| `PUBSUB_DLQ_DRIVER` | string | `memory` | Almacenamiento de esa cola |
+| `PUBSUB_DLQ_MAX_SIZE` | number | `1000` | Tamaño máximo de la cola |
+
+## Varios
+
+| Variable | Tipo | Predeterminado | Propósito |
+|-------------------------------|----------|--------------------------|-------------------------------------------------|
+| `LOGGER_LEVEL` | string | `info` | `debug`, `info`, `warn`, `error` |
+| `LOGGER_LOG_DB_QUERIES` | bool | `false` | Registrar las consultas a la base de datos. Solo para depuración |
+| `DEFAULT_LANGUAGE` | string | `""` | Idioma predeterminado de la interfaz, por ejemplo `ru` |
+| `GLOBAL_API_URL` | string | `https://api.gameap.com` | Dirección de la API global — actualizaciones de juegos |
+| `GAMES_CDN_URLS` | list | véase más abajo | Fuentes del catálogo de juegos, se prueban en orden |
+| `TASK_REAPER_INTERVAL` | duration | `1m` | Con qué frecuencia buscar tareas atascadas |
+| `TASK_REAPER_STALE_THRESHOLD` | duration | `10m` | Tiempo de inactividad tras el cual una tarea se considera atascada |
+
+El valor predeterminado de `GAMES_CDN_URLS`:
+`https://cdn.gameap.ru/games.json,https://cdn.gameap.com/games.json`.
+
+## Variables sin efecto
+
+Estas variables son analizadas por el panel pero no afectan a su comportamiento. No confíe en ellas:
+
+| Variable | Problema |
+|---------------------------------|-------------------------------------------------------------------------------------------|
+| `AUTH_SESSION_IDLE_TIMEOUT` | No existe terminación de sesión por inactividad. Una sesión vive todo su ciclo — 24 horas o 7 días |
+| `AUTH_SESSION_IDLE_UPDATE_FREQ` | Igual que la anterior |
+| `GRPC_FILE_TRANSFER_BASE_PATH` | El valor no se pasa a ninguna parte |
+| `GRPC_ENABLED` | Esta variable no existe en absoluto. gRPC siempre está en marcha y no se puede desactivar. Las versiones antiguas de `gameapctl` añaden esta línea a `config.env` |
+
+Los límites de frecuencia de inicio de sesión tampoco son configurables mediante variables de
+entorno — están definidos como constantes en el código. Los valores figuran en la página
+[Seguridad](/es/security.html).
+
+## Ejemplo de configuración mínima
+
+```dotenv
+DATABASE_DRIVER=postgres
+DATABASE_URL=postgres://gameap:password@localhost:5432/gameap?sslmode=disable
+
+AUTH_SECRET=replace_with_32_random_bytes
+ENCRYPTION_KEY=replace_with_32_random_bytes
+
+HTTP_HOST=panel.example.com
+HTTP_PORT=8025
+```
+
+Todo lo demás recurre a los valores predeterminados.
diff --git a/es/daemon/daemon.md b/es/daemon/daemon.md
new file mode 100644
index 0000000..9734160
--- /dev/null
+++ b/es/daemon/daemon.md
@@ -0,0 +1,271 @@
+---
+title: GameAP Daemon
+layout: default
+lang: es
+category: GameAP Daemon
+order: 400
+---
+
+GameAP Daemon es una aplicación en segundo plano que se ejecuta en un servidor dedicado y gestiona los servidores
+de juego: los instala, elimina, inicia y detiene, supervisa su estado y ejecuta
+comandos del panel.
+
+El propio daemon se conecta al panel a través de gRPC y mantiene una conexión persistente. El panel
+no se conecta al daemon, y el daemon no abre puertos entrantes en el servidor dedicado.
+
+
+
+## Instalación
+
+### Automáticamente desde el panel
+
+En el panel, vaya a **Administración** → **Servidores dedicados** → **Crear**, copie el comando
+y ejecútelo en el servidor dedicado.
+
+
+
+La guía de instalación completa, que incluye Windows, el registro manual y la solución de problemas, está en
+la página [Servidores dedicados](/es/gameap_configure/dedicated_servers.html).
+
+### Gestor de procesos
+
+Un gestor de procesos es una utilidad del sistema que inicia, detiene y reinicia los servidores de juego y supervisa
+su estado. Se puede elegir durante la instalación, en el bloque "Configuración avanzada".
+
+Si no se especifica ningún gestor, el daemon elige uno por sí mismo: en Linux — `systemd`, con una opción
+alternativa cuando systemd no está disponible o el daemon se ejecuta en un contenedor; en Windows —
+[Shawl](https://github.com/mtkennerly/shawl); en macOS — `tmux`.
+
+Consulte [Gestores de procesos](/es/daemon/process_managers.html) para más detalles.
+
+## Configuración
+
+El archivo de configuración está en formato YAML:
+
+* Linux — `/etc/gameap-daemon/gameap-daemon.yaml`
+* Windows — `C:\gameap\daemon\gameap-daemon.yaml`
+
+La ruta se puede establecer explícitamente con la opción `--config` (`-c`). Sin esta opción, el daemon
+busca en una lista de rutas: primero el directorio actual, luego `/etc/gameap-daemon/`,
+`/etc/gameap/`, después `/etc/gameap-daemon.yaml` y el directorio personal del usuario. Se utiliza el primer
+archivo encontrado.
+
+> La lista de búsqueda todavía contiene nombres con la extensión `.cfg`, un remanente de GameAP 3.
+> Dicho archivo será encontrado, pero **solo se procesa YAML**: el daemon se cerrará con un
+> error de formato no compatible. Utilice `.yaml` o `.yml`.
+
+Las claves desconocidas en el archivo se ignoran. Las rutas relativas a los archivos de certificados se resuelven
+respecto al directorio en el que se encuentra el propio archivo de configuración.
+
+### Parámetros obligatorios
+
+| Parámetro | Tipo | Descripción |
+|-----------------|--------|----------------------------------------------------------------|
+| `ds_id` | number | ID del servidor dedicado en el panel |
+| `api_key` | string | Clave de acceso a la API del panel |
+| `grpc.address` | string | Dirección del panel en formato `host:port` |
+
+Los tres se rellenan automáticamente cuando se registra el daemon.
+
+Si `grpc.address` no está definido, la dirección se deriva del parámetro obsoleto `api_host`: se
+toma el nombre del host y el puerto se sustituye por `31718`. Definir `grpc.address` explícitamente es
+más fiable: `api_host` se conserva solo por compatibilidad.
+
+### Conexión con el panel
+
+| Parámetro | Predeterminado | Descripción |
+|--------------------------------|----------------|--------------------------------------------------------------|
+| `grpc.address` | — | Dirección del panel `host:port` |
+| `grpc.insecure` | `false` | Desactiva TLS. Solo para depuración |
+| `grpc.heartbeat_interval` | `30s` | Intervalo de envío de heartbeat |
+| `grpc.connect_timeout` | `30s` | Tiempo de espera para establecer la conexión |
+| `grpc.initial_reconnect_delay` | `1s` | Retraso inicial antes de la reconexión |
+| `grpc.max_reconnect_delay` | `60s` | Retraso máximo antes de la reconexión |
+
+Más información sobre el protocolo, la reconexión y la autenticación mutua: [GRPC API](/es/daemon/grpc.html).
+
+### Certificados
+
+La conexión con el panel está protegida por TLS, y el daemon presenta un certificado de cliente.
+Los tres archivos son emitidos por el panel durante el registro.
+
+| Parámetro | Descripción |
+|--------------------------|---------------------------------------------------------------------|
+| `ca_certificate_file` | Ruta al certificado de la autoridad de certificación del panel |
+| `certificate_chain_file` | Ruta al certificado del daemon |
+| `private_key_file` | Ruta a la clave privada del daemon |
+| `private_key_password` | Contraseña de la clave privada, si la clave está cifrada |
+| `ca_certificate` | Certificado de la CA directamente en el archivo de configuración |
+| `certificate_chain` | Certificado del daemon directamente en el archivo de configuración |
+| `private_key` | Clave privada directamente en el archivo de configuración |
+
+Para cada uno de los tres certificados, defina la ruta del archivo o el contenido: el valor en el
+archivo de configuración tiene prioridad. Con `grpc.insecure: true`, no se requieren certificados.
+
+### Rutas
+
+| Parámetro | Predeterminado | Descripción |
+|-----------------|---------------------|--------------------------------------------------------------------|
+| `work_path` | — | Directorio de trabajo. Los archivos de los servidores de juego se encuentran en sus subdirectorios |
+| `tools_path` | `{work_path}/tools` | Directorio para herramientas auxiliares |
+| `steamcmd_path` | — | Directorio de SteamCMD |
+| `path_7zip` | — | Ruta a 7-Zip. Solo Windows |
+| `path_starter` | — | Ruta al programa starter. Solo Windows |
+
+Valores predeterminados establecidos durante la instalación: `work_path` — `/srv/gameap` en Linux y `C:\gameap` en
+Windows, `steamcmd_path` — `/srv/gameap/steamcmd` y `C:\gameap\steamcmd` respectivamente.
+
+### Registro (logging)
+
+| Parámetro | Predeterminado | Descripción |
+|--------------|----------------|-----------------------------------------------------|
+| `log_level` | `info` | `debug`, `info`, `warn`, `error` |
+| `output_log` | — | Archivo de registro para mensajes normales |
+| `error_log` | — | Archivo de registro de errores |
+
+Después de la instalación, el registro se escribe en `/var/log/gameap-daemon/output.log` en Linux y
+`C:\gameap\daemon\logs\output.log` en Windows.
+
+### Recolección de métricas
+
+| Parámetro | Predeterminado | Descripción |
+|-------------------------------|----------------|----------------------------------------------------------------|
+| `metrics.enabled` | `true` | Recolección de métricas |
+| `metrics.collection_interval` | `5s` | Intervalo de muestreo, no inferior a `1s` |
+| `metrics.retention_duration` | `10m` | Tiempo de conservación de las muestras. Rango permitido de `10m` a `60m` |
+| `if_list` | — | Interfaces de red sobre las que se recopilan métricas |
+| `drives_list` | — | Unidades sobre las que se recopilan métricas |
+
+Los valores de `retention_duration` fuera del rango permitido se ajustan a sus límites.
+
+### Ejecución de tareas
+
+| Parámetro | Predeterminado | Descripción |
+|--------------------------------|----------------|---------------------------------------------------------|
+| `task_manager.run_task_period` | `10ms` | Intervalo de sondeo de la cola de tareas |
+| `task_manager.task_timeout` | `2h` | Tiempo máximo de ejecución de una sola tarea |
+| `task_manager.workers_count` | — | Número de tareas ejecutadas simultáneamente |
+
+### Gestor de procesos
+
+| Parámetro | Predeterminado | Descripción |
+|--------------------------|------------------------|----------------------------------------------|
+| `process_manager.name` | detectado automáticamente | Nombre del gestor de procesos |
+| `process_manager.config` | — | Parámetros adicionales del gestor |
+
+El único parámetro adicional admitido es `scope` con el valor `system` o `user`, y solo
+para `systemd`. Para otros gestores provoca un error al iniciar.
+
+```yaml
+process_manager:
+ name: systemd
+ config:
+ scope: user
+```
+
+### Cuenta de Steam
+
+Muchos servidores de juego no se pueden descargar a través de SteamCMD de forma anónima: se necesita
+una cuenta con una copia comprada del juego.
+
+```yaml
+steam_config:
+ login: your_login
+ password: your_password
+ group: gameap
+```
+
+El parámetro `group` establece un grupo compartido para el directorio de SteamCMD, de modo que los servidores
+de juego que se ejecutan con sus propios usuarios puedan actualizar SteamCMD. Si no se establece, se utiliza
+el grupo principal del usuario del servidor.
+
+> La autenticación de dos factores debe estar desactivada en la cuenta de Steam utilizada; de lo contrario,
+> el daemon no podrá iniciar sesión en SteamCMD.
+
+### Sustitución de direcciones de repositorio
+
+Permite sustituir el host desde el que se descargan los archivos de los servidores de juego, por ejemplo,
+por un espejo más cercano.
+
+```yaml
+remote_repository_replacements:
+ files.gameap.ru: cdn.gameap.com
+ files.gameap.com:
+ - cdn1.gameap.com
+ - replace: cdn2.gameap.com
+ priority: 10
+```
+
+La primera línea cubre el caso más común: `files.gameap.ru` es una dirección retirada, y las entradas
+de juegos creadas antes de su desactivación todavía apuntan a ella. Esta sustitución las corrige sin
+editar cada juego.
+
+El valor puede ser una sola dirección o una lista. Los elementos de la lista pueden especificar una
+`priority`: cuanto mayor sea el número, mayor será la prioridad.
+
+### Parámetros de Windows
+
+| Parámetro | Predeterminado | Descripción |
+|-----------------------------|----------------|----------------------------------------------------------------|
+| `use_network_service_user` | `false` | Ejecutar los servidores de juego como `NT AUTHORITY\NETWORK SERVICE` |
+| `users` | — | Contraseñas de los usuarios con los que se ejecutan los servidores de juego |
+
+Con `use_network_service_user: true`, los servidores se ejecutan bajo una cuenta del sistema con
+privilegios limitados: esta es la opción predeterminada al instalar mediante `gameapctl`. De lo contrario,
+se utilizan las cuentas del bloque `users`:
+
+```yaml
+users:
+ gameap_user1: password
+ gameap_user2: base64:cGFyb2xi
+```
+
+La contraseña se puede escribir codificada en base64 con el prefijo `base64:`.
+
+## Ejemplo de archivo de configuración
+
+```yaml
+ds_id: 1
+api_key: your_key
+
+grpc:
+ address: panel.example.com:31718
+
+ca_certificate_file: /etc/gameap-daemon/certs/ca.crt
+certificate_chain_file: /etc/gameap-daemon/certs/server.crt
+private_key_file: /etc/gameap-daemon/certs/server.key
+
+work_path: /srv/gameap
+steamcmd_path: /srv/gameap/steamcmd
+
+if_list: []
+drives_list: []
+
+log_level: info
+```
+
+Este es exactamente el archivo que crea el comando de registro. Un ejemplo completo con todos
+los parámetros y comentarios está disponible
+[en el repositorio del daemon](https://github.com/gameap/daemon/blob/master/config/gameap-daemon.yaml).
+
+## Gestión del servicio
+
+### Linux
+
+```bash
+systemctl start gameap-daemon
+systemctl stop gameap-daemon
+systemctl restart gameap-daemon
+systemctl status gameap-daemon
+```
+
+Actualización:
+
+```bash
+gameapctl daemon upgrade
+```
+
+### Windows
+
+El daemon se ejecuta como el servicio **GameAP Daemon**. Se puede gestionar mediante `gameapctl.exe` o con
+las herramientas estándar de Windows.
diff --git a/es/daemon/grpc.md b/es/daemon/grpc.md
new file mode 100644
index 0000000..2fa0859
--- /dev/null
+++ b/es/daemon/grpc.md
@@ -0,0 +1,215 @@
+---
+title: API gRPC
+layout: default
+lang: es
+category: GameAP Daemon
+order: 405
+---
+
+A partir de GameAP 4.2 y GameAP Daemon 4.0, el panel y el daemon intercambian datos a través de gRPC
+mediante un flujo bidireccional (bidirectional streaming). Este método ha sustituido al antiguo
+intercambio a través de BINN y la API REST.
+
+La conexión la establece el daemon: se conecta al panel por sí mismo y mantiene un único flujo de
+larga duración que lo transporta todo: el registro, el heartbeat, las métricas, las tareas, los
+comandos, la consola y las operaciones con archivos. El panel no se conecta al daemon y no requiere
+puertos entrantes en el servidor dedicado.
+
+## Configuración del panel
+
+No existe un ajuste separado para activar gRPC: el servidor siempre se inicia. Solo se pueden
+configurar la dirección, el cifrado y los límites.
+
+| Variable | Predeterminado | Propósito |
+|-------------------------------|----------------|---------------------------------------------------------------------------|
+| `GRPC_PORT` | `31718` | Puerto en el que escucha el servidor gRPC del panel |
+| `GRPC_TLS_ENABLED` | `true` | Cifrado de la conexión |
+| `GRPC_REQUIRE_MTLS` | `false` | Exigir un certificado de cliente al daemon |
+| `GRPC_EXTERNAL_HOST` | `""` | Dirección del panel que se comunica al daemon. Vacío: se determina a partir de la petición |
+| `GRPC_EXTERNAL_PORT` | `0` | Puerto que se comunica al daemon. `0`: se usa `GRPC_PORT` |
+| `GRPC_MAX_RECV_MSG_SIZE` | `10485760` | Tamaño máximo de un mensaje entrante, en bytes |
+| `GRPC_MAX_SEND_MSG_SIZE` | `10485760` | Tamaño máximo de un mensaje saliente, en bytes |
+| `GRPC_MAX_CONCURRENT_STREAMS` | `100` | Número de flujos simultáneos por conexión |
+| `GRPC_ENABLE_REFLECTION` | `false` | Reflexión del esquema para herramientas de depuración como `grpcurl`. No lo active en producción |
+
+> La variable `GRPC_ENABLED` **no existe**: el panel no la lee. Las versiones antiguas de
+> `gameapctl` añaden una línea `GRPC_ENABLED=true` a `config.env`: es inofensiva, pero no tiene
+> ningún efecto. El servidor gRPC no se puede desactivar.
+
+### Puertos
+
+gRPC funciona en un **puerto separado, 31718**, mientras que la interfaz web y la API funcionan en
+`HTTP_PORT` (`8025` por defecto). Son dos listeners diferentes, no un único puerto con detección de
+protocolo.
+
+El puerto 31718 debe ser accesible desde cada servidor dedicado. Es el único puerto del panel que
+necesita un daemon en funcionamiento.
+
+> En el `Dockerfile` y el `docker-compose.yml` del panel solo se publica el puerto 8025. Al
+> desplegar con Docker, debe redirigir el puerto 31718 usted mismo.
+
+### Dirección del panel para el daemon
+
+El panel sustituye su propia dirección en el comando de instalación del daemon y en una URL de
+conexión de la forma `grpc://host:port/key`. Por defecto, el host se toma de la cabecera de la
+petición con la que el administrador abrió la página de creación del servidor dedicado, es decir,
+de la dirección en la barra de direcciones del navegador.
+
+Establezca `GRPC_EXTERNAL_HOST` si esa dirección difiere de aquella a la que deben conectarse los
+daemons:
+
+* el panel está detrás de un proxy inverso, gRPC no pasa a través del proxy y los daemons tienen
+ que conectarse directamente;
+* el panel está detrás de NAT y tiene direcciones diferentes dentro y fuera;
+* el panel se ejecuta en Docker, donde la cabecera acaba conteniendo `localhost` o el nombre del
+ contenedor.
+
+`GRPC_EXTERNAL_PORT` es necesario cuando el puerto 31718 se publica externamente con un número
+diferente.
+
+Si las variables no están establecidas, el propio servidor gRPC funciona correctamente; solo la
+dirección del comando de instalación generado resulta incorrecta, y el daemon no podrá conectarse.
+
+> `GRPC_EXTERNAL_HOST` se incluye en la lista de nombres alternativos del sujeto (SAN) del
+> certificado gRPC autofirmado. El certificado se crea una sola vez, en el primer arranque, por lo
+> que la variable debe establecerse **antes** de iniciar el panel por primera vez. Si la establece
+> más tarde, el daemon rechazará la conexión debido a la discrepancia del nombre en el certificado:
+> elimine `certs/server/api-server.crt` y `certs/server/api-server.key` y reinicie el panel para
+> que el certificado se genere de nuevo.
+
+### Cifrado y certificados
+
+Con `GRPC_TLS_ENABLED=true` (el valor por defecto) el panel utiliza un certificado autofirmado
+emitido por su propia autoridad certificadora interna: `certs/root.crt` y `certs/root.key`. El
+certificado del servidor es `certs/server/api-server.crt`. Las claves son RSA de 2048 bits, válidas
+durante 10 años, y todo se crea automáticamente en el primer uso.
+
+Estos certificados no tienen nada que ver con el certificado HTTPS propio del panel: ACME y Let's
+Encrypt no se aplican a gRPC, y no es necesario configurarlos por separado.
+
+Durante el registro, el daemon recibe `ca.crt`, `server.crt` y `server.key` del panel y los coloca
+en su directorio de certificados. A partir de entonces verifica el certificado del panel contra la
+CA recibida y presenta su propio certificado de cliente.
+
+Además, cada petición se autentica con la clave API del nodo, que se almacena en la base de datos
+del panel como un hash SHA-256 y se compara en tiempo constante.
+
+### Autenticación mutua (mTLS)
+
+`GRPC_REQUIRE_MTLS=true` hace que el panel exija al cliente un certificado emitido por su propia
+autoridad certificadora y rechace las peticiones sin él.
+
+No es necesario configurar el daemon de forma especial para esto: un daemon registrado siempre
+presenta su certificado de todos modos.
+
+> **Active mTLS solo después de que todos los daemons hayan sido registrados.** El registro en sí
+> ocurre sin un certificado de cliente: un daemon nuevo aún no lo tiene. Con
+> `GRPC_REQUIRE_MTLS=true` no podrá registrar un nuevo servidor dedicado. Para añadir un nodo más
+> tarde, vuelva a establecerlo temporalmente en `false`, registre el daemon y actívelo de nuevo.
+
+Los daemons registrados por otro panel no funcionarán: sus certificados están emitidos por una
+autoridad certificadora ajena.
+
+## Configuración del daemon
+
+Los parámetros de conexión se establecen en la configuración del daemon —
+`/etc/gameap-daemon/gameap-daemon.yaml` en Linux, `C:\gameap\daemon\gameap-daemon.yaml` en
+Windows — en el bloque `grpc`:
+
+```yaml
+grpc:
+ address: panel.example.com:31718
+ insecure: false
+ heartbeat_interval: 30s
+ connect_timeout: 30s
+ initial_reconnect_delay: 1s
+ max_reconnect_delay: 60s
+```
+
+| Parámetro | Predeterminado | Propósito |
+|---------------------------|----------------|------------------------------------------------------------------|
+| `address` | — | Dirección del panel en formato `host:port` |
+| `insecure` | `false` | Desactivar TLS. Solo para depuración |
+| `heartbeat_interval` | `30s` | Intervalo de heartbeat. El panel puede asignar su propio valor |
+| `connect_timeout` | `30s` | Tiempo de espera para establecer la conexión |
+| `initial_reconnect_delay` | `1s` | Pausa inicial antes de la reconexión |
+| `max_reconnect_delay` | `60s` | Pausa máxima antes de la reconexión |
+
+Si `address` no está establecido, se deriva del parámetro obsoleto `api_host`: se toma el nombre
+del host y el puerto se sustituye por 31718. Establecer `address` explícitamente es más fiable.
+
+> El daemon tampoco tiene la clave `grpc.enabled`. `gameapctl` la escribe durante la migración como
+> un marcador de que la migración se ha realizado; el daemon ignora esta clave.
+
+Los demás parámetros de configuración del daemon se describen en la página
+[GameAP Daemon](/es/daemon/daemon.html).
+
+### Reconexión y pérdida de conexión
+
+Si se pierde la conexión, el daemon se reconecta por sí mismo, con un retardo exponencial: se
+duplica desde `initial_reconnect_delay` hasta `max_reconnect_delay` y se dispersa con un jitter
+aleatorio de ±10 %, para que muchos daemons no lleguen a la vez. Con los valores por defecto es
+1 s, 2 s, 4 s, 8 s y así sucesivamente hasta 60 s. Tras una conexión exitosa, el contador se
+reinicia. En un apagado planificado, el panel puede asignar al daemon una pausa antes de su próximo
+intento.
+
+Mientras el panel no está disponible, **los servidores de juego siguen funcionando**: solo se
+interrumpe su gestión desde el panel. Una vez restaurada la conexión, el daemon se registra de
+nuevo, informa de las tareas que está ejecutando y recibe el estado actual completo del panel: la
+lista de servidores, tareas, juegos y modificaciones, y la configuración de los servidores. Por eso
+las tareas iniciadas antes de la desconexión no se pierden.
+
+> El mecanismo keepalive de gRPC no se utiliza en ninguno de los dos lados: la vitalidad de la
+> conexión depende únicamente del heartbeat cada 30 segundos. Si hay NAT o un firewall entre el
+> daemon y el panel que cierra las conexiones inactivas antes, reduzca `heartbeat_interval`.
+
+## Migración desde el protocolo antiguo
+
+Un daemon instalado antes de la aparición de gRPC se cambia al nuevo protocolo con el comando:
+
+```bash
+gameapctl daemon upgrade --switch-to-grpc
+```
+
+Qué hace el comando:
+
+1. Comprueba que la migración aún no se ha realizado y determina la dirección del panel a partir de
+ `api_host` (o la toma de `--grpc-address`).
+2. Comprueba que la configuración contiene `api_key`, `ds_id` y los tres archivos de certificados.
+3. **Antes de hacer cualquier cambio**, comprueba que el panel es accesible: establece una conexión
+ TCP y realiza un handshake TLS real con los certificados existentes. Si los certificados fueron
+ emitidos por otro panel, el comando lo informará y sugerirá una reinstalación.
+4. Crea una copia de seguridad de la configuración junto al original, con una marca de tiempo en el
+ nombre.
+5. Escribe la dirección gRPC y elimina los obsoletos `api_host`, `listen_ip` y `listen_port`.
+6. Reinicia el daemon y se asegura de que el panel ha revocado el acceso a través de la antigua API
+ HTTP.
+7. Ante cualquier fallo después del paso 5, restaura la configuración desde la copia de seguridad y
+ arranca el daemon de nuevo.
+
+Todo lo que se requiere es que el puerto 31718 del panel sea accesible desde el servidor dedicado.
+No hay que activar nada en el lado del panel; contrariamente a lo que dicen el propio mensaje de
+error del comando y su descripción, la variable `GRPC_ENABLED` no existe.
+
+## Verificación
+
+El panel responde al health check estándar de gRPC e informa del estado `SERVING` para los
+servicios `gameap.DaemonGateway` y `gameap.FileTransferService`.
+
+La comprobación más sencilla de que el puerto es accesible desde el servidor dedicado:
+
+```bash
+nc -zv panel.example.com 31718
+```
+
+El estado de conexión del daemon es visible en el panel en la página **«Administración»** →
+**«Servidores dedicados»**. Los detalles están en el log del daemon:
+`/var/log/gameap-daemon/output.log` en Linux, `C:\gameap\daemon\logs\output.log` en Windows.
+
+Mensajes típicos en el log del daemon:
+
+| Mensaje | Qué significa |
+|--------------------------------|-------------------------------------------------------------------------------|
+| `gRPC connection failed` | La conexión no se estableció o se interrumpió; le siguen una pausa y un nuevo intento |
+| `registration failed: ...` | Hay conexión, pero el panel rechazó el registro: `ds_id` o `api_key` incorrectos |
+| Error de verificación del certificado | El nombre del certificado del panel no coincide con la dirección de conexión. Véase `GRPC_EXTERNAL_HOST` |
diff --git a/es/daemon/process_managers.md b/es/daemon/process_managers.md
new file mode 100644
index 0000000..7a786de
--- /dev/null
+++ b/es/daemon/process_managers.md
@@ -0,0 +1,253 @@
+---
+title: Gestores de procesos
+layout: default
+lang: es
+category: GameAP Daemon
+order: 410
+---
+
+Un gestor de procesos es una utilidad del sistema que administra los procesos en el servidor.
+Se encarga de iniciar, detener y reiniciar los servidores de juego, supervisar su estado, recopilar estadísticas y limitar los recursos (CPU / RAM).
+De forma predeterminada, se utiliza systemd en Linux y [Shawl](https://github.com/mtkennerly/shawl) en Windows.
+
+## Configuración
+
+El gestor de procesos se configura en el archivo de configuración de GameAP Daemon.
+
+### Estructura básica
+
+```yaml
+process_manager:
+ name:
+ config:
+ :
+```
+
+**Gestores disponibles:**
+- Linux: `systemd` (predeterminado), `docker`, `podman`, `tmux`, `simple`
+- Windows: `shawl` (predeterminado), `winsw`, `simple`
+
+## Linux
+
+### Systemd
+
+Se utiliza de forma predeterminada en Linux. Es un gestor de procesos moderno que ofrece alto rendimiento y fiabilidad.
+Las capacidades de aislamiento de este gestor de procesos son limitadas.
+
+| Función | |
+|----------------------------------------|-----------------|
+| Inicio, detención y reinicio de servidores | ✅ |
+| Estadísticas | ⚠️ |
+| Límites de recursos (CPU / RAM) | ✅ |
+| Lectura de consola | ✅ |
+| Envío de comandos a la consola | ✅ |
+| Aislamiento | ⚠️ Limitado |
+
+#### Configuración de Systemd
+
+Systemd no requiere configuración adicional. El Daemon crea y administra automáticamente los archivos de unidad.
+
+##### Ejemplo de configuración
+
+```yaml
+process_manager:
+ name: systemd
+```
+
+### Docker
+
+Docker se utiliza para aislar los servidores de juego en contenedores.
+Utilice Docker si planea usar Pterodactyl Eggs o Pelican Eggs.
+
+| Función | |
+|----------------------------------------|----|
+| Inicio, detención y reinicio de servidores | ✅ |
+| Estadísticas | ⚠️ |
+| Límites de recursos (CPU / RAM) | ✅ |
+| Lectura de consola | ✅ |
+| Envío de comandos a la consola | ✅ |
+| Aislamiento | ✅ |
+
+#### Configuración de Docker
+
+##### Ejemplos de configuración
+
+Mínima:
+```yaml
+process_manager:
+ name: docker
+```
+
+### Podman
+
+Podman es una alternativa a Docker que proporciona aislamiento de los servidores de juego en contenedores.
+También puede utilizar Podman si planea usar Pterodactyl Eggs o Pelican Eggs.
+
+| Función | |
+|----------------------------------------|----|
+| Inicio, detención y reinicio de servidores | ✅ |
+| Estadísticas | ⚠️ |
+| Límites de recursos (CPU / RAM) | ✅ |
+| Lectura de consola | ✅ |
+| Envío de comandos a la consola | ✅ |
+| Aislamiento | ✅ |
+
+#### Configuración de Podman
+
+Podman utiliza los mismos parámetros que Docker, más un parámetro específico para conectarse a la API.
+
+##### Parámetros específicos
+
+| Clave de configuración | Descripción | Valor predeterminado |
+|---------------|-------------|---------|
+| `socket_path` | Ruta al socket unix de Podman | `unix:///run/user/{UID}/podman/podman.sock` (rootless) |
+
+##### Ejemplos de configuración
+
+Rootless (predeterminado):
+```yaml
+process_manager:
+ name: podman
+```
+
+Rootful:
+```yaml
+process_manager:
+ name: podman
+ config:
+ socket_path: "unix:///run/podman/podman.sock"
+```
+
+### Tmux
+
+[Tmux](https://github.com/tmux/tmux) es un multiplexor de terminal.
+Actualmente es un gestor de procesos obsoleto en GameAP,
+cuyo uso no se recomienda, ya que no proporciona funcionalidad completa.
+
+Tmux puede utilizarse en sistemas antiguos, así como dentro de contenedores (LXC, Docker/Podman, etc.), sistemas virtuales
+y sistemas que no disponen de Systemd o no pueden usar Docker o Podman.
+
+| Función | |
+|----------------------------------------|----|
+| Inicio, detención y reinicio de servidores | ✅ |
+| Estadísticas | ⚠️ |
+| Límites de recursos (CPU / RAM) | ❌ |
+| Lectura de consola | ✅ |
+| Envío de comandos a la consola | ✅ |
+| Aislamiento | ❌ |
+
+#### Configuración de Tmux
+
+Tmux no requiere configuración adicional.
+
+##### Ejemplo de configuración
+
+```yaml
+process_manager:
+ name: tmux
+```
+
+## Windows
+
+### Shawl
+
+[Shawl](https://github.com/mtkennerly/shawl) es un gestor de procesos ligero para Windows
+que proporciona funcionalidad básica para administrar servidores de juego.
+Está escrito en Rust y utiliza la API de Windows para ejecutar aplicaciones como servicios de Windows.
+
+| Función | |
+|----------------------------------------|----|
+| Inicio, detención y reinicio de servidores | ✅ |
+| Estadísticas | ⚠️ |
+| Límites de recursos (CPU / RAM) | ❌ |
+| Lectura de consola | ✅ |
+| Envío de comandos a la consola | ❌ |
+| Aislamiento | ❌ |
+
+#### Configuración de Shawl
+
+Shawl no requiere configuración adicional.
+
+##### Detalles de funcionamiento
+
+| Parámetro | Valor |
+|-----------|-------|
+| Directorio de configuración | `C:\gameap\services` |
+| Tiempo de espera de detención | 10000 ms |
+| Rotación de registros | Diaria |
+| Retención de registros | 7 días |
+
+##### Ejemplo de configuración
+
+```yaml
+process_manager:
+ name: shawl
+```
+
+### WinSW
+
+[WinSW](https://github.com/winsw/winsw) (Windows Service Wrapper) es un gestor de procesos escrito en C#.
+Permite ejecutar aplicaciones como servicios de Windows.
+En GameAP es un gestor obsoleto y ha sido reemplazado por Shawl.
+
+| Función | |
+|----------------------------------------|----|
+| Inicio, detención y reinicio de servidores | ✅ |
+| Estadísticas | ⚠️ |
+| Límites de recursos (CPU / RAM) | ❌ |
+| Lectura de consola | ✅ |
+| Envío de comandos a la consola | ❌ |
+| Aislamiento | ❌ |
+
+#### Configuración de WinSW
+
+WinSW no requiere configuración adicional.
+
+##### Detalles de funcionamiento
+
+| Parámetro | Valor |
+|-----------|-------|
+| Directorio de configuración | `C:\gameap\services` |
+| Formato de configuración | XML |
+
+##### Ejemplo de configuración
+
+```yaml
+process_manager:
+ name: winsw
+```
+
+## Linux y Windows
+
+### Simple
+
+Simple no recurre a ningún servicio del sistema. Inicia el servidor de juego mediante las plantillas
+de comandos configuradas para el servidor dedicado y no sabe nada del proceso más allá de lo que
+informan esos comandos.
+
+En Linux el Daemon recurre a él automáticamente cuando no hay ni systemd ni tmux disponibles.
+En Windows puede seleccionarse de forma explícita.
+
+| Función | |
+|----------------------------------------|----|
+| Inicio, detención y reinicio de servidores | ✅ |
+| Estadísticas | ❌ |
+| Límites de recursos (CPU / RAM) | ❌ |
+| Lectura de consola | ✅ |
+| Envío de comandos a la consola | ✅ |
+| Aislamiento | ❌ |
+
+El Daemon no guarda el identificador del proceso, por lo que al panel solo llega el indicador de
+«servidor en ejecución»: no hay gráficos de CPU, memoria ni red, y no se pueden aplicar límites de
+recursos. Elija Simple solo cuando no haya ninguna otra opción disponible en el sistema.
+
+#### Configuración de Simple
+
+Simple no requiere configuración adicional.
+
+##### Ejemplo de configuración
+
+```yaml
+process_manager:
+ name: simple
+```
diff --git a/es/database.md b/es/database.md
new file mode 100644
index 0000000..b378c9a
--- /dev/null
+++ b/es/database.md
@@ -0,0 +1,194 @@
+---
+title: Base de datos
+layout: default
+lang: es
+category: Administración
+order: 335
+---
+
+El panel funciona con PostgreSQL, MySQL o MariaDB, y SQLite. La elección se define con dos
+variables en `config.env`:
+
+```dotenv
+DATABASE_DRIVER=postgres
+DATABASE_URL=postgres://gameap:password@localhost:5432/gameap?sslmode=disable
+```
+
+## Cuál elegir
+
+| SGBD | Cuándo conviene |
+|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **SQLite** | Un solo panel, hasta unas pocas docenas de servidores de juego. No requiere un servicio aparte |
+| **PostgreSQL** | Cuando se necesita alto rendimiento. Recomendado para ejecutar varias instancias del panel |
+| **MySQL, MariaDB** | La opción familiar, incluso al actualizar desde GameAP 3. Ligeramente más lento que PostgreSQL, pero aún adecuado para varias instancias |
+
+Para una instalación típica en un solo servidor, SQLite es suficiente: no requiere un servicio
+aparte, ni configuración, ni una copia de seguridad más compleja que copiar un archivo.
+
+## Cadena de conexión
+
+### PostgreSQL
+
+```dotenv
+DATABASE_DRIVER=postgres
+DATABASE_URL=postgres://user:password@host:5432/database?sslmode=disable
+```
+
+Los nombres del driver son intercambiables: `postgres`, `postgresql`, `pgsql`, `pg`, `pgx`.
+
+El parámetro `sslmode` define el modo de cifrado de la conexión: `disable` para una base de
+datos local, `require` o `verify-full` para una remota.
+
+### MySQL y MariaDB
+
+```dotenv
+DATABASE_DRIVER=mysql
+DATABASE_URL=user:password@tcp(host:3306)/database?parseTime=true
+```
+
+El formato difiere de la URL habitual con esquema — es el formato del driver de Go. El
+parámetro `parseTime=true` es obligatorio.
+
+Conexión a través de un socket:
+
+```dotenv
+DATABASE_URL=user:password@unix(/var/run/mysqld/mysqld.sock)/database?parseTime=true
+```
+
+### SQLite
+
+```dotenv
+DATABASE_DRIVER=sqlite
+DATABASE_URL=file:/var/lib/gameap/db.sqlite?_busy_timeout=5000&_journal_mode=WAL&cache=shared
+```
+
+El archivo se crea automáticamente. Los parámetros del ejemplo activan el journaling WAL y la
+espera de bloqueos — consérvelos, mejoran notablemente el comportamiento con peticiones
+concurrentes.
+
+El directorio con el archivo de la base de datos debe tener permisos de escritura para el
+usuario con el que se ejecuta el panel.
+
+### inmemory
+
+```dotenv
+DATABASE_DRIVER=inmemory
+DATABASE_URL=inmemory
+```
+
+Los datos se mantienen solo en la RAM y se pierden al reiniciar. Está pensado para pruebas; no
+es adecuado para una instalación en producción.
+
+`DATABASE_URL` hay que definirla incluso aquí: el panel comprueba que no esté vacía antes de
+conocer el driver y sin ella no arranca. El valor en sí no se utiliza.
+
+## Migraciones
+
+El panel aplica las migraciones por sí mismo al arrancar — no existe un comando aparte. La
+versión del esquema se guarda en una tabla de servicio dentro de la misma base de datos.
+
+De esto se derivan dos reglas prácticas:
+
+* **Una actualización del panel cambia el esquema en el primer arranque.** Haga la copia de
+ seguridad antes.
+* **Volver a una versión anterior del panel sin restaurar la base de datos no funcionará** —
+ el esquema ya ha cambiado.
+
+Las migraciones se aplican incluso con huecos en la numeración, así que saltarse una versión
+intermedia del panel durante una actualización no es problema.
+
+## Copia de seguridad
+
+El panel no hace copias de seguridad — ni por sí mismo ni mediante `gameapctl`. Configure las
+copias de seguridad con las herramientas del SGBD.
+
+### PostgreSQL
+
+```bash
+pg_dump -U gameap gameap > gameap-$(date +%F).sql
+```
+
+Restauración:
+
+```bash
+psql -U gameap gameap < gameap-2026-08-02.sql
+```
+
+### MySQL y MariaDB
+
+```bash
+mysqldump -u gameap -p gameap > gameap-$(date +%F).sql
+```
+
+Restauración:
+
+```bash
+mysql -u gameap -p gameap < gameap-2026-08-02.sql
+```
+
+### SQLite
+
+No es necesario detener el panel si utiliza el comando incorporado:
+
+```bash
+sqlite3 /var/lib/gameap/db.sqlite ".backup '/backup/gameap-$(date +%F).sqlite'"
+```
+
+Copiar simplemente el archivo mientras el panel está en ejecución puede producir una copia
+corrupta debido al journal WAL.
+
+### Qué más guardar
+
+La base de datos no es suficiente. Junto con ella, guarde:
+
+* **`config.env`** — contiene `AUTH_SECRET` y `ENCRYPTION_KEY`. Sin `ENCRYPTION_KEY`,
+ algunos datos de la copia de seguridad no podrán restaurarse, y la autenticación de dos
+ factores dejará de funcionar para todos los usuarios;
+* **el directorio de archivos del panel** — contiene los certificados gRPC con los que se
+ conectan los daemons y los datos de ACME. La ruta se define con `FILES_LOCAL_BASE_PATH`.
+
+Los archivos de los servidores de juego residen en los servidores dedicados y no forman parte
+de la copia de seguridad del panel.
+
+## Cambiar a otro SGBD
+
+No existe una transferencia de datos incorporada entre SGBD: los esquemas de PostgreSQL, MySQL
+y SQLite se crean de forma independiente, y el panel no proporciona ninguna herramienta de
+migración.
+
+El procedimiento:
+
+1. Haga una copia de seguridad de la base de datos actual.
+2. Prepare la nueva base de datos y apunte `DATABASE_DRIVER` y `DATABASE_URL` hacia ella.
+3. Inicie el panel — creará el esquema desde cero.
+4. Migre los datos: vuelva a crear los usuarios, servidores dedicados, juegos y servidores de
+ juego manualmente o mediante la [API](/es/api.html).
+
+Los servidores de juego en sí no se ven afectados: sus archivos permanecen en los servidores
+dedicados; basta con describir los servidores en el panel con los mismos directorios y puertos.
+
+Cargar un volcado de un SGBD directamente en otro no funcionará — los tipos de columna y la
+sintaxis difieren.
+
+## Varias instancias del panel
+
+Para una instalación con varias instancias, utilice PostgreSQL o MySQL. SQLite no sirve: no
+está diseñado para que varios clientes trabajen con el archivo de la base de datos al mismo
+tiempo.
+
+Además de la base de datos, necesitará una caché compartida, un intercambio de eventos
+compartido y un almacenamiento de archivos compartido. Consulte
+[Varias instancias del panel](/es/multi_instance.html) para más detalles.
+
+## Comprobación
+
+Si el panel no arranca por culpa de la base de datos, el log contendrá un mensaje de conexión
+o de migración:
+
+```bash
+journalctl -u gameap -n 50 --no-pager
+```
+
+Causas habituales: un formato incorrecto de `DATABASE_URL` (especialmente en MySQL — necesita
+el formato del driver de Go, no una URL con esquema), falta de permisos sobre el directorio del
+archivo SQLite, la base de datos no creada o el usuario sin acceso concedido a ella.
diff --git a/es/gameap_configure/dedicated_servers.md b/es/gameap_configure/dedicated_servers.md
new file mode 100644
index 0000000..0ee7ce2
--- /dev/null
+++ b/es/gameap_configure/dedicated_servers.md
@@ -0,0 +1,219 @@
+---
+title: Servidores dedicados
+layout: default
+lang: es
+category: Configuración del panel
+order: 300
+---
+
+## Nuevo servidor dedicado
+
+Al comenzar a trabajar con el panel, debe agregar un servidor dedicado (VDS/VPS, contenedor, servidor
+físico) en el que se ejecutará GameAP Daemon.
+
+Vaya a la página **"Administración"** → **"Servidores dedicados"** y haga clic en el botón **"Crear"**.
+Se abrirá una ventana con un comando de instalación listo para usar: uno para Linux y otro para Windows.
+
+Los certificados los crea y los entrega al daemon el propio panel: no es necesario generarlos
+ni firmarlos manualmente.
+
+> La clave de configuración es válida durante **1 hora** y es de un solo uso: se revoca una vez que
+> el daemon se ha registrado correctamente. La clave es compartida en todo el panel, por lo que al
+> volver a abrir la ventana "Crear" se genera una nueva clave, y un comando copiado anteriormente
+> deja de funcionar.
+
+### Qué necesita en el servidor dedicado
+
+* Derechos de superusuario (root en Linux, administrador en Windows).
+* Acceso saliente al panel por el puerto **31718/TCP**: el daemon lo utiliza para comunicarse con
+ el panel a través de gRPC. Es el único puerto del panel que necesita un daemon en ejecución.
+* Acceso saliente a `github.com` y `api.github.com`: desde allí se descargan gameapctl y el propio daemon.
+* Para el script de Linux: tener instalados `curl`, `tar` e `install`.
+* Arquitectura: `amd64`, `arm64`, `386` o `arm`.
+
+El puerto **8025** es la interfaz web y la API del panel. Lo necesitan el navegador del administrador
+y el comando `curl` del comando de instalación de una línea, pero un daemon en ejecución no lo requiere.
+
+El puerto **31717** es el que el daemon informa como propio durante el registro. En GameAP 4 el panel
+no establece conexiones entrantes con el daemon, por lo que no es necesario abrir este puerto en el
+servidor dedicado.
+
+### Instalación en Linux
+
+Copie el comando de la pestaña Linux y ejecútelo en el servidor dedicado **como root**:
+
+```bash
+bash <(curl -fsSL 'https://your-panel/nodes/setup/zItWHWlI4RKPl9ZsYc3y3WgdKq7mNvBx')
+```
+
+El script comprueba el entorno, instala `gameapctl` en `/usr/local/bin` (o lo actualiza si ya está
+instalado) y luego ejecuta `gameapctl daemon install`. Esto instala GameAP Daemon, crea el usuario
+`gameap`, instala SteamCMD, registra el daemon en el panel y lo inicia como el servicio systemd
+`gameap-daemon`.
+
+El comando debe ejecutarse como root, no a través de `sudo`: la sustitución de procesos `bash <(...)`
+no sobrevive a `sudo`, y el script se detendrá con un error. Si no está trabajando como root, descargue
+el script a un archivo y ejecútelo:
+
+```bash
+curl -fsSL 'https://your-panel/nodes/setup/zItWHWlI4RKPl9ZsYc3y3WgdKq7mNvBx' -o gameap-setup.sh
+sudo bash gameap-setup.sh
+```
+
+Rutas de instalación:
+
+| Elemento | Ruta |
+|---------------------|-----------------------------------------|
+| Daemon | `/usr/bin/gameap-daemon` |
+| Configuración | `/etc/gameap-daemon/gameap-daemon.yaml` |
+| Certificados | `/etc/gameap-daemon/certs` |
+| Directorio de trabajo | `/srv/gameap` |
+| SteamCMD | `/srv/gameap/steamcmd` |
+| Registros | `/var/log/gameap-daemon/output.log` |
+
+### Instalación en Windows
+
+No hay un comando de PowerShell de una línea; la instalación se realiza a través de gameapctl:
+
+1. Descargue el archivo de gameapctl para su arquitectura desde la página de
+ [versiones de gameapctl](https://github.com/gameap/gameapctl/releases). El archivo se llama
+ `gameapctl--windows-amd64.zip`.
+2. Descomprima el archivo y ejecute `gameapctl.exe`.
+3. En la sección **GameAP Daemon** haga clic en **Install**.
+4. Pegue la cadena de la pestaña Windows del panel en el campo **Connect URL**.
+5. Haga clic en **Install**.
+
+Lo mismo se puede hacer con un comando en la consola:
+
+```shell
+gameapctl daemon install --connect=grpc://your-panel:31718/zItWHWlI4RKPl9ZsYc3y3WgdKq7mNvBx
+```
+
+Rutas de instalación:
+
+| Elemento | Ruta |
+|---------------------|------------------------------------------|
+| Daemon | `C:\gameap\daemon\gameap-daemon.exe` |
+| Configuración | `C:\gameap\daemon\gameap-daemon.yaml` |
+| Certificados | `C:\gameap\daemon\certs` |
+| Directorio de trabajo | `C:\gameap` |
+| SteamCMD | `C:\gameap\steamcmd` |
+| Registros | `C:\gameap\daemon\logs\output.log` |
+
+El daemon se registra como el servicio **GameAP Daemon**.
+
+### Configuración avanzada de la instalación
+
+La ventana de creación tiene un bloque plegable **"Advanced Settings"** (Configuración avanzada):
+
+* **Process manager**: qué debe gestionar los procesos de los servidores de juego. En Linux están
+ disponibles `systemd`, `docker`, `podman`, `tmux` y `simple`; en Windows, `winsw`, `shawl` y
+ `simple`. Se selecciona automáticamente por defecto. Consulte los detalles en la página
+ [Gestores de procesos](/es/daemon/process_managers.html).
+* **GitHub**: compilar el daemon desde el código fuente en lugar de usar una versión lista para usar.
+* **Branch**: rama del repositorio, si el daemon se compila desde el código fuente.
+
+El panel añade los valores seleccionados al comando de instalación:
+
+```bash
+bash <(curl -s '...') --config='process_manager.name=docker' --github --branch=master
+```
+
+### Instalación manual
+
+Si el script automático no se ajusta a sus necesidades —una distribución no estándar, sus propias
+reglas de instalación de paquetes, la instalación en una imagen preparada—, el daemon se puede
+registrar manualmente.
+
+1. Descargue el binario `gameap-daemon` para su plataforma desde la página de
+ [versiones del daemon](https://github.com/gameap/daemon/releases) y colóquelo en el servidor dedicado.
+2. Abra la ventana **"Crear"** en el panel y tome la URL de conexión de la forma
+ `grpc://host:port/key` del comando de Windows.
+3. Ejecute el registro:
+
+```bash
+gameap-daemon enroll --connect=grpc://your-panel:31718/zItWHWlI4RKPl9ZsYc3y3WgdKq7mNvBx
+```
+
+El daemon se conecta al panel, crea en él un registro de servidor dedicado, recibe los certificados
+(`ca.crt`, `server.crt`, `server.key` con permisos `0600` en un directorio con permisos `0700`)
+y escribe el archivo de configuración.
+
+| Flag | Valor por defecto en Linux | Valor por defecto en Windows | Propósito |
+|-------------------|-----------------------------------------|---------------------------------------|----------------------------------------------------------------------------|
+| `--connect` | — | — | URL de conexión. Obligatorio |
+| `--config-path` | `/etc/gameap-daemon/gameap-daemon.yaml` | `C:\gameap\daemon\gameap-daemon.yaml` | Dónde escribir la configuración |
+| `--certs-dir` | `/etc/gameap-daemon/certs` | `C:\gameap\daemon\certs` | Dónde guardar los certificados |
+| `--work-path` | `/srv/gameap` | `C:\gameap` | Directorio de trabajo de los servidores de juego |
+| `--steamcmd-path` | `/srv/gameap/steamcmd` | `C:\gameap\steamcmd` | Directorio de SteamCMD |
+| `--listen-ip` | `0.0.0.0` | `0.0.0.0` | IP que el daemon informa sobre sí mismo. Con `0.0.0.0` se detecta automáticamente |
+| `--listen-port` | `31717` | `31717` | Puerto que el daemon informa sobre sí mismo |
+
+> El comando `enroll` sobrescribe el archivo de configuración **por completo**: sin fusionarlo con
+> la configuración existente y sin copia de seguridad. Si el daemon ya ha sido configurado, guarde
+> la configuración de antemano.
+
+Después de registrar el daemon, debe registrarlo como servicio del sistema e iniciarlo usted mismo.
+
+### Si la instalación falla
+
+| Mensaje | Causa |
+|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
+| `This script must be run as root.` | El script no se ejecutó como root. Descárguelo a un archivo y ejecútelo a través de `sudo`, como se muestra arriba |
+| `Error: 'curl' is required but not installed.` | El servidor no tiene `curl`, `tar` o `install`. Instale el paquete que falta e inténtelo de nuevo |
+| `Unsupported architecture: ...` | No hay compilaciones listas para la arquitectura del servidor |
+| `Failed to detect latest gameapctl version` | No hay acceso a `api.github.com`, o se agotó el límite de la API de GitHub (60 solicitudes por hora desde una dirección). Espere o instale gameapctl manualmente |
+| `cannot reach gRPC server at ...` | El puerto 31718 del panel no es accesible desde el servidor dedicado. Compruebe el firewall y la dirección del panel — consulte [GRPC API](/es/daemon/grpc.html) |
+| El enlace devuelve `403` | La clave de configuración ha caducado o ha sido regenerada. Abra de nuevo la ventana "Crear" y copie el nuevo comando |
+
+## Edición de servidores dedicados
+
+Para editar un servidor dedicado (nodo), vaya a la página **"Administración"** → **"Servidores dedicados"**,
+luego seleccione el servidor dedicado que desea editar y haga clic en el botón **"Editar"**.
+
+### Descripción de los parámetros
+
+#### Básicos
+
+##### Nombre
+
+Nombre del servidor dedicado. Puede tomar cualquier valor no vacío; no afecta a ninguna función.
+
+##### Directorio de trabajo
+
+Este directorio contiene los scripts básicos para gestionar los procesos de los servidores de juego.
+Los subdirectorios del directorio de trabajo contienen los archivos de los servidores de juego. Para
+la ruta especificada se asigna el [directorio del servidor de juego](/es/gameap_configure/game_servers.html#directorio).
+El valor por defecto es `/srv/gameap`.
+
+##### Ruta a SteamCMD
+
+Ruta al directorio de SteamCMD (allí se encuentra el script `steamcmd.sh`). El valor por defecto es `/srv/gameap/steamcmd`.
+
+##### Lista de IP
+
+Lista de IP o hosts donde se ejecutarán los servidores de juego.
+
+#### Scripts
+
+Plantillas de comandos que el panel utiliza para gestionar los servidores de juego en este servidor
+dedicado. Completarlas es opcional: si un campo está vacío, se usa el comando por defecto.
+
+#### GameAP Daemon
+
+Datos de conexión del daemon. Se rellenan automáticamente cuando se registra el servidor dedicado
+y, por lo general, no es necesario modificarlos manualmente.
+
+En GameAP 4 la conexión la establece el daemon: se conecta él mismo al panel a través de gRPC y
+mantiene una conexión persistente. El panel no se conecta al daemon.
+
+##### Host y puerto de GameAP Daemon
+
+La dirección y el puerto que el daemon informó sobre sí mismo durante el registro. El puerto por
+defecto es `31717`. Los valores son informativos: el panel no los utiliza para conectarse, y no es
+necesario abrir este puerto en el servidor dedicado.
+
+##### Usuario y contraseña de GameAP Daemon
+
+Campos heredados de GameAP 3, donde el panel se conectaba al daemon por sí mismo. No se utilizan en
+GameAP 4 y no es necesario rellenarlos.
diff --git a/es/gameap_configure/file_manager.md b/es/gameap_configure/file_manager.md
new file mode 100644
index 0000000..2014cde
--- /dev/null
+++ b/es/gameap_configure/file_manager.md
@@ -0,0 +1,120 @@
+---
+title: Gestor de archivos
+layout: default
+lang: es
+category: Configuración del panel
+order: 317
+---
+
+El gestor de archivos permite trabajar con los archivos del servidor de juego directamente
+desde el panel: visualización y edición, subida y descarga, permisos. Se encuentra en la
+pestaña **Archivos** de la página del servidor de juego.
+
+Todas las operaciones se realizan a través de GameAP Daemon en el servidor dedicado y están
+limitadas al directorio del servidor de juego: no es posible salir de él.
+
+El acceso requiere el permiso `game-server-files` en el servidor; consulte
+[Usuarios, roles y permisos](/es/users.html).
+
+## Subida de archivos
+
+El panel utiliza dos métodos de subida y elige entre ellos automáticamente.
+
+**Subida normal**: para archivos pequeños, en una sola solicitud. El tamaño máximo es de
+**100 MB** y no es configurable.
+
+**Subida por fragmentos**: para archivos grandes. El archivo se divide en fragmentos de
+`FILES_UPLOAD_CHUNK_SIZE` (8 MB de forma predeterminada); los fragmentos se envían en
+paralelo, de cuatro en cuatro, con tres intentos cada uno.
+
+El tamaño máximo de archivo para una subida por fragmentos es el tamaño del fragmento
+multiplicado por `FILES_UPLOAD_MAX_CHUNKS` (100 000 de forma predeterminada), lo que equivale
+a unos 780 GB.
+
+### Reanudación
+
+Antes del envío, el navegador calcula la suma de verificación SHA-256 del archivo completo y
+la pasa al panel junto con los parámetros de subida. El panel registra qué fragmentos se han
+recibido.
+
+Si la subida se interrumpe —la pestaña se cerró, la red se cayó—, al reintentarlo el panel
+informa la lista de fragmentos faltantes y solo se envían esos. No es necesario volver a
+enviar el archivo completo.
+
+Las subidas incompletas se conservan durante `FILES_UPLOAD_SESSION_TTL` (24 horas de forma
+predeterminada), tras lo cual son eliminadas por una limpieza en segundo plano que se ejecuta
+cada `FILES_UPLOAD_JANITOR_INTERVAL` (12 horas de forma predeterminada).
+
+> La suma de verificación se calcula en el navegador mediante WebAssembly. Si ha editado la
+> política CSP y ha eliminado `'wasm-unsafe-eval'` de `script-src`, la subida de archivos
+> dejará de funcionar.
+> Consulte [Seguridad](/es/security.html).
+
+### Subida de directorios
+
+Además de archivos individuales, se puede subir un directorio completo: la estructura de
+subdirectorios anidados se conserva.
+
+### Qué archivos están permitidos
+
+El tipo de archivo se detecta **a partir del contenido**, no de la extensión ni de lo que
+informa el navegador. Permitidos:
+
+* imágenes: PNG, JPEG, GIF, WebP, BMP, ICO;
+* archivos de texto, incluidos los archivos de configuración: se detectan como `text/plain`;
+* JSON, XML, CSV, YAML;
+* PDF.
+
+**No permitidos de forma predeterminada:**
+
+| Qué | Variable | Por qué |
+|----------------------------------------|-------------------------------|------------------------------------------------------------------------------------|
+| Archivos comprimidos: zip, tar, gzip, bzip2, 7z, xz | `FILES_UPLOAD_ALLOW_ARCHIVES` | Un archivo comprimido puede contener ejecutables que se descomprimirán en el servidor dedicado |
+| Archivos binarios arbitrarios | `FILES_UPLOAD_ALLOW_BINARY` | El tipo `application/octet-stream` no da ninguna pista sobre su contenido |
+
+SVG y HTML están bloqueados siempre: pueden ocultar un script.
+
+La lista de tipos permitidos se puede ampliar, sin habilitar por completo los archivos
+comprimidos, con la variable `FILES_UPLOAD_ALLOWED_MIMES`: **amplía** la lista
+predeterminada en lugar de reemplazarla.
+
+Las subidas rechazadas se registran en el registro de auditoría con el tipo de archivo
+detectado y el motivo del rechazo.
+
+## Descarga
+
+Un archivo individual se descarga tal cual. Un directorio o varios archivos seleccionados se
+empaquetan en un **ZIP** y se transmiten en una sola pasada, sin crear un archivo temporal
+en el disco.
+
+Los límites se establecen con variables:
+
+| Variable | Predeterminado | Propósito |
+|----------------------------------------|----------------|--------------------------------------------------|
+| `FILES_ARCHIVE_MAX_BYTES` | `100G` | Tamaño máximo del archivo comprimido |
+| `FILES_ARCHIVE_MAX_FILES` | `500000` | Número máximo de archivos en un archivo comprimido |
+| `FILES_ARCHIVE_CONCURRENT_PER_SERVER` | `2` | Operaciones de empaquetado simultáneas por servidor de juego |
+
+## Permisos de archivos
+
+Los permisos de archivos y directorios se pueden cambiar con **chmod**, igual que en una
+terminal. Esto es necesario, por ejemplo, para hacer ejecutable un script de inicio de un
+servidor de juego.
+
+## Edición
+
+Los archivos de texto se abren en el editor integrado. Los plugins pueden añadir sus propios
+editores para archivos concretos, por ejemplo, un editor hexadecimal para binarios. Los
+editores de los plugins no abren archivos de más de 1 MB. Consulte [Plugins](/es/plugins/index.html).
+
+## Si un archivo no se sube
+
+| Síntoma | Causa |
+|------------------------------------------|----------------------------------------------------------------------------|
+| Rechazado inmediatamente, sin subirse | El tipo de archivo no está permitido. La mayoría de las veces es un archivo comprimido o un binario |
+| Rechazado en un archivo de más de 100 MB | Se aplicó el límite de la subida normal; los archivos grandes usan la subida por fragmentos |
+| La subida se interrumpe al calcular la suma de verificación | Se ha eliminado `'wasm-unsafe-eval'` de la política CSP |
+| La subida no comienza | Falta el permiso `game-server-files` o el daemon no está accesible |
+
+Los detalles del rechazo se pueden ver en el registro del panel; consulte
+[Solución de problemas](/es/troubleshooting.html).
diff --git a/es/gameap_configure/game_servers.md b/es/gameap_configure/game_servers.md
new file mode 100644
index 0000000..bff8f8c
--- /dev/null
+++ b/es/gameap_configure/game_servers.md
@@ -0,0 +1,197 @@
+---
+title: Servidores de juego
+layout: default
+lang: es
+category: Configuración del panel
+order: 310
+---
+
+## Página del servidor de juego
+
+Abra la **Lista de servidores** y seleccione un servidor. La página está dividida en pestañas; cuáles están presentes
+depende de los permisos otorgados al usuario y de las capacidades del juego.
+
+| Pestaña | Qué contiene |
+|--------------------|------------------------------------------------------------------------------|
+| **Control** | Botones de inicio, detención, reinicio, actualización y reinstalación, estado del servidor, consola y estadísticas |
+| **RCON** | Consola RCON y gestión de jugadores. Aparece si el juego admite RCON |
+| **Archivos** | El gestor de archivos del servidor |
+| **Planificador de tareas** | Tareas periódicas: reinicio, actualización, comandos arbitrarios |
+| **Configuración** | Valores de las variables del mod para este servidor |
+
+Los plugins pueden añadir sus propias pestañas al servidor: aparecen junto a las integradas.
+
+### Control
+
+La pestaña principal. Además de los botones de control, contiene:
+
+**Estado del servidor** — en ejecución o detenido, el mapa actual y el número de jugadores, si el juego informa
+de estos datos a través del protocolo Query.
+
+**Consola** — la salida del servidor de juego y un campo para enviar comandos. No funciona a través de
+RCON, sino a través del gestor de procesos en el servidor dedicado, por lo que está disponible incluso cuando el
+servidor aún no se ha iniciado.
+
+**Estadísticas** — gráficos de uso de CPU, memoria y red. Los datos son recopilados por el daemon
+cada `metrics.collection_interval` (5 segundos de forma predeterminada) y se conservan durante
+`metrics.retention_duration` (10 minutos de forma predeterminada), consulte
+[GameAP Daemon](/es/daemon/daemon.html#recolección-de-métricas).
+
+### Archivos
+
+El gestor de archivos funciona dentro del directorio del servidor de juego. Permite ver y editar archivos,
+subirlos y descargarlos, crear directorios y cambiar permisos.
+
+Las subidas están restringidas por tipo de archivo: el tipo se detecta a partir del contenido, no de la extensión,
+y de forma predeterminada **los archivos comprimidos y los archivos binarios arbitrarios no están permitidos**. Esta es la razón más común
+por la que se rechaza una subida. Cómo permitirlos — [Seguridad](/es/security.html).
+
+### Planificador de tareas
+
+Tareas periódicas para el servidor: reinicio programado, actualización, ejecución de comandos. Las tareas son
+ejecutadas por el daemon en el servidor dedicado.
+
+### Configuración
+
+Valores de las variables declaradas en el mod del juego — por ejemplo, el mapa predeterminado o el número
+de slots. Se sustituyen en el comando de inicio en lugar de los shortcodes. Qué variables están
+disponibles se define [en la configuración del mod](/es/gameap_configure/games.html#variables).
+
+## Edición de servidores de juego
+
+Para editar un servidor de juego (nodo), vaya a la página **"Administración"** → **"Servidores de juego"**, luego
+seleccione el servidor de juego que desea editar y haga clic en el botón **"Editar"**.
+
+### Descripción de los parámetros
+
+#### Básicos
+
+Grupo básico de parámetros
+
+##### UUID
+
+UUID es un identificador único universal. Se genera automáticamente para cada servidor. Se utiliza como
+identificador de los procesos del servidor de juego.
+
+##### Nombre
+
+Nombre del servidor. Puede ser cualquier cadena de texto. Puede introducir cualquier nombre de servidor de juego.
+
+##### Juego
+
+El juego al que pertenece el servidor de juego. Lea la documentación sobre la configuración de juegos en la sección correspondiente —
+[Configuración de juegos](/es/gameap_configure/games.html).
+
+Consulte también [Manual para añadir juegos que faltan](/es/tutorials/additional_games.html).
+
+##### Mod
+
+El mod al que pertenece el servidor de juego. El mod puede determinar configuraciones adicionales (variables)
+para el servidor de juego, como el mapa predeterminado (`{default_map}`), FPS y otras. Consulte la información sobre cómo
+añadir sus propias configuraciones y otros detalles al respecto en la
+[página de configuración de juegos](/es/gameap_configure/games.html#variables).
+
+##### Contraseña RCON
+
+Contraseña para administrar el servidor de juego a través de RCON.
+
+##### Directorio
+
+Directorio del servidor de juego relativo al [directorio de trabajo del servidor dedicado](/es/gameap_configure/dedicated_servers.html#directorio-de-trabajo).
+
+Por ejemplo, si el directorio es `servers/my_server` y el directorio de trabajo del servidor dedicado es `/srv/gameap`, entonces el
+servidor de juego se ubicará en `/srv/gameap/servers/my_server`.
+
+##### Nombre de usuario en el servidor dedicado
+
+El usuario que ejecuta el servidor de juego en el nodo. De forma predeterminada es `gameap`.
+
+El usuario especificado en este campo debe existir en el servidor dedicado.
+
+#### Servidor dedicado, IP, puertos
+
+Un grupo de parámetros relacionados con el servidor dedicado (VDS, nodo) y la conexión al servidor de juego.
+
+##### Servidor dedicado
+
+##### IP
+
+IP o host del servidor de juego. Ejemplos: `127.0.0.1`, `my-server.gameap.ru`.
+
+##### Puerto del servidor
+
+Puerto principal del servidor. Se utiliza para conectar a los jugadores al servidor.
+
+##### Puerto Query
+
+Puerto del servidor para consultas. Query se utiliza para obtener datos generales del servidor: mapa actual, número actual
+de jugadores en el servidor, lista de jugadores.
+
+En GoldSource y Source, coincide con el puerto principal del servidor. En Minecraft, puede especificar cualquier puerto. En algunos
+otros servidores de juego, puede ser una unidad más o menos.
+
+##### Puerto RCON
+
+Puerto del servidor para la administración remota.
+
+En GoldSource y Source, coincide con el puerto principal del servidor. En Minecraft, puede especificar cualquier puerto. En algunos
+otros servidores de juego, puede ser una unidad más o menos.
+
+#### Límites de recursos
+
+Límites sobre el consumo de recursos del servidor de juego.
+
+##### Límite de CPU
+
+Se establece en millicores: `1000` es un núcleo completo, `500` es medio núcleo. En la interfaz el valor
+también puede introducirse como porcentaje o en núcleos; el panel lo convierte automáticamente.
+
+##### Límite de RAM
+
+La cantidad máxima de RAM. En la interfaz se introduce en bytes, megabytes o gigabytes.
+
+> Los límites solo los aplican los gestores de procesos que los admiten: **systemd**, **Docker**
+> y **Podman**. Con `tmux`, `winsw`, `shawl` o `simple`, los valores configurados no tienen
+> efecto. Consulte [Gestores de procesos](/es/daemon/process_managers.html).
+
+Un valor vacío o cero significa sin límite.
+
+#### Comando de inicio
+
+El comando de inicio es un parámetro importante y obligatorio para iniciar un servidor de juego. Es una cadena con diversas
+opciones y parámetros de arranque del servidor de juego. Puede ser individual para cada juego y mod.
+
+Los parámetros pueden incluir shortcodes que se reemplazan automáticamente por el valor de la configuración (variables)
+del servidor de juego específico. Los shortcodes son palabras entre llaves `{` y `}`, por ejemplo, `{ip}`,
+`{port}`, `{default_map}` y otros.
+
+Lea cómo añadir sus propios shortcodes, que se reemplazarán automáticamente por el valor de la configuración, en
+la página de [configuración de juegos](/es/gameap_configure/games.html#variables).
+
+## Envío de comandos a la consola
+
+Un comando introducido en la consola en la pestaña **Control** se entrega al servidor de juego de una
+de dos maneras.
+
+Si el servidor dedicado tiene configurada la plantilla **Script Send Command** (`script_send_command`),
+el panel sustituye el comando en ella y lo ejecuta en el servidor dedicado.
+Si la plantilla no está configurada, el comando se escribe en el archivo de entrada del servidor de juego.
+
+> **No ponga `{command}` entre comillas.** El panel sustituye el valor ya entrecomillado: antes de la
+> sustitución, el comando se escapa para el shell y se envuelve en comillas simples. Añadir sus propias
+> comillas provoca un doble escape, y el servidor recibe el comando junto con las comillas.
+
+Correcto:
+
+```text
+tmux send-keys -t {uuid} {command} Enter
+```
+
+Incorrecto:
+
+```text
+tmux send-keys -t {uuid} "{command}" Enter
+```
+
+La plantilla se configura en la página **Administración** → **Servidores dedicados** → seleccione el servidor →
+**Editar** → la pestaña **Scripts**.
diff --git a/es/gameap_configure/games.md b/es/gameap_configure/games.md
new file mode 100644
index 0000000..56639c4
--- /dev/null
+++ b/es/gameap_configure/games.md
@@ -0,0 +1,357 @@
+---
+title: Juegos
+layout: default
+lang: es
+category: Configuración del panel
+order: 320
+---
+
+## Adición de nuevos juegos
+
+El panel de control permite iniciar y realizar el control básico de cualquier servidor de juego y aplicación. Si
+el panel no contiene el juego que necesita, puede agregarlo.
+
+Para agregar un nuevo juego, vaya a **"Administración"** → **"Juegos"** y seleccione
+**"Agregar juego"**.
+
+Después de agregar el juego, agregue el primer mod de este juego y especifique los parámetros de inicio del servidor de juego.
+
+### Campos {#fields}
+
+#### Código
+
+El código del juego es un valor único. Normalmente es una abreviatura del nombre del juego, por ejemplo para
+"7 Day To Die" el código es `7d2d`. El código debe ser único. Puede introducir cualquiera, pero debe ser único.
+
+#### Código de inicio
+
+El código de inicio se suele especificar en los parámetros de inicio. Este valor puede ser cualquiera, pero con mayor frecuencia coincide con el código
+del juego. Puede coincidir con el de otros juegos.
+
+#### Nombre del juego
+
+Simplemente introduzca el nombre completo del juego.
+
+#### Motor del juego
+
+El motor del juego es el sistema sobre el que está escrito el juego. Half-Life y Counter-Strike están escritos en el motor GoldSource.
+
+Half-Life 2 y Counter-Strike Source están escritos en Source. A veces no conoce el motor del juego;
+en este caso, puede introducir el código del juego o alguna abreviatura del juego.
+
+Para los juegos escritos en GoldSource y Source, introduzca el nombre exacto del motor. Para los juegos escritos en Unity, es mejor introducir
+alguna abreviatura del juego, por ejemplo Rust está escrito en Unity, pero el nombre del motor es `rust`.
+Minecraft está escrito sin usar ningún motor, por lo que el nombre del motor también es "Minecraft".
+
+#### Versión del motor
+
+Valor numérico o de cadena de la versión del motor. Puede especificar cualquier valor semántico, por ejemplo, "legacy", "beta",
+etc.
+
+#### Steam App ID
+
+ID del servidor de juego en Steam. Se utiliza para instalar el servidor a través de SteamCMD.
+Puede encontrar el SteamID en la [wiki oficial de Steam](https://developer.valvesoftware.com/wiki/Dedicated_Servers_List), o
+en [SteamDB](https://steamdb.info/).
+
+#### Steam App Set Config
+
+Opciones adicionales para instalar el servidor a través de SteamCMD. Puede encontrar algunos valores en la
+[wiki oficial de Steam](https://developer.valvesoftware.com/wiki/Dedicated_Servers_List)
+
+#### Repositorio local {#local-repository}
+
+Ruta en el servidor dedicado al archivo o al directorio con la build del juego que se usa como plantilla.
+Esta ruta debe existir en el servidor dedicado donde se instala un nuevo servidor de juego.
+Al agregar un nuevo servidor de juego, el archivo se descomprimirá en el directorio de trabajo del servidor de juego.
+
+Se admiten los siguientes archivos: Zip, 7z, Tar, XZ, Bzip, GZip.
+
+Los archivos RAR no se admiten.
+
+##### Ejemplos
+
+Directorio de trabajo de ejemplo del servidor de juego `/srv/gameap/servers/example-server`
+
+| Valor del campo "Repositorio local" | Validez del valor | Resultado de la instalación
+| ------ | ------- | ------ |
+| `/srv/gameap/repo/cs16_gungame.zip` | Válido, si el archivo existe en el servidor dedicado | El contenido del archivo `cs16_gungame.zip` se descomprimirá en `/srv/gameap/servers/example-server`
+| `/srv/gameap/repo/cs16_public` | Válido, si el directorio existe en el servidor dedicado | El contenido del directorio se copiará a `/srv/gameap/servers/example-server`
+| `/srv/gameap/repo/cs16_gungame.rar` | No válido. Los archivos RAR no se admiten | El método de instalación desde el repositorio local se omitirá o el servidor no se instalará
+| `https://cdn.gameap.com/cstrike-1.6/rehlds-amxx-reunion.tar.xz` | No válido. Se ha especificado un valor para el repositorio remoto | El método de instalación desde el repositorio local se omitirá o el servidor no se instalará
+
+
+#### Repositorio remoto {#remote-repository}
+
+Enlace a una fuente remota. Debe ser una URL a un recurso HTTP o FTP. El archivo debe estar accesible mediante un enlace directo,
+sin páginas intermedias que requieran espera o acciones adicionales. No se admiten enlaces a Yandex Disk, Google Drive,
+etc.
+
+Las builds listas para usar se encuentran en el repositorio de GameAP: `cdn.gameap.com` para todo el mundo y `cdn.gameap.ru`
+para Rusia. No es posible explorar la lista de archivos: el listado de directorios está desactivado y solo funcionan los enlaces
+directos. En lugar de buscar estas URL manualmente, use el botón **Actualizar juegos**: las configuraciones de juegos
+incluidas ya contienen las URL correctas, consulte
+[Importación de juegos](/es/gameap_configure/games_import.html#actualización-de-juegos-desde-el-catálogo-de-gameap).
+
+##### Ejemplos
+
+Directorio de trabajo de ejemplo del servidor de juego `/srv/gameap/servers/example-server`
+
+| Valor del campo "Repositorio remoto" | Validez del valor | Resultado de la instalación
+| ------ | ------- | ------ |
+| `https://cdn.gameap.com/cstrike-1.6/rehlds-amxx-reunion.tar.xz` | Válido | `rehlds-amxx-reunion.tar.xz` se descargará y se descomprimirá en `/srv/gameap/servers/example-server`
+| `/srv/gameap/repo/cs16_gungame.zip` | No válido. Debe especificarse un recurso http o ftp | El método de instalación se omitirá o el servidor de juego no se instalará.
+
+## Adición de nuevos mods
+
+Cada juego puede tener muchos mods; cada uno tiene sus propias características, configuraciones,
+parámetros de inicio, archivos de configuración, etc.
+
+Para agregar un nuevo mod, vaya a **"Administración"** → **"Juegos"** y seleccione
+**"Agregar mod"**
+
+### Campos
+
+##### Juego
+
+El juego al que pertenece el mod.
+
+##### Nombre
+
+Nombre del mod. Puede ser el nombre del addon, de la build, del kernel, de cualquier característica, etc. Introduzca el nombre completo
+a su discreción.
+
+#### Repositorios
+
+El mod puede tener su propio conjunto de archivos y configuraciones que se escriben sobre los archivos de la build principal.
+En el archivo puede incluir cualquier plugin adicional, contenido adicional, sonidos, música, etc.
+Los campos de repositorio local son opcionales.
+
+Se admiten los siguientes archivos: Zip, 7z, Tar, XZ, Bzip, GZip.
+
+Los archivos RAR no se admiten.
+
+##### Repositorio local {#local-repository-1}
+
+Ruta en el servidor dedicado al archivo o al directorio con los archivos del mod del juego que se usan como plantilla.
+Esta ruta debe existir en el servidor dedicado donde se instala un nuevo servidor de juego.
+Al instalar el servidor de juego, el archivo se descomprimirá sobre la build principal en el directorio de trabajo
+del servidor de juego.
+
+Se admiten los siguientes archivos: Zip, 7z, Tar, XZ, Bzip, GZip.
+
+Los archivos RAR no se admiten.
+
+###### Ejemplos
+
+Directorio de trabajo de ejemplo del servidor de juego `/srv/gameap/servers/example-server`
+
+| Valor del campo "Repositorio local" | Validez del valor | Resultado de la instalación
+| ------ | ------- | ------ |
+| `/srv/gameap/repo/cs16_gungame.zip` | Válido, si el archivo existe en el servidor dedicado | El contenido del archivo `cs16_gungame.zip` se descomprimirá en `/srv/gameap/servers/example-server`
+| `/srv/gameap/repo/cs16_cs16_gungame` | Válido, si el directorio existe en el servidor dedicado | El contenido del directorio se copiará a `/srv/gameap/servers/example-server`
+| `/srv/gameap/repo/cs16_gungame.rar` | No válido. Los archivos RAR no se admiten | La descompresión del archivo se omitirá
+| `https://cdn.gameap.com/cstrike-1.6/rehlds-amxx-reunion.tar.xz` | No válido. Se ha especificado un valor de repositorio remoto | La instalación del mod se omitirá
+
+
+##### Repositorio remoto {#remote-repository-1}
+
+Enlace a una fuente remota. Debe ser una URL a un recurso HTTP o FTP. El archivo debe estar accesible mediante un enlace directo,
+sin páginas intermedias que requieran espera o acciones adicionales. No se admiten enlaces a Yandex Disk, Google Drive,
+etc.
+
+Las builds listas para usar se encuentran en el repositorio de GameAP: `cdn.gameap.com` para todo el mundo y `cdn.gameap.ru`
+para Rusia. No es posible explorar la lista de archivos: el listado de directorios está desactivado y solo funcionan los enlaces
+directos. En lugar de buscar estas URL manualmente, use el botón **Actualizar juegos**: las configuraciones de juegos
+incluidas ya contienen las URL correctas, consulte
+[Importación de juegos](/es/gameap_configure/games_import.html#actualización-de-juegos-desde-el-catálogo-de-gameap).
+
+###### Ejemplos
+
+Directorio de trabajo de ejemplo del servidor de juego `/srv/gameap/servers/example-server`
+
+| Valor del campo "Repositorio remoto" | Validez del valor | Resultado de la instalación
+| ------ | ------- | ------ |
+| `https://cdn.gameap.com/cstrike-1.6/rehlds-amxx-reunion.tar.xz` | Válido | `rehlds-amxx-reunion.tar.xz` se descargará y se descomprimirá en `/srv/gameap/servers/example-server`
+| `/srv/gameap/repo/cs16_gungame.zip` | No válido. Debe especificarse un recurso http o ftp | La instalación del mod se omitirá
+
+## Edición de mods
+
+Después de crear un mod, puede configurarlo más a fondo especificando parámetros adicionales como
+los "comandos de inicio predeterminados", variables de inicio y diversos comandos RCON.
+
+### Configuración principal
+
+La configuración principal incluye parámetros como el nombre del mod, los repositorios y los comandos de inicio predeterminados. Preste especial atención
+a los comandos de inicio.
+
+#### Nombre del mod
+
+#### Repositorios
+
+Lea más en [Adición de nuevos mods, repositorios](#repositorios)
+
+#### Comandos de inicio predeterminados
+
+Al agregar un nuevo servidor de juego para el mod seleccionado, este comando se le asignará automáticamente. Si
+el comando está vacío, no se asignará nada y deberá especificar el comando de inicio para cada servidor de juego manualmente.
+El servidor de juego no puede iniciarse sin un comando de inicio.
+
+Puede usar shortcodes en los comandos de inicio; luego se reemplazarán por los valores de las variables del servidor. Los shortcodes
+son palabras sin espacios entre llaves `{`, `}`; por ejemplo, `{ip}`, `{port}`, `{maxplayers}`, etc.
+
+##### Shortcodes básicos
+
+Estos shortcodes están siempre disponibles, no requieren agregar variables adicionales en la configuración del mod.
+
+| Shortcode | Descripción
+| ------ | -------
+| {ip} | IP del servidor de juego
+| {port} | Puerto principal del servidor de juego. A veces llamado puerto de conexión
+| {query_port} | Puerto de consulta
+| {rcon_port} | Puerto de comunicación del servidor (puerto RCON)
+| {rcon_password} | Contraseña RCON
+| {uuid} | UUID del servidor
+| {uuid_short} | UUID corto del servidor
+
+##### Shortcodes definidos por el usuario
+
+Puede definir estos shortcodes usted mismo para cada mod de juego concreto. Dependiendo de la
+configuración individual del servidor, estos shortcodes se reemplazarán por los valores de los parámetros del servidor de juego. Lea más
+en [Variables](#variables).
+
+### Variables
+
+Puede agregar configuraciones individuales para cada servidor de juego. Luego, estas configuraciones pueden ser editadas
+por el administrador o por un usuario normal en la página de configuración (**"Lista de servidores"** → **"Administración"** →
+**"Configuración"**).
+
+| Campo | Descripción
+| ------ | -------
+| Variable | Nombre de la variable. Sin llaves.
+| Predeterminado | Valor de la variable por defecto. Este valor se usará si no se ha establecido un valor individual para el servidor de juego.
+| Descripción | Descripción del servidor de juego en la página de configuración (**"Lista de servidores"** → **"Administración"** → **"Configuración"**)
+| Variable de administrador | Si está marcado, solo el administrador podrá editar esta configuración para los servidores de juego.
+
+#### Ejemplos
+
+| Valores | Descripción
+| ------ | -------
+| **Variable:** default_map
**Predeterminado:** de_dust
**Descripción:** Mapa al inicio | Para cada servidor de juego de este mod, aparecerá el shortcode `{default_map}` y un nuevo parámetro en la configuración llamado "Mapa predeterminado" con el valor predeterminado "de_dust".
De forma individual para cada servidor, este parámetro se puede editar en **"Lista de servidores"** → **"Administración"** → **"Configuración"**
+
+
+### Comandos RCON
+
+Estos comandos permiten una administración más avanzada del servidor de juego. Si el juego admite trabajar con RCON o
+si se admite la consola, puede hacer lo siguiente: expulsar jugadores del servidor, banear jugadores, cambiar el mapa del servidor,
+enviar mensajes de texto al chat común, establecer una contraseña.
+
+Algunas funciones pueden estar limitadas por el propio juego o por el mod. Por ejemplo, no todos los servidores de juego admiten
+el acceso al servidor con contraseña.
+
+#### Comando de expulsión
+
+Puede establecer el comando RCON para expulsar a un jugador del servidor.
+
+Puede establecer shortcodes para el comando que se reemplazarán con los datos de un jugador concreto
+
+| Shortcode | Descripción
+| ------ | -------
+| {id} | ID del jugador en el servidor
+| {name} | Nombre del jugador en el servidor
+
+Para muchos juegos GoldSource/Source, este es el comando:
+```text
+kick #{id}
+```
+
+#### Comando de ban
+
+Puede establecer un comando que se usará para el ban temporal o permanente de un jugador en el servidor.
+
+Puede establecer shortcodes para el comando que se reemplazarán con los datos de un jugador concreto
+
+| Shortcode | Descripción
+| ------ | -------
+| {id} | ID del jugador en el servidor
+| {name} | Nombre del jugador en el servidor
+| {time} | Tiempo del ban
+| {reason} | Razón del ban
+
+Para muchos juegos GoldSource (Half-Life, Counter-Strike 1.6, etc.) que ejecutan AMX Mod X, este es el comando:
+```text
+amx_ban "{name}" {time} "{reason}"
+```
+
+#### Comando de cambio de nombre (nick)
+
+Con este comando puede cambiar el nick del jugador seleccionado.
+
+Puede establecer shortcodes para el comando que se reemplazarán con los datos de un jugador concreto
+
+| Shortcode | Descripción
+| ------ | -------
+| {id} | ID del jugador en el servidor
+| {name} | Nombre actual del jugador en el servidor
+| {new_name} | Nuevo nombre del jugador en el servidor
+| {reason} | Razón del cambio
+
+Para muchos juegos GoldSource (Half-Life, Counter-Strike 1.6, etc.) que ejecutan AMX Mod X, este es el comando:
+```text
+amx_nick #{id} {new_name}
+```
+
+#### Comando de reinicio
+
+Puede establecer el comando para un reinicio suave del servidor, sin reiniciar el proceso del servidor de juego. Normalmente,
+este comando reinicia el mapa o la ronda del juego. No es compatible con muchos juegos.
+
+Para muchos juegos GoldSource/Source, este es el comando:
+```text
+restart
+```
+
+#### Comando de cambio de mapa
+
+Con este comando puede cambiar el mapa del servidor de juego.
+
+| Shortcode | Descripción
+| ------ | -------
+| {map} | Nombre del mapa
+
+Para muchos juegos GoldSource/Source, este es el comando:
+```text
+changelevel {map}
+```
+
+#### Comando de envío de mensajes
+
+Con este comando puede enviar un mensaje de texto al chat de todos los jugadores del servidor.
+
+| Shortcode | Descripción
+| ------ | -------
+| {msg} | Mensaje que se enviará al servidor
+
+Para muchos juegos GoldSource (Half-Life, Counter-Strike 1.6, etc.) que ejecutan AMX Mod X, este es el comando:
+```text
+amx_say "{msg}"
+```
+
+#### Comando para establecer/cambiar la contraseña
+
+Puede establecer una contraseña para el servidor de juego, de modo que solo los jugadores que conozcan
+esta contraseña puedan entrar.
+
+| Shortcode | Descripción
+| ------ | -------
+| {password} | Contraseña del servidor
+
+Para muchos juegos GoldSource/Source, este es el comando:
+```text
+password {password}
+```
+
+### Comandos FastRCON
+
+Puede especificar sus propios comandos RCON opcionales. Por ejemplo, un comando de estado del servidor, obtención de estadísticas, obtención de la
+lista de jugadores desconectados recientemente, etc.
diff --git a/es/gameap_configure/games_import.md b/es/gameap_configure/games_import.md
new file mode 100644
index 0000000..42b7acb
--- /dev/null
+++ b/es/gameap_configure/games_import.md
@@ -0,0 +1,108 @@
+---
+title: Importación de juegos
+layout: default
+lang: es
+category: Configuración del panel
+order: 330
+---
+
+La configuración de juegos y mods se puede trasladar entre instalaciones de GameAP y tomarse
+de otros paneles de control. Esta funcionalidad apareció en GameAP 4.1.
+
+Todo esto se encuentra en la página **Administración** → **Juegos**.
+
+
+
+## Formato propio de GameAP
+
+El formato de GameAP contiene el juego completo: el juego en sí, todos sus mods, variables,
+comandos de ejecución y comandos RCON.
+
+### Exportación
+
+En la página **Administración** → **Juegos**, seleccione un juego y haga clic en **Exportar**.
+El panel servirá un archivo `.gameap.yaml`.
+
+A través de la API:
+
+```http
+GET /api/games/{code}/export
+```
+
+### Importación
+
+**Administración** → **Juegos** → **Importar YAML de GameAP**, suba el archivo y haga clic
+en **Importar**.
+
+
+
+A través de la API:
+
+```http
+POST /api/games/import/gameap
+```
+
+La importación crea el juego si aún no existe y **actualiza el existente** si ya hay un juego
+con ese código. Los mods se importan junto con el juego: después de la subida, el panel
+informa cuántos se importaron.
+
+La versión del formato se indica en la parte superior del archivo:
+
+```yaml
+schema_version: "1.0"
+```
+
+El panel no aceptará un archivo con una versión de formato diferente. También conviene
+revisar este campo cuando una transferencia falla.
+
+## Importación desde otros paneles
+
+La importación de plantillas es compatible con:
+
+* [Pterodactyl](https://pterodactyl.io/)
+* [Pelican](https://pelican.dev/)
+
+**Administración** → **Juegos** → **Importar Egg de Pelican**, suba el archivo egg en formato
+JSON o YAML.
+
+A través de la API:
+
+```text
+POST /api/games/import/pelican-egg
+```
+
+Ambos paneles comparten el mismo formato egg, por lo que los archivos de Pterodactyl y
+Pelican se suben de la misma manera.
+
+## Actualización de juegos desde el catálogo de GameAP
+
+El panel incluye un catálogo de configuraciones listas para usar de juegos y mods. Para
+obtener los cambios del catálogo, haga clic en **Actualizar juegos** en la página
+**Administración** → **Juegos**.
+
+A través de la API:
+
+```text
+POST /api/games/upgrade
+```
+
+> **Sus cambios pueden sobrescribirse.** La actualización lleva la configuración de juegos y
+> mods al estado del catálogo. Si ha editado comandos de ejecución o variables de los juegos
+> estándar, guárdelos antes de actualizar; por ejemplo, exportando el juego a un archivo.
+
+Un mod no se actualiza si el panel encuentra varios mods con el mismo nombre para el juego:
+estos casos se omiten para evitar sobrescribir el incorrecto.
+
+Las direcciones del catálogo se configuran con la variable `GAMES_CDN_URLS`; se prueban en
+orden hasta que una responda. Consulte la [referencia de config.env](/es/config.html).
+
+## Funciones de Pelican y Pterodactyl
+
+Trabajar con Eggs de Pelican y Eggs de Pterodactyl importados solo es posible con los
+[gestores de procesos](/es/daemon/process_managers.html) Docker y Podman.
+
+Debe configurar GameAP Daemon para trabajar con uno de estos gestores de procesos.
+Para ello, al agregar un nuevo nodo, seleccione el gestor de procesos deseado en la sección
+"Configuración avanzada".
+
+
diff --git a/es/gameap_configure/tasks.md b/es/gameap_configure/tasks.md
new file mode 100644
index 0000000..97a9b02
--- /dev/null
+++ b/es/gameap_configure/tasks.md
@@ -0,0 +1,120 @@
+---
+title: Planificador de tareas
+layout: default
+lang: es
+category: Configuración del panel
+order: 315
+---
+
+El planificador ejecuta comandos en un servidor de juego según una programación: un reinicio
+nocturno, actualizaciones periódicas, una detención por mantenimiento.
+
+Las tareas están vinculadas a un servidor de juego concreto y se encuentran en la pestaña
+**Planificador de tareas** de su página.
+
+## Crear una tarea
+
+Abra la página del servidor de juego → la pestaña **Planificador de tareas** → **Nueva tarea**.
+
+| Campo | Descripción |
+|-----------------------|---------------------------------------------------------------------------|
+| **Nombre** | Opcional, hasta 128 caracteres. Ayuda a distinguir las tareas en la lista |
+| **Comando** | Qué hacer: iniciar, detener, reiniciar, actualizar o reinstalar |
+| **Fecha** | Cuándo ejecutar la tarea por primera vez |
+| **Zona horaria** | La zona en la que se interpreta la programación. UTC por defecto |
+| **Repetir** | Una vez, para siempre o un número determinado de veces |
+| **Período de repetición** | El intervalo entre ejecuciones. Al menos 10 minutos |
+
+Los comandos disponibles son los mismos que los botones de control del servidor: `start`, `stop`,
+`restart`, `update`, `reinstall`.
+
+### Zona horaria
+
+Si no se establece ninguna zona, la programación se calcula en UTC. Especifique la zona si la
+tarea debe ejecutarse a una hora local concreta; de lo contrario, tras un cambio de horario de
+verano la tarea se desviará respecto a la hora esperada.
+
+## Configuración avanzada
+
+El bloque **Avanzado** define el comportamiento en dos situaciones anómalas.
+
+### Comportamiento ante solapamientos
+
+Qué hacer cuando llega el momento de una nueva ejecución pero la anterior sigue en curso.
+
+| Valor | Comportamiento |
+|-------------------------------|--------------------------------------------------------|
+| **Omitir la nueva ejecución** | Omitir la ejecución. Valor por defecto |
+| **Añadir a la cola** | Ejecutar justo después de que termine la ejecución actual |
+
+La omisión conviene a tareas que no tienen que ejecutarse cada vez; las actualizaciones
+periódicas, por ejemplo. La cola tiene sentido cuando cada ejecución importa.
+
+### Comportamiento ante ejecuciones perdidas
+
+Qué hacer con las ejecuciones que cayeron en un período en el que el daemon no estaba disponible.
+
+| Valor | Comportamiento |
+|-----------------------------------|------------------------------------------------------------|
+| **Omitir las ejecuciones perdidas** | Descartar todo lo que se perdió. Valor por defecto |
+| **Ejecutar una vez** | Fusionar todas las ejecuciones perdidas en una sola |
+
+La segunda opción protege contra una avalancha: si el daemon estuvo caído un día y la tarea se
+ejecuta cada hora, tras restablecerse la conexión se ejecutará una vez, no veinticuatro.
+
+## Activación y desactivación
+
+Una tarea tiene un interruptor **Activa**. Una tarea desactivada permanece en la lista con el
+estado **En pausa** y no se ejecuta. Esto es preferible a eliminarla cuando la tarea solo
+necesita estar inactiva temporalmente.
+
+## Historial de ejecución
+
+El botón **Historial de ejecución** muestra lo que ha estado ocurriendo con la tarea.
+
+| Estado | Significado |
+|---------------|----------------------------------------------------------------|
+| `running` | Se está ejecutando ahora mismo |
+| `success` | Completada correctamente |
+| `failed` | Terminó con un error; la razón está en el mensaje de error |
+| `canceled` | Cancelada |
+| `skipped` | Omitida por la política de solapamiento o de ejecuciones perdidas |
+| `timed_out` | Se superó el límite de tiempo de ejecución |
+
+Para cada ejecución se almacenan la hora de inicio y de fin, la duración, el código de salida,
+el mensaje de error y la salida del comando.
+
+A través de la API, el historial está disponible con:
+
+```http
+GET /api/servers/{server}/tasks/{id}/executions
+```
+
+## Tareas del daemon
+
+El planificador crea tareas del daemon: las mismas que se ven en **Administración** →
+**Tareas de GDaemon**. Allí también se ejecutan las tareas creadas manualmente: iniciar el
+servidor con un botón, la instalación, las actualizaciones.
+
+Una tarea atascada o innecesaria se puede cancelar: ábrala y haga clic en **Cancelar**. A
+través de la API:
+
+```http
+POST /api/gdaemon_tasks/{id}/cancel
+```
+
+La cancelación ayuda cuando una tarea está atascada en espera, por ejemplo, porque el daemon no
+estaba disponible cuando se creó. El propio panel marca periódicamente las tareas atascadas: el
+intervalo de comprobación y el umbral se configuran con las variables `TASK_REAPER_INTERVAL` y
+`TASK_REAPER_STALE_THRESHOLD`; consulte la [referencia de config.env](/es/config.html).
+
+## Permisos
+
+Para trabajar con el planificador se requiere el permiso `game-server-tasks` en el servidor;
+consulte [Usuarios, roles y permisos](/es/users.html).
+
+## Tareas de plugins
+
+Los plugins pueden registrar sus propias tareas periódicas; no están relacionadas con servidores
+de juego y no aparecen en esta sección. Sus límites se configuran con las variables
+`PLUGIN_SCHEDULER_*`; consulte la [referencia de config.env](/es/config.html).
diff --git a/es/get_started.md b/es/get_started.md
new file mode 100644
index 0000000..6e5d639
--- /dev/null
+++ b/es/get_started.md
@@ -0,0 +1,44 @@
+---
+title: Primeros pasos
+layout: default
+lang: es
+category: General
+order: 2
+---
+
+Para empezar, es recomendable disponer de dos servidores dedicados o virtuales.
+En uno se encuentra el panel de control, en el otro los servidores de juego.
+También puede instalar todo en un único servidor dedicado.
+
+## Instalación del panel
+
+El panel se instala en un servidor dedicado con una base de datos (PostgreSQL, MySQL, SQLite).
+
+ [Instalación en Linux](/es/install/install_on_linux.html)
+
+ [Instalación en Windows](/es/install/install_on_windows.html)
+
+## La instalación más sencilla en Linux
+
+Si tiene Linux, CURL instalado y no quiere entrar en los detalles de la instalación, ejecute el comando:
+```bash
+bash <(curl -s https://gameap.com/install.sh) --with-daemon
+```
+
+## Añadir un servidor dedicado
+
+Añada un nuevo servidor dedicado (VDS) en el que después instalará los servidores de juego.
+Después de instalar el panel, inicie sesión y seleccione **"Administración"** → **"Servidores dedicados"** → **"Crear"** en el menú. A continuación,
+se abrirá una ventana con instrucciones, sígalas.
+
+
+
+Para información más detallada sobre la instalación y la configuración, consulte la página [Servidores dedicados](/es/gameap_configure/dedicated_servers.html).
+
+## Añadir un servidor de juego
+
+Vaya a **"Administración"** → **"Servidores de juego"** → **"Crear"**.
+
+
+
+Para más detalles, consulte la página de [servidores de juego](/es/gameap_configure/game_servers.html).
diff --git a/es/https.md b/es/https.md
new file mode 100644
index 0000000..f82ea6a
--- /dev/null
+++ b/es/https.md
@@ -0,0 +1,286 @@
+---
+title: HTTPS y certificados
+layout: default
+lang: es
+category: Administración
+order: 333
+---
+
+El panel puede servir HTTPS por sí mismo, sin un proxy inverso. El certificado puede tomarse de
+archivos, definirse directamente en la configuración u obtenerse automáticamente a través de Let's Encrypt.
+
+Los ajustes se definen en `config.env` — `/etc/gameap/config.env` en Linux, `C:\gameap\web\config.env`
+en Windows. Después de un cambio es necesario reiniciar: `gameapctl panel restart`.
+
+## Fuentes de certificados
+
+El panel elige la fuente por sí mismo, en este orden:
+
+1. **ACME** — si `ACME_ENABLED=true`, `ACME_EMAIL` y `ACME_DOMAINS` están definidos.
+2. **Archivos** — si `TLS_CERT_FILE` y `TLS_KEY_FILE` están definidos.
+3. **Valores en la configuración** — si `TLS_CERT` y `TLS_KEY` están definidos.
+4. **Sin certificado** — el panel sirve solo HTTP.
+
+Lo que se comprueba son las **parejas**: un `TLS_CERT_FILE` solo, sin `TLS_KEY_FILE`, no cuenta
+como una fuente configurada y se ignora silenciosamente.
+
+HTTPS escucha en el puerto de `HTTPS_PORT` (`443` por defecto) y solo cuando hay un certificado
+disponible. HTTP en `HTTP_PORT` (`8025` por defecto) está siempre activo.
+
+> El certificado del panel no tiene relación con los certificados gRPC que el panel usa para
+> comunicarse con los daemons. Estos se emiten automáticamente mediante una autoridad de
+> certificación interna; ACME no se aplica a ellos.
+> Consulte [GRPC API](/es/daemon/grpc.html) para más detalles.
+
+## Certificado desde archivos
+
+```dotenv
+TLS_CERT_FILE=/etc/gameap/certs/panel.crt
+TLS_KEY_FILE=/etc/gameap/certs/panel.key
+HTTPS_PORT=443
+```
+
+El archivo del certificado debe contener la cadena completa: el certificado mismo y después los
+intermedios. Sin los intermedios, algunos clientes no podrán verificar la firma.
+
+Los archivos se leen al arrancar. Después de reemplazar el certificado, reinicie el panel — no
+vigila los archivos por sí mismo para detectar cambios.
+
+## Certificado directamente en la configuración
+
+Es conveniente cuando la configuración se despliega mediante un sistema de gestión de secretos y
+no se desean archivos adicionales en el disco.
+
+```dotenv
+TLS_CERT=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
+TLS_KEY=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
+```
+
+Se aceptan tanto PEM plano como PEM codificado en base64 — el panel detecta el formato por sí
+mismo. Dado que el formato de `config.env` no admite valores multilínea, en la práctica se usa
+base64:
+
+```bash
+base64 -w0 panel.crt
+base64 -w0 panel.key
+```
+
+## Let's Encrypt
+
+El panel tiene un cliente ACME integrado: el certificado se emite y se renueva sin herramientas
+externas como certbot.
+
+| Variable | Valor predeterminado | Propósito |
+|-------------------------------|-----------------|-------------------------------------------------------------|
+| `ACME_ENABLED` | `false` | Activa la emisión automática |
+| `ACME_EMAIL` | `""` | Dirección para notificaciones de caducidad. Obligatorio |
+| `ACME_DOMAINS` | `""` | Dominios separados por comas. Obligatorio |
+| `ACME_CHALLENGE_TYPE` | `http-01` | Tipo de desafío: `http-01` o `dns-01` |
+| `ACME_DNS_PROVIDER` | `""` | Proveedor DNS, solo para `dns-01` |
+| `ACME_DIRECTORY_URL` | ACME de producción | URL del directorio ACME |
+| `ACME_RENEWAL_THRESHOLD` | `720h` | Cuánto tiempo antes de la caducidad renovar. 30 días por defecto |
+| `ACME_RENEWAL_CHECK_INTERVAL` | `12h` | Con qué frecuencia comprobar la fecha de caducidad |
+| `ACME_PROPAGATION_TIMEOUT` | `180s` | Cuánto tiempo esperar la propagación del registro DNS con `dns-01` |
+| `ACME_STORAGE_PATH` | `acme` | Directorio para los certificados y la clave de la cuenta ACME |
+
+> ACME se activa **solo** cuando `ACME_ENABLED=true`, `ACME_EMAIL` y `ACME_DOMAINS` están todos
+> definidos, y para `dns-01` también `ACME_DNS_PROVIDER`. Si falta algo, el panel arranca
+> sin ACME y sin error. Compruebe el resultado a través del estado en el área de administración.
+
+### Desafío http-01
+
+El método por defecto. No requiere nada, salvo que el panel sea accesible desde internet.
+
+```dotenv
+ACME_ENABLED=true
+ACME_EMAIL=admin@example.com
+ACME_DOMAINS=panel.example.com
+ACME_CHALLENGE_TYPE=http-01
+TLS_FORCE_HTTPS=true
+```
+
+Qué se necesita:
+
+* los dominios de `ACME_DOMAINS` deben resolverse a la dirección de este servidor;
+* **las peticiones al puerto 80 deben llegar al panel** — ese es el puerto al que se conecta la
+ autoridad de certificación;
+* el panel sirve la ruta `/.well-known/acme-challenge/` por sí mismo, en su puerto HTTP.
+
+> Por defecto el panel escucha en el puerto **8025**, mientras que la autoridad de certificación
+> siempre se conecta al puerto **80**. Por sí solos, estos no coinciden. Defina `HTTP_PORT=80` o
+> redirija el puerto 80 al puerto del panel mediante herramientas del sistema:
+>
+> ```bash
+> iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8025
+> ```
+>
+> Esta es la causa más común de que falle la emisión con `http-01`.
+
+El puerto 80 se necesita no solo para la primera emisión: el desafío se repite en cada renovación,
+por lo que no debe cerrarse después.
+
+El método `http-01` **no emite certificados wildcard** (`*.example.com`) — para esos se requiere
+`dns-01`.
+
+### Desafío dns-01
+
+Es necesario cuando el panel no es accesible desde internet en el puerto 80 o cuando se requiere
+un certificado wildcard.
+
+De los proveedores integrados, solo se admite **Cloudflare**. Otros se añaden mediante plugins: en
+ese caso `ACME_DNS_PROVIDER` se define como `:`.
+
+```dotenv
+ACME_ENABLED=true
+ACME_EMAIL=admin@example.com
+ACME_DOMAINS=panel.example.com,*.example.com
+ACME_CHALLENGE_TYPE=dns-01
+ACME_DNS_PROVIDER=cloudflare
+CLOUDFLARE_DNS_API_TOKEN=token_from_the_cloudflare_dashboard
+```
+
+El token se crea en Cloudflare con el permiso **Zone → DNS → Edit** para la zona en cuestión.
+Además de `CLOUDFLARE_DNS_API_TOKEN`, se aceptan las variables `CF_DNS_API_TOKEN`,
+`CLOUDFLARE_API_TOKEN` y `CF_API_TOKEN`, así como la pareja heredada
+«global key + email»: `CLOUDFLARE_API_KEY` junto con `CLOUDFLARE_EMAIL`. Es preferible un token
+con permisos restringidos.
+
+Si los registros DNS se propagan lentamente, aumente `ACME_PROPAGATION_TIMEOUT`.
+
+### Configuración mediante gameapctl
+
+En lugar de editar `config.env` a mano, puede usar el asistente:
+
+```bash
+gameapctl panel letsencrypt setup
+```
+
+Este solicita los dominios, la dirección de correo electrónico y el tipo de desafío, escribe los
+ajustes en `config.env` y reinicia el panel.
+
+La misma llamada sin preguntas:
+
+```bash
+gameapctl panel letsencrypt setup --non-interactive \
+ --domains=panel.example.com \
+ --email=admin@example.com \
+ --challenge=http-01
+```
+
+Opciones útiles:
+
+| Opción | Propósito |
+|---------------------|-------------------------------------------------------------------------------|
+| `--challenge` | `http-01` o `dns-01` |
+| `--domains` | Dominios separados por comas |
+| `--email` | Dirección de la cuenta ACME |
+| `--dns-provider` | Proveedor DNS para `dns-01` |
+| `--env` | Líneas `KEY=VALUE` adicionales para `config.env` — para las credenciales DNS |
+| `--staging` | Directorio staging de Let's Encrypt |
+| `--non-interactive` | No hacer preguntas; fallar con un error cuando falten parámetros |
+
+Desactivación:
+
+```bash
+gameapctl panel letsencrypt disable
+```
+
+El comando elimina las variables `ACME_*` de `config.env` y reinicia el panel. La opción
+`--purge-certs` está declarada pero aún no implementada — los certificados emitidos permanecen en
+el disco.
+
+### Depuración de la emisión
+
+El directorio de producción de Let's Encrypt tiene límites estrictos sobre el número de intentos
+por dominio, y es fácil agotarlos mientras se configura. Hasta que su configuración funcione, use
+el directorio staging:
+
+```dotenv
+ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory
+```
+
+Los navegadores tratan sus certificados como no confiables, pero los límites son mucho más
+flexibles. Una vez que la emisión tenga éxito, elimine esta variable, borre el contenido del
+directorio `ACME_STORAGE_PATH` y reinicie el panel para obtener un certificado de producción.
+
+### Renovación
+
+El panel comprueba la fecha de caducidad cada `ACME_RENEWAL_CHECK_INTERVAL` (cada 12 horas por
+defecto) y renueva el certificado cuando queda menos de `ACME_RENEWAL_THRESHOLD` para la
+caducidad (30 días por defecto). No se necesita un planificador ni una tarea de cron aparte.
+
+Los certificados, la clave de la cuenta ACME y los datos auxiliares se almacenan en el directorio
+`ACME_STORAGE_PATH` dentro del almacenamiento de archivos del panel. Con `FILES_DRIVER=s3`
+terminan en S3 — esto es lo que permite que varias instancias del panel compartan un mismo
+certificado.
+
+El almacenamiento compartido por sí solo no basta para varias instancias: el bloqueo que impide
+que soliciten un certificado al mismo tiempo funciona a través de Redis y está activo solo con
+`CACHE_DRIVER=redis`. Con la caché en memoria el bloqueo es local a cada instancia, y estas se
+estorbarán entre sí. Consulte [Varias instancias del panel](/es/multi_instance.html).
+
+## Estado del certificado
+
+El estado actual está disponible para un administrador en `GET /api/admin/letsencrypt/status`:
+
+```json
+{
+ "enabled": true,
+ "state": "active",
+ "challenge_type": "http-01",
+ "domains": ["panel.example.com"],
+ "not_after": "2026-10-30T12:00:00Z",
+ "last_renewal_at": "2026-08-01T12:00:00Z",
+ "next_renewal_check_at": "2026-08-02T00:00:00Z"
+}
+```
+
+Valores posibles de `state`:
+
+| Valor | Significado |
+|------------|-------------------------------------------------------|
+| `disabled` | ACME está desactivado |
+| `pending` | El certificado aún no ha sido emitido |
+| `active` | El certificado está emitido y es válido |
+| `renewing` | La renovación está en curso |
+| `failed` | El último intento falló; la razón está en `last_error` |
+
+## Redirección a HTTPS
+
+```dotenv
+TLS_FORCE_HTTPS=true
+```
+
+Todas las peticiones HTTP reciben una redirección `301`, excepto `/.well-known/acme-challenge/` —
+de lo contrario, el desafío `http-01` dejaría de funcionar.
+
+La misma variable afecta a otros dos mecanismos: la cabecera HSTS empieza a enviarse incluso
+cuando TLS termina en un proxy inverso, y el origen CORS se calcula con el esquema `https`.
+
+## Panel detrás de un proxy inverso
+
+Si TLS termina en nginx, Traefik u otro proxy, no hace falta configurar certificados en el panel —
+deje `ACME_ENABLED=false` y no defina `TLS_*`. El panel servirá HTTP en `8025`, y el proxy se
+encargará de HTTPS.
+
+Lo que importa en esta configuración:
+
+* El proxy debe pasar la cabecera `X-Forwarded-Proto: https`, de lo contrario el panel no sabrá
+ que la conexión es segura y no enviará HSTS.
+* El proxy debe **sobrescribir** la cabecera `X-Forwarded-Proto` y la cabecera de
+ `AUDIT_CLIENT_IP_HEADER`, no añadir a ellas: el panel confía en ellas sin verificar el remitente.
+* El puerto **31718** normalmente no pasa por el proxy — los daemons deben conectarse al panel
+ directamente. Defina `GRPC_EXTERNAL_HOST` con la dirección en la que el panel es accesible para
+ los daemons.
+* Si la dirección pública difiere de `HTTP_HOST`, inclúyala en `HTTP_ALLOWED_ORIGINS`.
+
+## Problemas comunes
+
+| Síntoma | Causa |
+|------------------------------------------------------|---------------------------------------------------------------------------------------------|
+| El panel arrancó, pero HTTPS no está escuchando | Una pareja de variables no está completa, o falta una de las variables `ACME_*` requeridas |
+| `state: failed` con `http-01` | El puerto 80 no es accesible desde fuera, el dominio no resuelve a este servidor, u otro servicio lo intercepta |
+| `state: failed` con `dns-01` | El token DNS no tiene permisos, o el registro no se propagó a tiempo — aumente `ACME_PROPAGATION_TIMEOUT` |
+| La emisión dejó de funcionar después de varios intentos | Se agotó el límite de peticiones del directorio de producción de Let's Encrypt. Cambie al directorio staging y termine la configuración allí |
+| El navegador se queja de la cadena | `TLS_CERT_FILE` contiene solo el certificado, sin los intermedios |
+| El certificado fue reemplazado, pero se sirve el antiguo | Los archivos se leen al arrancar — hace falta `gameapctl panel restart` |
diff --git a/es/index.md b/es/index.md
new file mode 100644
index 0000000..961ca1f
--- /dev/null
+++ b/es/index.md
@@ -0,0 +1,58 @@
+---
+title: Descripción general
+layout: default
+lang: es
+order: 1
+---
+
+GameAP es un panel de código abierto para administrar servidores de juego y servicios.
+
+## Características
+
+* Administración de servidores de juego y servicios (iniciar, detener, reiniciar)
+* Gestión de archivos del servidor de juego (editar, subir, descargar)
+* Administración del servidor de juego mediante RCON (enviar comandos, ver la consola, gestionar jugadores)
+* Límites de recursos del servidor de juego (CPU, RAM)
+* Planificación de tareas (reinicio automático, actualizaciones, etc.)
+* Control de acceso (usuarios, roles, permisos)
+* API para la integración con otros sistemas y la automatización. La documentación de la API está disponible en [openapi.gameap.io](https://openapi.gameap.io/)
+
+La funcionalidad del panel se amplía con plugins. Los plugins están disponibles en el catálogo [plugins.gameap.dev](https://plugins.gameap.dev/).
+Cualquiera puede desarrollar y publicar su propio plugin (la publicación pasa por moderación).
+Los plugins pueden escribirse en cualquier lenguaje que compile a WASM: existe un SDK listo para Rust, y hay ejemplos en Go y AssemblyScript.
+Más información: [Plugins](/es/plugins/index.html).
+
+## Juegos compatibles
+
+El panel permite iniciar, detener y reiniciar absolutamente cualquier juego y servicio.
+
+| Juego | Query | Rcon | Notas |
+|-------------------------------------------|-------|------|----------------------------------------------------------------------------|
+| [Minecraft](/es/tutorials/minecraft.html) | ✔ | ✔ | Se admiten muchos mods |
+| Half-Life | ✔ | ✔ | Se admiten todas las versiones y mods populares (Sven Co-op, HeadCrab Frenzy) |
+| [Counter-Strike](/es/tutorials/cs2.html) | ✔ | ✔ | Se admiten todas las versiones (1.6, Source, Global Offensive, Counter-Strike 2) |
+| Team Fortress 2 | ✔ | ✔ | |
+| Garry's Mod | ✔ | ✔ | |
+| [Quake](/es/tutorials/quake3.html) | ✔ | ✔ | |
+| [Rust](/es/tutorials/rust.html) | ✔ | ✔ | |
+| FiveM | ✔ | ✘ | Mod en línea de Grand Theft Auto V |
+| [Hytale](/es/tutorials/hytale.html) | ✘ | ✘ | |
+| Terraria | | | |
+| San Andreas: MP | | | |
+
+y muchos más...
+
+El panel admite la importación de juegos desde otros paneles de control como Pterodactyl y Pelican.
+Puede importar Pelican Eggs y Pterodactyl Eggs para añadir rápidamente configuraciones predefinidas
+y crear servidores de juego basados en ellas.
+Más información en la sección [Importación de juegos](/es/gameap_configure/games_import.html).
+
+## Instalación automática del panel
+
+Disponible para Linux y Windows.
+
+Debe ejecutar el script, y este instalará automáticamente los paquetes necesarios y el panel.
+La instalación tarda solo unos minutos y, una vez finalizada, puede empezar a usar el panel de inmediato.
+
+* [Instalación del panel en Linux](/es/install/install_on_linux.html)
+* [Instalación del panel en Windows](/es/install/install_on_windows.html)
diff --git a/es/install/install_docker.md b/es/install/install_docker.md
new file mode 100644
index 0000000..5516785
--- /dev/null
+++ b/es/install/install_docker.md
@@ -0,0 +1,183 @@
+---
+title: Instalación en Docker
+layout: default
+lang: es
+category: Instalación de GameAP
+order: 102
+---
+
+El panel se distribuye como una imagen lista para usar `gameap/gameap`. El daemon no se ejecuta en Docker:
+debe gestionar los procesos de los servidores de juego directamente en el servidor dedicado.
+
+## Inicio rápido
+
+La opción más sencilla es SQLite, sin una base de datos separada:
+
+```bash
+docker run -d \
+ --name gameap \
+ -p 8025:8025 \
+ -p 31718:31718 \
+ -e DATABASE_DRIVER=sqlite \
+ -e 'DATABASE_URL=file:/var/lib/gameap/db.sqlite?_busy_timeout=5000&_journal_mode=WAL&cache=shared' \
+ -e AUTH_SECRET=$(openssl rand -base64 24) \
+ -e ENCRYPTION_KEY=$(openssl rand -hex 32) \
+ -e GRPC_EXTERNAL_HOST=panel.example.com \
+ -v gameap-data:/var/lib/gameap \
+ gameap/gameap:latest
+```
+
+El panel estará disponible en `http://localhost:8025`. El usuario y la contraseña del primer
+administrador aparecerán en el registro del contenedor:
+
+```bash
+docker logs gameap
+```
+
+> Dos parámetros del ejemplo son esenciales, y ambos son fáciles de pasar por alto porque los
+> archivos `docker-compose.yml` listos para usar no los incluyen. Consulte [Puerto 31718](#puerto-31718) y
+> [Dirección del panel para los daemons](#dirección-del-panel-para-los-daemons).
+
+## Puerto 31718
+
+Los daemons se conectan al panel a través de este puerto. El `Dockerfile` solo declara `8025`, y el
+`docker-compose.yml` de ejemplo del repositorio también publica únicamente ese.
+
+**Debe publicar el puerto 31718 usted mismo** — de lo contrario el panel funcionará, pero ningún
+servidor dedicado podrá conectarse a él.
+
+```yaml
+ports:
+ - "8025:8025"
+ - "31718:31718"
+```
+
+## Dirección del panel para los daemons
+
+El panel sustituye su propia dirección en el comando de instalación del daemon. Por defecto la toma
+de la cabecera de la solicitud, y dentro de un contenedor suele ser `localhost` o el nombre del
+servicio — un comando así no funcionará en un servidor dedicado remoto.
+
+Configure la dirección en la que el panel es accesible desde el exterior:
+
+```yaml
+environment:
+ GRPC_EXTERNAL_HOST: panel.example.com
+```
+
+Si el puerto 31718 se publica externamente con un número diferente, configúrelo también:
+
+```yaml
+environment:
+ GRPC_EXTERNAL_PORT: 41718
+```
+
+> `GRPC_EXTERNAL_HOST` se incluye en la lista de nombres del certificado gRPC autofirmado, y el
+> certificado se crea en el primer arranque. Configure la variable **antes** del primer arranque. Si
+> la configura más tarde, elimine `certs/server/api-server.crt` y `certs/server/api-server.key` en el
+> directorio de archivos del panel y reinicie el contenedor. Consulte [GRPC API](/es/daemon/grpc.html)
+> para más detalles.
+
+## Docker Compose
+
+Hay un `docker-compose.yml` listo para usar disponible en el
+[repositorio del panel](https://github.com/gameap/gameap). Levanta el panel junto con
+PostgreSQL y Redis:
+
+```bash
+git clone https://github.com/gameap/gameap.git
+cd gameap
+docker compose up -d
+```
+
+Debe añadirle la publicación del puerto 31718 y `GRPC_EXTERNAL_HOST`, como se describe arriba.
+
+Las contraseñas y las claves se configuran mediante un archivo `.env` junto a `docker-compose.yml`:
+
+```dotenv
+AUTH_SECRET=replace_with_32_random_bytes
+ENCRYPTION_KEY=replace_with_32_random_bytes
+POSTGRES_PASSWORD=replace_with_a_strong_password
+LOGGER_LEVEL=info
+```
+
+> En el `docker-compose.yml` de ejemplo, los valores predeterminados de `AUTH_SECRET` y
+> `ENCRYPTION_KEY` son `change-me-in-production`. Con tales claves, los tokens de sesión son
+> triviales de falsificar. Configure sus propios valores antes del primer arranque.
+>
+> Tenga en cuenta: cambiar `ENCRYPTION_KEY` en una instalación en funcionamiento rompe la
+> autenticación de dos factores para todos los usuarios. Consulte [Seguridad](/es/security.html).
+
+## Datos y volúmenes
+
+Todo el estado del panel se almacena en `/var/lib/gameap`:
+
+| Qué | Dónde |
+|----------------------|------------------------------------------------------------|
+| Archivos del panel | El directorio de `FILES_LOCAL_BASE_PATH`, `/var/lib/gameap/files` en el ejemplo de compose |
+| Certificados gRPC | El subdirectorio `certs/` dentro de él |
+| Certificados ACME | El subdirectorio de `ACME_STORAGE_PATH`, `acme/` por defecto |
+| Base de datos SQLite | Donde apunte `DATABASE_URL` |
+
+Los certificados y los datos de ACME no se almacenan en la raíz del volumen, sino **dentro del
+directorio de archivos del panel**. Con la configuración del `docker-compose.yml` de ejemplo, la ruta
+completa al certificado gRPC es `/var/lib/gameap/files/certs/server/api-server.crt`.
+
+Monte este directorio como un volumen; de lo contrario, al recrear el contenedor se perderán los
+certificados — y todos los daemons dejarán de conectarse.
+
+El contenedor se ejecuta como el usuario sin privilegios `gameap`, por lo que el volumen debe tener
+los permisos adecuados.
+
+## Variables de entorno
+
+La imagen se configura con las mismas variables que una instalación normal — la lista completa está
+en la [referencia de config.env](/es/config.html). No se necesita ningún archivo `config.env` dentro
+del contenedor.
+
+El mínimo indispensable:
+
+| Variable | Propósito |
+|----------------------|------------------------------------------------------------|
+| `DATABASE_DRIVER` | `postgres`, `mysql` o `sqlite` |
+| `DATABASE_URL` | Cadena de conexión a la base de datos |
+| `AUTH_SECRET` | Clave de firma de tokens, 32 bytes aleatorios |
+| `ENCRYPTION_KEY` | Clave de cifrado de secretos, 32 bytes aleatorios |
+| `GRPC_EXTERNAL_HOST` | Dirección del panel que usan los daemons para conectarse |
+
+## Comprobación de estado
+
+La imagen incluye una comprobación de estado — una solicitud a `/api/health`:
+
+```bash
+docker inspect --format='{{.State.Health.Status}}' gameap
+```
+
+El registro del panel:
+
+```bash
+docker logs -f gameap
+```
+
+## Detrás de un proxy inverso
+
+Si el HTTPS lo sirve nginx o Traefik, no es necesario configurar certificados en el
+panel. El proxy debe enviar la cabecera `X-Forwarded-Proto: https` y **sobrescribirla**, no
+añadirla.
+
+El puerto 31718 normalmente no pasa por un proxy inverso: los daemons deben conectarse al panel
+directamente. Consulte [HTTPS y certificados](/es/https.html) para más detalles.
+
+## Actualización
+
+```bash
+docker compose pull
+docker compose up -d
+```
+
+Antes de actualizar, haga una copia de seguridad de la base de datos y del volumen de datos: las
+migraciones se aplican al arrancar y son irreversibles.
+
+```bash
+docker compose exec postgres pg_dump -U gameap gameap > gameap-backup.sql
+```
diff --git a/es/install/install_on_linux.md b/es/install/install_on_linux.md
new file mode 100644
index 0000000..bc62dcc
--- /dev/null
+++ b/es/install/install_on_linux.md
@@ -0,0 +1,122 @@
+---
+title: Instalación en Linux
+layout: default
+lang: es
+category: Instalación de GameAP
+order: 100
+---
+
+## Instalación
+
+La instalación en Linux se realiza con un único comando:
+```shell
+bash <(curl -s https://gameap.com/install.sh)
+```
+
+Durante el proceso de instalación, se le pedirá que introduzca cierta información.
+
+### Host
+
+Especifique el dominio o la dirección IP en la que el panel estará accesible.
+
+En el caso de una dirección IP, debe ser una dirección asignada a la interfaz
+de red del VDS. Si su red utiliza NAT, no especifique
+la IP externa, sino la interna, y luego configure el reenvío
+de puertos.
+
+Se puede especificar cualquier dominio, pero no olvide configurar el DNS.
+
+Ejemplos de valores correctos:
+* 10.182.104.8
+* 10.182.104.8:2080
+* example.com
+* http://example.com
+
+### Base de datos
+
+La base de datos donde se almacenarán los datos: usuarios, información sobre los servidores, etc.
+Puede usar [PostgreSQL](https://www.postgresql.org/),
+[MySQL](https://www.mysql.com/) y
+[SQLite](https://www.sqlite.org/).
+
+PostgreSQL se recomienda en la mayoría de los casos. Si se espera que la carga de su servidor
+sea baja y no planea usar más de 10 servidores de juego,
+puede usar SQLite.
+
+Algunas distribuciones pueden tener [MariaDB](https://mariadb.org/) instalado.
+
+## Finalización de la instalación
+
+Al final de la instalación, se mostrarán los datos de acceso al panel.
+No olvide guardar esta información para acceder al panel.
+
+
+
+## Opciones adicionales de instalación
+
+### Versión en desarrollo
+
+Puede instalar la versión actualmente en desarrollo pasando los parámetros
+adicionales `--github --branch=develop` al instalador.
+En este caso, la instalación tardará notablemente más, ya que se
+realiza desde el código fuente.
+
+```shell
+bash <(curl -s https://gameap.com/install.sh) \
+ --github \
+ --branch=develop
+```
+
+### Instalación no interactiva
+
+Este tipo de instalación le permite instalar el panel sin introducir ningún dato
+durante el proceso. Pase los parámetros y el instalador no necesitará
+ninguna entrada adicional por su parte.
+
+```shell
+bash <(curl -s https://gameap.com/install.sh) \
+ --non-interactive \
+ --host=panel.example.com \
+ --port=8025 \
+ --database=sqlite
+```
+
+> En `--host` indique la dirección por la que el panel estará accesible: un nombre de dominio o la
+> IP externa del servidor. El valor pasa a `HTTP_HOST`, y de él el panel deduce la dirección de
+> escucha: con `--host=127.0.0.1` los listeners HTTP y gRPC se levantan solo en la interfaz de
+> loopback, y no podrán conectarse ni los administradores remotos ni los daemons de otras máquinas.
+
+Parámetros principales:
+
+| Parámetro | Propósito |
+|-----------------------|---------------------------------------------------------------------|
+| `--non-interactive` | No hacer preguntas |
+| `--host` | Dirección en la que el panel estará accesible |
+| `--port` | Puerto del panel, `8025` por defecto |
+| `--grpc-port` | Puerto gRPC para los daemons, `31718` por defecto |
+| `--database` | `sqlite`, `mysql` o `postgres` |
+| `--database-host` | Host de la base de datos |
+| `--database-port` | Puerto de la base de datos |
+| `--database-name` | Nombre de la base de datos |
+| `--database-username` | Usuario de la base de datos |
+| `--database-password` | Contraseña del usuario de la base de datos |
+| `--with-daemon` | Instalar también GameAP Daemon |
+| `--version` | Versión específica del panel |
+
+SQLite no necesita parámetros de conexión: el archivo de la base de datos se crea automáticamente.
+
+> Los parámetros `--path` y `--web-server` son restos de GameAP 3 y no tienen efecto al
+> instalar GameAP 4: el panel es un único ejecutable con una interfaz web integrada y no
+> necesita un servidor web separado.
+
+### Instalación completa
+
+Para instalar GameAP Daemon además del propio panel,
+añada el parámetro `--with-daemon`.
+
+Este método se recomienda si planea alojar tanto el panel
+como los servidores de juego en el mismo VDS.
+
+```Shell
+bash <(curl -s https://gameap.com/install.sh) --with-daemon
+```
diff --git a/es/install/install_on_windows.md b/es/install/install_on_windows.md
new file mode 100644
index 0000000..6d70367
--- /dev/null
+++ b/es/install/install_on_windows.md
@@ -0,0 +1,97 @@
+---
+title: Instalación en Windows
+layout: default
+lang: es
+category: Instalación de GameAP
+order: 101
+---
+
+## Versiones compatibles
+
+| Versión | Compatible |
+|-------------|------------|
+| Server 2025 | ✔ |
+| Server 2022 | ✔ |
+| Server 2019 | ✔ |
+| 11 | ✔ |
+| 10 | ✔ |
+
+
+## Descarga de GameAP Control
+
+Necesita descargar la utilidad GameAP Control (gameapctl)
+para gestionar el entorno de GameAP.
+
+Para ello, vaya a la página de versiones de gameapctl en Github:
+[https://github.com/gameap/gameapctl/releases](https://github.com/gameap/gameapctl/releases)
+
+Seleccione la última versión y haga clic en ella.
+
+
+
+Después, busque la versión adecuada para usted.
+La arquitectura más común es Windows AMD64,
+por lo que lo más probable es que necesite descargar este archivo:
+
+
+
+## Instalación del panel mediante GameAP Control UI
+
+Después de descargar el archivo de gameapctl, ejecútelo.
+
+Se abrirá una ventana del navegador, en la que debe hacer clic en "Install"
+en la sección Web/API.
+
+
+
+### Parámetros de instalación
+
+Especifique los datos necesarios para la instalación.
+
+
+
+#### Host
+
+Especifique el dominio o la dirección IP donde estará accesible el panel.
+
+En el caso de una dirección IP, debe ser la dirección asignada a
+la interfaz de red del VDS. Si su red utiliza NAT,
+no especifique la IP externa, sino la interna,
+y luego configure el reenvío de puertos.
+
+Se puede especificar cualquier dominio, pero no olvide configurar el DNS.
+
+Ejemplos de valores correctos:
+* 10.182.104.8
+* 10.182.104.8:2080
+* example.com
+* http://example.com
+
+#### Base de datos
+
+La base de datos donde se almacenarán los datos: usuarios, información de los servidores, etc.
+Puede utilizar:
+* [PostgreSQL](https://www.postgresql.org/). Recomendado para proyectos grandes con muchos servidores de juego y usuarios.
+* [MySQL](https://www.mysql.com/)/[MariaDB](https://mariadb.org/)
+* [SQLite](https://www.sqlite.org/). Si se espera que la carga de su servidor sea baja y no planea utilizar más de 10 servidores de juego.
+
+#### Instalación de GameAP Daemon
+
+Además del panel web, también puede instalar
+la parte de servidor GameAP Daemon, que gestiona las operaciones de los servidores de juego.
+
+### Finalización de la instalación
+
+Espere a que finalice la instalación.
+Algunas etapas pueden tardar un tiempo considerable.
+
+No olvide guardar los datos de acceso y la información de la base de datos
+que se mostrarán al final.
+
+
diff --git a/es/multi_instance.md b/es/multi_instance.md
new file mode 100644
index 0000000..b361a22
--- /dev/null
+++ b/es/multi_instance.md
@@ -0,0 +1,140 @@
+---
+title: Múltiples instancias del panel
+layout: default
+lang: es
+category: Administración
+order: 338
+---
+
+El panel puede ejecutarse como varias instancias detrás de un balanceador de carga — para
+tolerancia a fallos o para repartir la carga. Para ello, las instancias deben compartir el
+estado: la base de datos, la caché, los archivos y el intercambio de eventos.
+
+> La mayoría de las instalaciones no necesitan esto. Un solo panel gestiona cientos de
+> servidores de juego — el trabajo pesado lo hacen los daemons en los servidores dedicados,
+> no el panel.
+
+## Qué debe ser compartido
+
+| Componente | Configuración | Qué ocurre en caso contrario |
+|-------------------|--------------------------------------------|--------------------------------------------------------------|
+| Base de datos | PostgreSQL o MySQL | SQLite no está diseñado para varias instancias conectadas |
+| Caché | `CACHE_DRIVER=redis` | Con `memory`, las sesiones y las claves de configuración solo son visibles para una instancia |
+| Intercambio de eventos | `PUBSUB_DRIVER=redis` o `postgres` | Con `memory`, las instancias nunca se enteran de los eventos de las demás |
+| Archivos | `FILES_DRIVER=s3` | Con `local`, cada instancia tiene sus propios archivos y sus propios certificados |
+
+Ejemplo de configuración:
+
+```dotenv
+DATABASE_DRIVER=postgres
+DATABASE_URL=postgres://gameap:password@db.example.com:5432/gameap?sslmode=require
+
+CACHE_DRIVER=redis
+CACHE_REDIS_ADDR=redis.example.com:6379
+
+PUBSUB_DRIVER=redis
+PUBSUB_REDIS_ADDR=redis.example.com:6379
+PUBSUB_INSTANCE_ID=panel-1
+
+FILES_DRIVER=s3
+FILES_S3_ENDPOINT=https://s3.example.com
+FILES_S3_BUCKET=gameap
+FILES_S3_ACCESS_KEY_ID=...
+FILES_S3_SECRET_ACCESS_KEY=...
+```
+
+`AUTH_SECRET` y `ENCRYPTION_KEY` deben ser **idénticos** en todas las instancias: de lo
+contrario, un token emitido por una no será aceptado por otra, y los datos cifrados serán
+ilegibles.
+
+## Identificador de instancia
+
+```dotenv
+PUBSUB_INSTANCE_ID=panel-1
+```
+
+El valor debe ser **único para cada instancia**. Las instancias lo utilizan para distinguir
+sus propios mensajes de los ajenos y para dirigirse respuestas entre sí.
+
+Si la variable no está definida, se utiliza el valor `default` — el mismo en cada instancia, y
+el intercambio entre instancias no funcionará. Defínala explícitamente.
+
+## Cómo funciona
+
+Un daemon se conecta a una de las instancias — aquella a la que el balanceador de carga lo
+haya dirigido — y mantiene una conexión persistente con ella. Ninguna otra instancia puede
+alcanzar ese daemon directamente.
+
+Así, un comando que llega a una instancia que no posee la conexión con el daemon necesario se
+reenvía a través del intercambio de eventos compartido a la instancia que sí la posee; la
+respuesta regresa por el mismo camino. Las operaciones con archivos, la consola y las
+peticiones de métricas funcionan del mismo modo.
+
+De esto se deduce que el intercambio de eventos compartido es obligatorio: sin él, el panel
+solo controlará los daemons que hayan conectado con esa instancia en particular.
+
+Las tareas que aparecieron en la base de datos mientras un daemon estaba desconectado, o
+mientras su conexión pertenecía a otra instancia, se entregan inmediatamente al conectarse, en
+lugar de esperar a la próxima reconexión.
+
+## Balanceador de carga y puertos
+
+Separe los dos flujos:
+
+* **HTTP (8025)** — puede balancearse de la forma habitual; la afinidad de sesión no es
+ necesaria: los tokens se verifican con la clave compartida, y el estado de la sesión reside
+ en la caché compartida.
+* **gRPC (31718)** — las conexiones son de larga duración. El balanceador de carga debe ser
+ capaz de mantenerlas y no cortarlas por un tiempo de inactividad: el canal no tiene un
+ mecanismo keepalive propio, y la disponibilidad se apoya en un heartbeat cada 30 segundos.
+
+Establezca `GRPC_EXTERNAL_HOST` a la dirección en la que los daemons ven el panel a través del
+balanceador, y `GRPC_EXTERNAL_PORT` si el puerto se publica externamente con un número
+diferente.
+
+## Certificados
+
+**Los certificados gRPC deben ser compartidos.** Se almacenan en el almacenamiento de archivos
+del panel, por lo que con `FILES_DRIVER=s3` son compartidos automáticamente por todas las
+instancias.
+
+Con `FILES_DRIVER=local`, cada instancia generará su propia autoridad de certificación, y un
+daemon registrado a través de una instancia no podrá conectarse a otra.
+
+### HTTPS
+
+Si el certificado del panel se emite mediante ACME, hay dos cosas que debe saber.
+
+El almacenamiento ACME también reside en el almacenamiento de archivos — con S3 es compartido.
+
+El bloqueo que impide que varias instancias soliciten un certificado al mismo tiempo funciona
+a través de Redis. Está activo solo con `CACHE_DRIVER=redis`; con la caché en memoria el
+bloqueo es local, y las instancias se estorbarán entre sí.
+
+El desafío `http-01` requiere que la petición de la autoridad de certificación llegue a la
+misma instancia que inició la emisión. Es más sencillo utilizar `dns-01` o terminar TLS en el
+balanceador. Consulte [HTTPS y certificados](/es/https.html).
+
+## Limitaciones
+
+**Los contadores de intentos de inicio de sesión** residen en la caché. Con
+`CACHE_DRIVER=redis` son compartidos; con `memory` cada instancia tiene los suyos, por lo que
+el límite efectivo se multiplica por el número de instancias.
+
+**El registro de auditoría** se escribe en la salida de cada instancia por separado. Para
+agregarlos se necesita un recolector de registros externo, consulte [Seguridad](/es/security.html).
+
+**Las tareas de los plugins** se desduplican entre instancias mediante bloqueos distribuidos,
+de modo que la tarea periódica de un plugin se ejecuta una sola vez, y no en cada instancia.
+
+## Comprobación
+
+Tras el arranque, asegúrese de que:
+
+* un daemon registrado a través de una instancia puede ser controlado a través de otra;
+* un inicio de sesión realizado en una instancia es válido en las demás;
+* el gestor de archivos se abre independientemente de la instancia a la que haya llegado la
+ petición.
+
+Si algo de esto no funciona, la causa es casi siempre que uno de los cuatro componentes
+compartidos ha permanecido local.
diff --git a/es/plugins/development.md b/es/plugins/development.md
new file mode 100644
index 0000000..79e6097
--- /dev/null
+++ b/es/plugins/development.md
@@ -0,0 +1,226 @@
+---
+title: Desarrollo de plugins
+layout: default
+lang: es
+category: Plugins
+order: 342
+---
+
+## Arquitectura
+
+Un plugin de GameAP es un módulo WASM para el target `wasm32-wasip1` (WASI preview 1), compilado como reactor. El panel ejecuta cada plugin en un entorno aislado de [wazero](https://github.com/tetratelabs/wazero): sin sistema de archivos, sin red, sin variables de entorno; stdout y stderr se descartan.
+
+El panel y el plugin intercambian mensajes protobuf a través de la memoria lineal de WASM: el panel escribe la solicitud en la memoria del invitado usando el `malloc` exportado por el plugin, llama a la función y lee la respuesta. La versión de la API de plugins es `1`.
+
+Los tipos de mensajes y los SDK se publican desde el repositorio [github.com/gameap/gameap-proto](https://github.com/gameap/gameap-proto):
+
+* `gameap-plugin-sdk` (crates.io) — el SDK completo de Rust: la capa ABI, el trait `Plugin`, la macro `register_plugin!`, clientes tipados de funciones del host;
+* `@gameap/proto-as` (npm) — tipos de mensajes de AssemblyScript (sin la capa ABI);
+* `@gameap/proto` (npm) — tipos de TypeScript (para herramientas; actualmente no es posible compilar un plugin WASM en JS puro).
+
+## La interfaz del plugin
+
+Un plugin implementa el servicio `PluginService` de `plugin.proto`. Los métodos del servicio:
+
+| Método | Propósito |
+|---|---|
+| `GetInfo` | Devuelve los metadatos del plugin (`PluginInfo`) |
+| `Initialize` | Inicialización al cargar el plugin |
+| `Shutdown` | Apagado al descargar el plugin |
+| `HandleEvent` | Manejo de un evento del panel |
+| `GetSubscribedEvents` | La lista de tipos de eventos a los que el plugin se suscribe |
+| `GetHTTPRoutes` | La lista de rutas HTTP del plugin |
+| `HandleHTTPRequest` | Manejo de una solicitud HTTP en una ruta del plugin |
+| `GetFrontendBundle` | El frontend integrado: bundle JS y CSS (opcional) |
+| `GetServerAbilities` | Registro de los permisos de servidor del plugin (opcional) |
+
+En el SDK de Rust la interfaz está representada por el trait `Plugin` con implementaciones por defecto neutras — en la práctica solo `get_info` es obligatorio. La macro `register_plugin!` genera todas las exportaciones WASM necesarias, incluidas `malloc`/`free` y la comprobación de la versión de la API.
+
+## Metadatos de `PluginInfo`
+
+| Campo | Descripción |
+|---|---|
+| `id` | El identificador de cadena del plugin (véanse los requisitos a continuación) |
+| `name` | Nombre del plugin |
+| `version` | Versión (semver) |
+| `description` | Descripción breve |
+| `author` | Autor |
+| `license` | Licencia (por ejemplo, `MIT`) |
+| `homepage` | Enlace a la página del plugin |
+| `required_permissions` | Permisos que declara el plugin: se registran al instalar y se comprueban en cada llamada a una función del host (véase más abajo) |
+| `api_version` | Versión de la API de plugins, debe ser `"1"` |
+
+**Requisitos para `id`.** Utilice un identificador estable compuesto por caracteres del alfabeto base32 `a-z2-7`, sin guiones. El panel normaliza el id: una cadena con guiones u otros caracteres se reemplaza por un hash, lo que rompe las rutas `/api/plugins/{id}/...` y `/plugins/{id}/...`. Evite también los ids puramente numéricos: dicho identificador se trata como un ID numérico decimal (el análisis del id primero intenta interpretar la cadena como un número). Ejemplos de ids válidos de plugins reales: `hexeditor4jm2`, `ezvdsxmlu6fbk`, `dshdabjp2l73a`.
+
+## Eventos
+
+Un plugin se suscribe a los eventos del panel con el método `GetSubscribedEvents`. Los tipos de eventos (listados sin el prefijo `EVENT_TYPE_`):
+
+| Evento | Cancelable | Entrega |
+|---|---|---|
+| `SERVER_PRE_START`, `SERVER_PRE_STOP`, `SERVER_PRE_RESTART`, `SERVER_PRE_INSTALL`, `SERVER_PRE_UPDATE`, `SERVER_PRE_REINSTALL`, `SERVER_PRE_DELETE` | Sí | Síncrona |
+| `SERVER_POST_START`, `SERVER_POST_STOP`, `SERVER_POST_RESTART`, `SERVER_POST_INSTALL`, `SERVER_POST_UPDATE`, `SERVER_POST_REINSTALL`, `SERVER_POST_DELETE` | No | Asíncrona |
+| `SERVER_CREATED`, `SERVER_UPDATED`, `SERVER_DELETED` | No | Asíncrona |
+| `DAEMON_TASK_CREATED`, `DAEMON_TASK_COMPLETED`, `DAEMON_TASK_FAILED` | No | Asíncrona |
+
+Los pre-eventos se entregan de forma síncrona y bloquean la operación: un plugin puede cancelarla devolviendo un `EventResult` con `should_cancel = true` y un `message`, o modificar los datos mediante `modified_data`. Los post-eventos y los demás tipos se entregan de forma asíncrona y no afectan a la operación.
+
+Un evento de servidor contiene una instantánea completa del servidor (`ServerEventPayload`); un evento de tarea contiene los datos de la tarea del daemon (`TaskEventPayload`). El tiempo de espera del manejador de eventos es de 10 segundos; una vez expirado, el plugin se desactiva hasta que se reinicie el panel.
+
+## Rutas HTTP del plugin
+
+Un plugin registra rutas HTTP con el método `GetHTTPRoutes`; cada ruta es un mensaje `HTTPRoute`:
+
+| Campo | Descripción |
+|---|---|
+| `path` | La ruta, comienza con `/`; se admiten parámetros de ruta de la forma `{name}` |
+| `methods` | Métodos: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS` |
+| `requires_auth` | Requerir un usuario autenticado |
+| `admin_only` | Requerir un administrador |
+| `description` | Descripción de la ruta |
+
+Las rutas están disponibles en `/api/plugins/{plugin_id}/...`. La solicitud pasada al plugin contiene el método, la ruta relativa, las cabeceras, los parámetros de ruta y de consulta, el cuerpo (no mayor de 1 MB) y la sesión del usuario (si la solicitud está autenticada). El plugin devuelve un `HTTPResponse` con los campos `status_code`, `headers` y `body`.
+
+## Funciones del host
+
+Todas las llamadas desde un plugin al panel y al mundo exterior pasan por funciones del host, agrupadas en módulos `gameap-*`:
+
+| Módulo | Funciones | Propósito |
+|---|---|---|
+| `gameap-log` | `log` | Escritura en el registro del panel |
+| `gameap-cache` | `get`, `set`, `delete` | La caché compartida del panel (claves con prefijo `plugin:`) |
+| `gameap-crypto` | `random_uint64`, `random_string`, `argon2_hash`, `argon2_verify` | Generadores de valores aleatorios, hash Argon2 |
+| `gameap-http` | `fetch` | Solicitudes HTTP salientes con protección SSRF |
+| `gameap-storage` | `get`, `set`, `delete`, `list` | El almacenamiento clave-valor persistente del plugin |
+| `gameap-servercontrol` | `start_server`, `stop_server`, `restart_server`, `update_server`, `install_server`, `reinstall_server` | Control de servidores de juego (devuelve un `task_id`) |
+| `gameap-servers` | `find_servers`, `get_server`, `save_server`, `delete_server` | Servidores de juego |
+| `gameap-users` | `find_users`, `get_user` | Usuarios del panel |
+| `gameap-nodes` | `find_nodes`, `get_node` | Servidores dedicados (nodos) |
+| `gameap-games` | `find_games`, `get_game` | Juegos |
+| `gameap-gamemods` | `find_game_mods`, `get_game_mod` | Mods de juegos |
+| `gameap-daemontasks` | `find_daemon_tasks`, `create_daemon_task` | Tareas del daemon |
+| `gameap-serversettings` | `find_server_settings`, `save_server_setting` | Configuración de servidores de juego |
+| `gameap-nodefs` | `read_dir`, `mk_dir`, `copy`, `move`, `download`, `upload`, `remove`, `get_file_info`, `chmod`, `hash`, `create_archive`, `extract_archive`, `start_create_archive`, `start_extract_archive`, `cancel_archive`, `get_archive_operation` | Operaciones con archivos en un nodo |
+| `gameap-nodecmd` | `execute_command` | Ejecución de un comando en un nodo |
+
+Detalles:
+
+* `gameap-http` actúa como proxy de las solicitudes a través del panel con protección SSRF: por defecto solo se permite el esquema `https`, las IPs privadas y de servicio están bloqueadas, y el cuerpo de la respuesta está limitado a 10 MB. La política se configura con las variables de entorno `PLUGIN_HTTP_*` (véase [Instalación y administración](/es/plugins/management.html)).
+* `gameap-storage` es el almacenamiento persistente del plugin, aislado por `plugin_id`, con vinculación opcional de registros a una entidad (`entity_type`, `entity_id`). Utilícelo para la configuración del plugin: el mecanismo de configuración separado (`config` en `InitializeRequest`) no se usa en la versión actual del panel.
+* `gameap-nodefs` y `gameap-nodecmd` trabajan con archivos y comandos en el servidor dedicado (nodo) a través de GameAP Daemon. Las lecturas (`read_dir`, `download`, `get_file_info`, `hash`, `get_archive_operation`) requieren el permiso `files_read`; todo lo que escribe — incluidos `chmod` y las funciones de archivado — requiere `files`, que incluye `files_read`.
+* `gameap-nodefs.download` sin `offset`/`length` devuelve el archivo entero en un solo mensaje y está limitado por `PLUGIN_NODEFS_MAX_INLINE`; un archivo mayor se rechaza con un error que indica ambos tamaños. Indicar una ventana `offset`/`length` permite leer ese archivo por partes: solo la ventana tiene que caber en el límite, y un `length` mayor que el límite se rechaza en lugar de recortarse en silencio. La respuesta repite `offset` e incluye `total_size`, y `length: 0` junto con un `offset` lee tanto como permita el límite — por eso un bucle de paginación avanza según la longitud de `content` hasta alcanzar `total_size`. Leer al final del archivo o más allá no es un error: la respuesta trae `content` vacío.
+
+**Comprobaciones en una llamada al host.** Las funciones del host se ejecutan con los propios privilegios del panel, pero la llamada no pasa sin comprobaciones:
+
+* **Permisos.** Los módulos privilegiados están protegidos por los permisos registrados para el plugin: `files_read`, `files`, `manage_servers`, `node_commands`, `listen_events`, `manage_rbac`, `secrets`, `ssh`, `manage_nodes` (`manage_games`, `manage_game_mods` y `manage_users` están reservados para operaciones de escritura que todavía no existen). Un permiso más amplio satisface a uno más estrecho: `files` incluye `files_read`. La aplicación es transitoria: `PLUGIN_PERMISSIONS_ENFORCE` vale `false` por defecto, así que los permisos se registran, se muestran y se pueden editar mientras todas las comprobaciones siguen pasando; una versión futura lo pondrá en `true` por defecto, así que declare ya los permisos que su plugin necesita. Las llamadas de modificación de `gameap-nodes` comprueban su permiso independientemente de ese ajuste. Una llamada rechazada responde `plugin permission required`.
+* **Rutas.** Toda ruta entregada a `gameap-nodefs`, el `work_dir` de `gameap-nodecmd` y un archivo del nodo referenciado por una respuesta HTTP se comprueban en el panel antes de llegar al daemon. Una ruta con un segmento `..` o un byte NUL se rechaza siempre. `PLUGIN_NODEFS_PATH_POLICY` puede además confinar las rutas al `work_path` del nodo o a los directorios de los servidores de juego que haya en él; en todos los modos restringidos queda abierto el directorio de servicio del propio plugin, `/.plugins/`, y ahí es donde deben ir los archivos de trabajo en el nodo. Una llamada rechazada responde `path policy: : `.
+* **Límites de frecuencia.** Cada plugin tiene su propio cubo de tokens por clase: `gameap-nodefs` 50 llamadas/s (ráfaga 200), `gameap-http` 20/s (50), `gameap-ssh` 20/s (60), `gameap-rbac` 10/s (50), `gameap-nodecmd` y el control de servidores 5/s (20). Una llamada rechazada responde `rate limited: ...`; el plugin nunca se desactiva por ello.
+* **Registro de auditoría.** Las operaciones privilegiadas — control de servidores, comandos en el nodo, escritura de archivos, SSH, cambios de RBAC — se registran con el plugin como actor, incluidos los rechazos.
+
+Aun así, la instalación de plugins está encomendada únicamente a los administradores — instale plugins solo de fuentes de confianza.
+
+## Límites en tiempo de ejecución
+
+| Límite | Valor |
+|---|---|
+| Llamadas concurrentes a un plugin | 1 (las llamadas se serializan) |
+| Tiempo de espera de una llamada al plugin | 30 s (inicio del módulo — 60 s) |
+| Tiempo de espera del manejador de eventos | 10 s |
+| Tamaño de un archivo `.wasm` subido | 100 MB |
+| Cuerpo de una solicitud HTTP a un plugin | 1 MB |
+| Cuerpo de una respuesta de `gameap-http` | 10 MB |
+| Un mensaje `download` / `upload` de `gameap-nodefs` | 32 MB (`PLUGIN_NODEFS_MAX_INLINE`); un `download` con ventana se mide por la ventana |
+
+Cuando se supera el tiempo de espera de una llamada, el panel desactiva el plugin hasta un reinicio. El sistema de archivos y la red no están disponibles desde WASM — solo a través de las funciones del host.
+
+## Compilación de un plugin con Rust
+
+La estructura de un proyecto mínimo (siguiendo [plugin-hex-editor](https://github.com/gameap/plugin-hex-editor)):
+
+`rust-toolchain.toml`:
+```toml
+[toolchain]
+channel = "1.94.0"
+targets = ["wasm32-wasip1"]
+```
+
+`Cargo.toml`:
+```toml
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+gameap-plugin-sdk = "0.1"
+
+[profile.release]
+opt-level = "z"
+lto = true
+strip = true
+```
+
+Un `src/lib.rs` mínimo — un plugin que solo devuelve metadatos y un frontend integrado:
+
+```rust
+#![cfg(target_arch = "wasm32")]
+
+use gameap_plugin_sdk::proto::gameap::plugin as pb;
+use gameap_plugin_sdk::{Plugin, PluginError, register_plugin};
+
+const FRONTEND_JS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/plugin.js"));
+const FRONTEND_CSS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/plugin.css"));
+
+#[derive(Default)]
+struct MyPlugin;
+
+impl Plugin for MyPlugin {
+ fn get_info(&mut self, _req: pb::GetInfoRequest) -> Result {
+ Ok(pb::PluginInfo {
+ id: "myplugin2j7d".into(),
+ name: "My Plugin".into(),
+ version: env!("CARGO_PKG_VERSION").into(),
+ description: "My first GameAP plugin".into(),
+ author: "Me".into(),
+ api_version: "1".into(),
+ ..Default::default()
+ })
+ }
+
+ fn get_frontend_bundle(
+ &mut self,
+ _req: pb::GetFrontendBundleRequest,
+ ) -> Result {
+ Ok(pb::GetFrontendBundleResponse {
+ bundle: FRONTEND_JS.to_vec(),
+ has_bundle: !FRONTEND_JS.is_empty(),
+ styles: FRONTEND_CSS.to_vec(),
+ has_styles: !FRONTEND_CSS.is_empty(),
+ })
+ }
+}
+
+register_plugin!(MyPlugin);
+```
+
+Compilación:
+
+```bash
+cargo build --target wasm32-wasip1 --release
+# opcional — reducción de tamaño (binaryen):
+wasm-opt -Oz target/wasm32-wasip1/release/my_plugin.wasm -o my-plugin.wasm
+```
+
+El archivo `.wasm` resultante se instala a través de la interfaz del panel o se copia en el directorio `plugins/` (véase [Instalación y administración](/es/plugins/management.html)). El frontend se compila por separado y se integra en el `.wasm` (véase [Frontend del plugin](/es/plugins/frontend.html)).
+
+## Desarrollo en AssemblyScript
+
+Todavía no existe un SDK listo para AssemblyScript: el paquete `@gameap/proto-as` proporciona solo los tipos de mensajes, y la capa ABI (empaquetado de punteros, manejo de errores, exportación de `malloc`/`free`) hay que escribirla a mano. Debido al recolector de basura de AssemblyScript, los búferes pasados al host deben fijarse (`__pin`/`__unpin`), y un analizador JSON y utilidades básicas hay que implementarlos uno mismo o traerlos de bibliotecas de terceros. Un ejemplo funcional de plugin en AssemblyScript es [plugin-minecraft-modrinth](https://github.com/gameap/plugin-minecraft-modrinth).
+
+## Validación de un plugin antes de la instalación
+
+El panel puede validar un archivo `.wasm` sin instalarlo — el endpoint `POST /api/admin/plugins/upload/dry-run` (multipart, campo `file`). La respuesta devuelve los metadatos del plugin, las rutas HTTP, los permisos de servidor, las suscripciones a eventos, si hay un frontend presente y una lista de errores. La misma comprobación se ejecuta en la interfaz al subir un archivo.
+
+## Ejemplos de plugins
+
+* [plugin-hex-editor](https://github.com/gameap/plugin-hex-editor) — un plugin mínimo de Rust: metadatos más un frontend integrado, sin funciones del host.
+* [plugin-goldsrc-addons](https://github.com/gameap/plugin-goldsrc-addons) — un plugin de Rust con rutas HTTP, operaciones con archivos en un nodo (`nodefs`), ejecución de comandos (`nodecmd`) y llamadas a la API del panel (RCON) desde el frontend.
+* [plugin-minecraft-modrinth](https://github.com/gameap/plugin-minecraft-modrinth) — un plugin en AssemblyScript: llamadas a una API externa (modrinth.com) a través de `gameap-http`, caché, almacenamiento persistente.
diff --git a/es/plugins/frontend.md b/es/plugins/frontend.md
new file mode 100644
index 0000000..d1608ee
--- /dev/null
+++ b/es/plugins/frontend.md
@@ -0,0 +1,135 @@
+---
+title: Frontend de plugins
+layout: default
+lang: es
+category: Plugins
+order: 343
+---
+
+## Cómo se integra el frontend
+
+Un plugin puede contener una interfaz: componentes de Vue 3 empaquetados en JS y CSS e integrados en el mismo archivo `.wasm`. El panel sirve los bundles de frontend concatenados de todos los plugins cargados en `/plugins.js` y `/plugins.css` (solo para usuarios autenticados). El cargador del panel importa este código como un módulo y registra cada objeto `PluginDefinition` exportado.
+
+El frontend de un plugin se ejecuta en el contexto de la SPA principal del panel (no es un iframe ni un web component) y utiliza las bibliotecas del panel a través de objetos globales:
+
+| Objeto global | Contenido |
+|---|---|
+| `window.Vue` | Vue 3 |
+| `window.VueRouter` | Vue Router |
+| `window.Pinia` | Pinia |
+| `window.axios` | La instancia de axios configurada del panel (con autorización) |
+| `window.NaiveUI` | Naive UI |
+
+## El manifiesto `PluginDefinition`
+
+No hay un archivo de manifiesto separado: el manifiesto del frontend es el objeto `PluginDefinition` exportado desde el bundle:
+
+| Campo | Obligatorio | Descripción |
+|---|---|---|
+| `id` | Sí | El identificador del plugin; debe coincidir con el `id` del `PluginInfo` del backend |
+| `name` | Sí | Nombre del plugin |
+| `version` | Sí | Versión (semver) |
+| `apiVersion` | Sí | Versión de la API del frontend, solo `'1.0'` |
+| `description` | No | Descripción |
+| `author` | No | Autor |
+| `routes` | No | Páginas propias del plugin |
+| `menuItems` | No | Elementos del menú izquierdo (sidebar) |
+| `slots` | No | Componentes en los slots integrados del panel |
+| `homeButtons` | No | Botones en la página de inicio |
+| `fileEditors` | No | Editores de archivos para el gestor de archivos |
+| `translations` | No | Diccionarios de traducción `{ en: {...}, ru: {...} }` |
+| `onInit` | No | Hook de inicialización que se llama cuando se registra el plugin |
+
+## Puntos de integración
+
+| Mecanismo | Dónde aparece |
+|---|---|
+| `routes` | Las páginas propias del plugin en `/plugins/{id}/...` |
+| El slot `server-tabs` | Una pestaña en la página del servidor de juego (junto a "Console", "Files", etc.) |
+| El slot `dashboard-widgets` | Un widget en la página de inicio del panel |
+| `homeButtons` | Botones en la página de inicio |
+| `menuItems` | Elementos del sidebar (las secciones `servers`, `admin` y `custom`) |
+| El slot `admin-user-info` | Un bloque en el diálogo de información del usuario (administración) |
+| `fileEditors` | El menú contextual del gestor de archivos: apertura de un archivo en el editor del plugin |
+
+Los slots `sidebar-sections` y `admin-pages` están declarados en el SDK, pero no están integrados en la versión actual del panel.
+
+Detalles:
+
+* Para `server-tabs` está disponible una comprobación de permisos: `checkPermission: { type: 'hasServerPermissions', permissions: [...] }` — la pestaña se muestra solo si el usuario tiene todos los permisos indicados para el servidor (los permisos de plugin tienen la forma `plugin:{id}:...`, por ejemplo `plugin:ezvdsxmlu6fbk:manage`).
+* Los editores de archivos se registran con reglas de coincidencia (`fileName`, `extensions`, `pathContains`, `fullPath`, `gameCode` y otras): el editor más específico entre los que coinciden se convierte en el editor predeterminado para el archivo y se abre con un doble clic. Un editor marcado con `contextMenuOnly: true` nunca es el predeterminado y solo se ofrece en el menú contextual, que es lo que necesita un editor que coincide con todos los archivos (`allFiles: true`) para no apropiarse de todas las vistas previas. `menuLabel` sustituye el texto «Edit with …» del elemento del menú, y `checkPermission: { type: 'hasServerPermissions', permissions: [...] }` oculta el elemento a los usuarios que no tengan los permisos de servidor indicados.
+* El componente del editor recibe las props `content`, `filePath`, `fileName`, `extension`, `fileSize`, `fileMtime`, `disk`, `pluginId`, `gameCode` y `gameName`, y emite los eventos `save` y `close`; el guardado del archivo en el servidor lo realiza el propio panel.
+* El panel descarga el archivo antes de montar el editor, y no lo hace con archivos de más de 1 MB. A un editor que declara `contentType: 'none'` no se le pasa la prop `content` en absoluto: carga por su cuenta lo que necesita, por lo que el límite de tamaño no se le aplica y así se puede abrir un archivo de cualquier tamaño; `fileSize` y `fileMtime` provienen del listado del directorio, de modo que un editor así puede describir el archivo sin descargarlo. Con `contentType: 'text'` (el valor predeterminado) el contenido llega como cadena, y con `'binary'` como `ArrayBuffer`.
+
+## Traducciones
+
+Las traducciones se definen con los diccionarios `translations: { en: {...}, ru: {...} }`. En los campos `label`, `name` y `text` se admiten referencias a claves de traducción de la forma `@:key` — el panel sustituye la cadena correspondiente al idioma actual de la interfaz.
+
+## Acceso a la API
+
+El frontend de un plugin utiliza `window.axios` — la misma instancia que usa el panel, con la autorización del usuario actual. A través de ella se puede acceder a:
+
+* la API del panel — por ejemplo, enviar un comando RCON con `POST /api/servers/{id}/rcon`, o trabajar con archivos a través de `/api/file-manager/...`;
+* el backend propio del plugin en `/api/plugins/{id}/...` (las rutas HTTP registradas por la parte WASM).
+
+## El SDK `@gameap/plugin-sdk`
+
+El paquete npm `@gameap/plugin-sdk` proporciona:
+
+* tipos de TypeScript: `PluginDefinition`, `PluginRoute`, `PluginMenuItem`, `PluginSlotComponent`, `PluginHomeButton`, `PluginFileEditor`, `PluginContext` y otros;
+* hooks de contexto: `usePluginContext`, `useServer`, `useServerId`, `useServerAbilities`, `useCurrentUser`, `useIsAdmin`, `useIsAuthenticated`, `usePluginRoute`, `usePluginId`;
+* hooks de traducción: `usePluginTrans`, `providePluginTrans`;
+* los componentes de UI del panel (reexportados desde `@gameap/ui`): `GCard`, `GDataTable`, `GModal`, `GStatusBadge`, `GSwitch` y otros;
+* `createPluginConfig` — una configuración de Vite lista para usar: una compilación en modo biblioteca (el módulo ES `plugin.js`) donde las dependencias externas (`vue`, `vue-router`, `pinia`, `axios`, `@gameap/ui`) se reescriben a objetos globales. Nota: el panel expone `window.NaiveUI` pero no `window.gameapUI`, por lo que los plugins reales usan su propia configuración de Vite con externals que apuntan a `naive-ui` (véase `frontend/vite.config.js` en [plugin-hex-editor](https://github.com/gameap/plugin-hex-editor) como ejemplo).
+
+## Compilación del frontend
+
+```bash
+npm run build # → dist/plugin.js (+ un archivo CSS)
+```
+
+El `plugin.js` y el CSS compilados se integran en el `.wasm` del plugin: para Rust, con un script `build.rs` que los copia en `OUT_DIR` y los incluye mediante `include_bytes!` (véase [Desarrollo de plugins](/es/plugins/development.html)); para AssemblyScript, con un script de generación de código (véase `scripts/embed-frontend.mjs` en plugin-minecraft-modrinth como ejemplo).
+
+## Depuración local
+
+El paquete `@gameap/debug` inicia un entorno de depuración con el frontend real del panel y una API simulada (MSW):
+
+```bash
+PLUGIN_PATH=./dist npx @gameap/debug
+```
+
+El entorno se abre en `http://localhost:5174`. Un panel de depuración flotante permite cambiar el tipo de usuario (administrador / usuario normal / invitado), el retardo de red y el idioma. El plugin debe estar compilado previamente (`npm run build`).
+
+## Un ejemplo de `PluginDefinition`
+
+Un ejemplo de una pestaña en la página del servidor de juego (siguiendo plugin-goldsrc-addons):
+
+```ts
+export const myPlugin: PluginDefinition = {
+ id: 'myplugin2j7d',
+ name: 'My Plugin',
+ version: '0.1.0',
+ apiVersion: '1.0',
+ description: 'My first GameAP plugin',
+ author: 'Me',
+ translations: {
+ en: { tab_label: 'My Plugin' },
+ ru: { tab_label: 'Мой плагин' },
+ },
+ slots: {
+ 'server-tabs': [
+ {
+ component: MyTab,
+ order: 100,
+ label: '@:tab_label',
+ icon: 'plug',
+ name: 'my-tab',
+ checkPermission: {
+ type: 'hasServerPermissions',
+ permissions: ['plugin:myplugin2j7d:manage'],
+ },
+ },
+ ],
+ },
+};
+```
diff --git a/es/plugins/index.md b/es/plugins/index.md
new file mode 100644
index 0000000..cb91153
--- /dev/null
+++ b/es/plugins/index.md
@@ -0,0 +1,41 @@
+---
+title: Plugins
+layout: default
+lang: es
+category: Plugins
+order: 340
+---
+
+Los plugins amplían la funcionalidad del panel GameAP: añaden nuevas páginas, pestañas en la página del servidor de juego, editores de archivos, botones en la página principal e integraciones con servicios externos.
+
+Un plugin es un único archivo `.wasm` (un módulo WASM compilado para `wasm32-wasip1`). Los plugins pueden escribirse en cualquier lenguaje que compile a WASM: existe un SDK listo para Rust y es posible desarrollar en AssemblyScript. Un plugin puede incluir un frontend en Vue 3 integrado en el mismo archivo `.wasm`: las páginas y los componentes del plugin se ejecutan directamente dentro de la interfaz del panel.
+
+
+
+*Un editor hexadecimal para archivos del servidor de juego: un ejemplo de plugin en funcionamiento.*
+
+## Seguridad
+
+Los plugins se ejecutan en un entorno aislado (un runtime WASM): no tienen acceso directo al sistema de archivos ni a la red, y cada llamada pasa por una interfaz controlada del panel. Solo un administrador del panel puede instalar y eliminar plugins. Al instalar desde el catálogo, el panel verifica el hash SHA-256 del archivo descargado.
+
+## Catálogo de plugins
+
+El catálogo oficial de plugins está disponible en [plugins.gameap.dev](https://plugins.gameap.dev/) (versión en ruso: [plugins.gameap.ru](https://plugins.gameap.ru/)). Allí pueden publicarse tanto plugins del equipo de GameAP como plugins de desarrolladores externos: cualquiera puede registrarse y publicar su propio plugin (consulte [Publicación en el catálogo](/es/plugins/publishing.html)).
+
+Los plugins del catálogo se instalan desde la interfaz del panel en unos pocos clics (consulte [Instalación y administración](/es/plugins/management.html)).
+
+### Plugins oficiales
+
+| Plugin | Descripción | Catálogo | Repositorio |
+|---|---|---|---|
+| FTP (files) | Administración de un servidor FTP(S)/SFTP en servidores dedicados (nodos) | [plugins.gameap.dev/plugins/files](https://plugins.gameap.dev/plugins/files) | [github.com/gameap/plugin-files](https://github.com/gameap/plugin-files) |
+| HEX Editor | Visualización y edición de archivos del servidor de juego en formato hexadecimal | [plugins.gameap.dev/plugins/hexeditor4jm2](https://plugins.gameap.dev/plugins/hexeditor4jm2) | [github.com/gameap/plugin-hex-editor](https://github.com/gameap/plugin-hex-editor) |
+| GoldSource Addons | Administración de plugins de Metamod y AMX Mod X en servidores GoldSource (Half-Life, CS 1.6, etc.) | [plugins.gameap.dev/plugins/ezvdsxmlu6fbk](https://plugins.gameap.dev/plugins/ezvdsxmlu6fbk) | [github.com/gameap/plugin-goldsrc-addons](https://github.com/gameap/plugin-goldsrc-addons) |
+| Minecraft Modrinth | Búsqueda, instalación y actualización de mods y plugins de Minecraft desde modrinth.com | [plugins.gameap.dev/plugins/dshdabjp2l73a](https://plugins.gameap.dev/plugins/dshdabjp2l73a) | [github.com/gameap/plugin-minecraft-modrinth](https://github.com/gameap/plugin-minecraft-modrinth) |
+
+## En esta sección
+
+* [Instalación y administración](/es/plugins/management.html) — instalación de plugins desde el catálogo y desde un archivo, actualización, eliminación, permisos, variables de entorno.
+* [Desarrollo de plugins](/es/plugins/development.html) — arquitectura del sistema de plugins, la interfaz del plugin, eventos, rutas HTTP, funciones del host, compilación con Rust.
+* [Frontend de plugins](/es/plugins/frontend.html) — integración de la interfaz del plugin en el panel, el manifiesto `PluginDefinition`, slots, SDK, depuración local.
+* [Publicación en el catálogo](/es/plugins/publishing.html) — registro en el catálogo, publicación de versiones, moderación, publicación desde CI/CD.
diff --git a/es/plugins/management.md b/es/plugins/management.md
new file mode 100644
index 0000000..e3cc197
--- /dev/null
+++ b/es/plugins/management.md
@@ -0,0 +1,68 @@
+---
+title: Instalación y gestión
+layout: default
+lang: es
+category: Plugins
+order: 341
+---
+
+## La página de Plugins
+
+La gestión de plugins está disponible para el administrador en la página **Administración** → **Plugins**. La página tiene dos pestañas:
+
+* **Instalados** — la lista de plugins cargados en el panel. La tabla muestra el nombre, la categoría, la valoración, el número de descargas, la versión y las acciones. Las insignias muestran el origen de la instalación (`file` — «Archivo local», `store` — «Tienda»), el estado y si hay una actualización disponible.
+* **Tienda** — la lista de plugins del catálogo plugins.gameap.dev con paginación en el lado del servidor. Los plugins ya instalados reciben una insignia; los de pago reciben un botón de compra de suscripción.
+
+## Instalación desde el catálogo
+
+1. En la pestaña **Tienda**, haga clic en el nombre del plugin o en el botón **Instalar**: se abrirá la ficha del plugin con su descripción, autor, licencia, etiquetas, valoración y lista de versiones.
+2. Seleccione una versión de la lista desplegable (por defecto se ofrece la más reciente) y haga clic en **Instalar**.
+3. El panel descarga el archivo `.wasm`, verifica su hash SHA-256, lo escribe en el directorio `plugins/`, carga el plugin y recarga la página (para actualizar `/plugins.js` con los frontends de los plugins).
+
+Para los plugins de pago (marcados como `requires_subscription`), se ofrece un botón de compra de suscripción en lugar de la instalación. Para permitir que el panel descargue plugins de pago, defina la clave de licencia en la variable de entorno `PLUGIN_STORE_LICENSE_KEY`.
+
+## Instalación desde un archivo
+
+1. En la pestaña **Instalados**, haga clic en el botón **Subir** y seleccione el archivo `.wasm` del plugin (de no más de 100 MB).
+2. Haga clic en **Comprobar**: el panel realiza una carga de prueba del módulo (una ejecución en seco, sin instalar) y muestra los metadatos del plugin: nombre, versión, autor, versión de la Plugin API, si hay rutas HTTP y un frontend, y una lista de errores de validación.
+3. Si el plugin es válido, haga clic en **Instalar**: el panel instala el plugin y recarga la página.
+
+## Actualización y eliminación
+
+Cuando se publica una nueva versión de un plugin instalado desde el catálogo, aparece una insignia de actualización en la tabla y la columna de versión muestra `instalada → última`. La actualización se realiza con el botón de la lista o desde la ficha del plugin: el panel descarga la nueva versión, verifica el hash, sustituye el archivo y recarga el plugin.
+
+La eliminación se realiza con el botón **Eliminar**, con confirmación. El panel descarga el plugin del runtime y elimina el archivo `.wasm` y el registro del plugin. Tenga en cuenta que el almacenamiento clave-valor del plugin (la tabla `plugin_storage`) **no** se borra al eliminarlo: en una reinstalación el plugin verá su configuración anterior.
+
+Después de la instalación, la actualización y la eliminación, la página se recarga para actualizar los frontends de los plugins (`/plugins.js`).
+
+## Activación y desactivación
+
+La versión actual del panel no tiene una acción separada de «activar/desactivar» para un plugin en la interfaz: un plugin funciona desde el momento en que se instala hasta que se elimina.
+
+Si un plugin deja de responder (supera el tiempo de espera de la llamada), el panel lo desactiva temporalmente: deja de recibir eventos y solicitudes HTTP hasta que se reinicie el panel. El estado se muestra como una insignia en la lista de plugins.
+
+Para desactivar todos los plugins por completo, defina la variable de entorno `PLUGINS_DISABLED=true` y reinicie el panel.
+
+## Permisos
+
+Un plugin puede registrar sus propios permisos de servidor de juego con el formato `plugin:{id}:{ability}`, por ejemplo `plugin:ezvdsxmlu6fbk:manage`. Estos permisos aparecen en la lista de permisos del servidor de juego junto con los integrados y se otorgan a los usuarios de la forma habitual (configurando los permisos de un usuario para un servidor). Los administradores reciben todos los permisos de los plugins automáticamente.
+
+Basándose en estos permisos, un plugin oculta o muestra elementos de la interfaz (por ejemplo, una pestaña en la página del servidor de juego), y también puede restringir el acceso a sus rutas HTTP con las opciones `requires_auth` y `admin_only`.
+
+## Variables de entorno del panel
+
+| Variable | Valor por defecto | Descripción |
+|---|---|---|
+| `PLUGINS_DISABLED` | `false` | Desactiva por completo el sistema de plugins: los plugins no se cargan, y los frontends y las rutas de los plugins no se registran |
+| `PLUGINS_AUTOLOAD` | — | Una lista de nombres de archivos `.wasm` separados por comas para registrar y cargar automáticamente al iniciar el panel |
+| `PLUGIN_STORE_URL` | `https://plugins.gameap.dev/api` | Dirección de la API del catálogo de plugins |
+| `PLUGIN_STORE_LICENSE_KEY` | — | Clave de licencia para descargar plugins de pago del catálogo |
+| `PLUGIN_HTTP_ALLOWED_SCHEMES` | `https` | Esquemas permitidos para las solicitudes HTTP salientes de los plugins |
+| `PLUGIN_HTTP_BLOCK_PRIVATE_IPS` | `true` | Bloquea las solicitudes de los plugins a IPs privadas, de loopback y de servicio (protección contra SSRF) |
+| `PLUGIN_HTTP_ALLOWED_HOSTS` | — | Una lista de hosts de excepción que pueden solicitarse incluso con IPs privadas |
+| `PLUGIN_HTTP_MAX_REDIRECTS` | `5` | Número máximo de redirecciones en las solicitudes salientes de los plugins |
+| `PLUGIN_HTTP_MAX_TIMEOUT_SECONDS` | `30` | Tiempo de espera máximo de una solicitud HTTP saliente de un plugin, en segundos |
+
+## Instalación manual a través del sistema de archivos
+
+Un plugin puede instalarse sin la interfaz: copie el archivo `.wasm` en el directorio `plugins/` del panel, añada el nombre del archivo a la variable de entorno `PLUGINS_AUTOLOAD` (nombres separados por comas) y reinicie el panel: el plugin se registrará y cargará en el inicio.
diff --git a/es/plugins/publishing.md b/es/plugins/publishing.md
new file mode 100644
index 0000000..e51eae7
--- /dev/null
+++ b/es/plugins/publishing.md
@@ -0,0 +1,81 @@
+---
+title: Publicación en el catálogo
+layout: default
+lang: es
+category: Plugins
+order: 344
+---
+
+## El catálogo de plugins
+
+El catálogo de plugins de GameAP está disponible en [plugins.gameap.dev](https://plugins.gameap.dev/) (versión en inglés) y [plugins.gameap.ru](https://plugins.gameap.ru/) (versión en ruso). La parte pública del catálogo incluye:
+
+* la lista de plugins con ordenación (populares, mejor valorados, nuevos, actualizados recientemente);
+* filtrado por categorías y etiquetas;
+* la página del plugin: descripción general, reseñas, registro de cambios por versión, capturas de pantalla, información de licencia y un enlace al repositorio.
+
+No hay botón «Install» en el sitio: los plugins se instalan desde la interfaz del panel (consulte [Instalación y gestión](/es/plugins/management.html)).
+
+El panel del desarrollador está en [plugins.gameap.dev/dashboard](https://plugins.gameap.dev/dashboard).
+
+## Registro
+
+El registro se realiza por correo electrónico, con un nombre de usuario y una contraseña (de al menos 8 caracteres) y confirmación por correo electrónico, o mediante OAuth (GitHub, Google).
+
+## Creación de un plugin en el panel del desarrollador
+
+En el panel del desarrollador, cree un plugin y complete los campos:
+
+* nombre;
+* descripción corta (no más de 500 caracteres);
+* descripción;
+* categoría y etiquetas;
+* licencia;
+* enlace al repositorio;
+* versiones mínimas de GameAP y de la Plugin API.
+
+El icono del plugin se sube después de la creación, en la página de edición.
+
+## Publicación de una versión
+
+En la página del plugin, añada una nueva versión:
+
+* versión — en formato de versionado semántico (por ejemplo, `1.0.0`);
+* el archivo del plugin — solo `.wasm`;
+* una firma GPG del archivo (`.asc`, opcional);
+* un registro de cambios («qué hay de nuevo en esta versión»);
+* el indicador «stable release» — marca la versión como recomendada para su uso;
+* capturas de pantalla de la versión.
+
+La descripción, el registro de cambios y las capturas de pantalla se pueden traducir a otros idiomas.
+
+## Moderación
+
+Un plugin nuevo pasa por moderación: «draft» → «under review» → «published» o «rejected». Cuando un plugin o una versión es rechazado, el moderador indica el motivo y el desarrollador recibe una notificación.
+
+## Publicación desde CI/CD
+
+Para publicar versiones automáticamente se utilizan deploy tokens: se crean en la página del plugin en el panel del desarrollador y se muestran solo una vez. La publicación se realiza con una petición al endpoint `https://plugins.gameap.dev/api/ci/plugins/{PLUGIN_ID}/versions`:
+
+```http
+POST /api/ci/plugins/{PLUGIN_ID}/versions
+Authorization: Bearer
+```
+
+Campos multipart: `version`, `file` (el archivo `.wasm`), `signature` (el archivo `.asc`, opcional), `changelog`, `is_stable`. Una respuesta exitosa es HTTP 201.
+
+La firma GPG del archivo se crea con:
+
+```bash
+gpg --detach-sign --armor -o my-plugin.wasm.asc my-plugin.wasm
+```
+
+Para un ejemplo listo de un flujo de trabajo de publicación por etiquetas de release (compilación, firma, subida al catálogo), consulte el archivo `release.yml` en los repositorios [plugin-minecraft-modrinth](https://github.com/gameap/plugin-minecraft-modrinth) y [plugin-hex-editor](https://github.com/gameap/plugin-hex-editor).
+
+## Requisitos del plugin
+
+* Un único archivo `.wasm` (target `wasm32-wasip1`); el frontend está integrado en el mismo archivo.
+* Metadatos `PluginInfo` válidos: `api_version` con valor `"1"` y un `id` estable son obligatorios (consulte los requisitos del id en [Desarrollo de plugins](/es/plugins/development.html)).
+* Versiones en formato de versionado semántico.
+
+Cuando un plugin se instala desde el catálogo, el panel verifica el hash SHA-256 del archivo descargado. La firma GPG no es verificada por el panel — está ahí para que los usuarios puedan comprobar la autenticidad del archivo por sí mismos.
diff --git a/es/requirements.md b/es/requirements.md
new file mode 100644
index 0000000..afeca7c
--- /dev/null
+++ b/es/requirements.md
@@ -0,0 +1,117 @@
+---
+title: Requisitos
+layout: default
+lang: es
+category: General
+order: 10
+---
+
+GameAP 4 es un único ejecutable con una interfaz web integrada. No se necesita ningún servidor
+web, PHP, Composer ni Node.js para ejecutar el panel.
+
+## Requisitos del sistema
+
+### Panel
+
+* RAM: 512 MB o más
+* Disco: 200 MB o más
+* El panel no es exigente con la CPU, un núcleo es suficiente
+
+### GameAP Daemon
+
+Los valores siguientes corresponden al propio daemon, **sin contar los servidores de juego**.
+Reserve recursos para los servidores de juego por separado: algunos juegos tienen requisitos
+elevados.
+
+* RAM: 128 MB
+* Disco: 1 GB o más, más espacio para los archivos de los servidores de juego
+* El daemon no es exigente con la CPU, un núcleo es suficiente
+
+El panel y el daemon se pueden instalar en el mismo servidor.
+
+## Sistemas operativos
+
+Las compilaciones se publican para:
+
+| Sistema | Arquitecturas |
+|---------|--------------------------|
+| Linux | `amd64`, `arm64`, `386` |
+| Windows | `amd64`, `arm64`, `386` |
+| macOS | `amd64`, `arm64` |
+
+La instalación mediante `gameapctl` se describe en las páginas
+[Instalación en Linux](/es/install/install_on_linux.html) e
+[Instalación en Windows](/es/install/install_on_windows.html).
+
+Las versiones de Windows compatibles se indican en la página de instalación.
+
+## Red
+
+| Puerto | Quién lo necesita | Obligatorio |
+|---------|------------------------------------------------------------|----------|
+| `8025` | Interfaz web y API. Lo necesita el navegador del administrador | sí |
+| `31718` | gRPC. Los daemons se conectan al panel a través de él | sí, si hay servidores dedicados remotos |
+| `443` | HTTPS, cuando el propio panel lo sirve | no |
+| `80` | Desafío `http-01` de Let's Encrypt | no |
+
+Los puertos 8025 y 443 se cambian con las variables `HTTP_PORT` y `HTTPS_PORT`, y el puerto gRPC
+con `GRPC_PORT`. Consulte la [referencia de config.env](/es/config.html).
+
+El daemon necesita acceso saliente al panel por el puerto 31718, y ese es el único puerto del
+panel que requiere. El panel no se conecta al daemon; no es necesario abrir ningún puerto de
+entrada en el servidor dedicado.
+
+## Base de datos
+
+Se necesita una de las siguientes:
+
+| DBMS | Nota |
+|------------------|-------------------------------------------------------------------|
+| PostgreSQL | Recomendado para instalaciones con varias instancias del panel |
+| MySQL / MariaDB | |
+| SQLite | No se necesita un servidor de base de datos separado, el archivo se crea automáticamente |
+
+Para una instalación pequeña, SQLite es suficiente: no requiere ni un servicio separado ni
+ninguna configuración.
+
+La cadena de conexión se define con las variables `DATABASE_DRIVER` y `DATABASE_URL`; los
+formatos se indican en la [referencia de config.env](/es/config.html).
+
+Al actualizar desde GameAP 3, el panel se conecta a la base de datos MySQL existente y la
+migra directamente; consulte [Actualización de v3 a v4](/es/upgrade_from_v3_to_v4.html).
+
+## Componentes opcionales
+
+Necesarios solo en escenarios concretos, no en una instalación habitual.
+
+| Componente | Cuándo se necesita |
+|------------------|-------------------------------------------------------------------------------|
+| Redis | Caché compartida e intercambio de eventos entre varias instancias del panel |
+| Almacenamiento S3 | Almacenamiento compartido para archivos y certificados ACME con varias instancias del panel |
+| Proxy inverso | Cuando HTTPS lo sirve nginx o Traefik en lugar del propio panel |
+
+## Requisitos del servidor dedicado
+
+La instalación automática del daemon necesita:
+
+* privilegios de root (Linux) o de administrador (Windows);
+* las utilidades `curl`, `tar` e `install`, para el script de instalación en Linux;
+* acceso saliente a `github.com` y `api.github.com`: desde ahí se descargan gameapctl y el
+ daemon;
+* acceso saliente al panel por el puerto 31718.
+
+Los paquetes que necesitan el daemon y los servidores de juego —incluidos SteamCMD y un gestor
+de procesos— los instala el propio `gameapctl`. Consulte
+[Servidores dedicados](/es/gameap_configure/dedicated_servers.html) para más detalles.
+
+### Curl
+
+Si `curl` no está presente en el sistema, instálelo:
+
+```shell
+# Debian, Ubuntu
+apt install curl
+
+# CentOS, RHEL, Fedora
+yum install curl
+```
diff --git a/es/robots.txt b/es/robots.txt
new file mode 100644
index 0000000..143e8e0
--- /dev/null
+++ b/es/robots.txt
@@ -0,0 +1,6 @@
+---
+---
+User-agent: *
+Allow: /
+
+Sitemap: {{ site.domains.es }}/sitemap.xml
diff --git a/es/security.md b/es/security.md
new file mode 100644
index 0000000..1764bf7
--- /dev/null
+++ b/es/security.md
@@ -0,0 +1,530 @@
+---
+title: Seguridad
+layout: default
+lang: es
+category: Administración
+order: 331
+---
+
+La configuración de seguridad se define mediante variables de entorno en el archivo de configuración
+del panel: `/etc/gameap/config.env` en Linux, `C:\gameap\web\config.env` en Windows. Después de
+modificar el archivo, hay que reiniciar el panel: `gameapctl panel restart`.
+
+## Autenticación de dos factores
+
+### 2FA obligatorio para administradores
+
+**Activado por defecto.** Un administrador sin autenticación de dos factores primero ve un
+recordatorio y, después de 30 días, una exigencia de activarla, sin la cual no se puede usar el
+panel.
+
+| Variable | Valor por defecto | Propósito |
+|---------------------------------|-------------------|------------------------------------------------------------------------|
+| `AUTH_REQUIRE_MFA_FOR_ADMINS` | `true` | Exigir 2FA a los administradores. `false` desactiva el mecanismo por completo |
+| `AUTH_MFA_HARD_FAIL_DAYS` | `30` | Cuántos días se conceden para activarla. `0` — solo recordatorio, sin bloqueo |
+| `AUTH_MFA_ENROLLMENT_TOKEN_TTL` | `15m` | Duración de la sesión restringida que se emite tras el plazo |
+
+Un administrador es un usuario que tiene el permiso **`admin roles & permissions`** concedido de
+forma global, ya sea a través de un rol o directamente. En el panel no existe un "rol de
+administrador" separado, y el usuario con `id = 1` no recibe privilegios por defecto.
+
+### Cómo se cuentan los 30 días
+
+La cuenta atrás comienza **en el primer inicio de sesión exitoso del administrador sin 2FA**, no en
+la instalación del panel, ni en la fecha de actualización, ni cuando se activó la opción.
+
+La fecha del primer recordatorio se guarda en el campo `metadata` de la tabla `users` bajo la clave
+`mfa_first_shown_at`. Si un administrador nunca ha iniciado sesión en el panel, su cuenta atrás aún
+no ha comenzado.
+
+De esto se deduce que, tras actualizar una instalación existente, cada administrador dispone de los
+30 días completos a partir de su próximo inicio de sesión.
+
+El botón "Recordarme más tarde" pospone el diálogo 24 horas, pero **no mueve la fecha límite**.
+Cerrar el diálogo con la cruz o con la tecla Esc funciona de la misma manera.
+
+### Qué ocurre después del plazo
+
+El inicio de sesión sigue funcionando, pero en lugar de una sesión normal se emite un token
+restringido válido durante 15 minutos. Da acceso a solo cinco rutas, las necesarias para activar
+2FA:
+
+* `GET /api/config/public`
+* `POST /api/auth/logout`
+* `GET /api/profile`
+* `POST /api/profile/2fa/setup`
+* `POST /api/profile/2fa/confirm`
+
+Todas las demás peticiones devuelven `403` con el mensaje `session is restricted to two-factor
+enrollment`. La interfaz muestra un diálogo modal sin botón de cierre.
+
+> Esto no es un bloqueo de cuenta. Active 2FA, inicie sesión de nuevo y el trabajo continúa.
+> Un token restringido no puede "ascenderse" a uno completo, por lo que una vez activado 2FA el
+> panel le cierra la sesión por sí mismo y le pide que inicie sesión de nuevo.
+
+Dos excepciones que conviene conocer de antemano:
+
+* **Una sesión completa emitida antes del plazo sigue funcionando** hasta que caduca. Un token de
+ "recordarme" obtenido el día 29 seguirá siendo válido durante otros 7 días.
+* **Los tokens de acceso personal (PAT) no se restringen en absoluto.** Un token emitido con
+ antelación seguirá funcionando con la API después del plazo: un recurso razonable para la
+ automatización.
+
+### Activación de 2FA
+
+Perfil → autenticación de dos factores. El panel muestra un código QR y un secreto para introducirlo
+manualmente, después de lo cual se introduce el código de la aplicación de autenticación.
+
+Los parámetros son fijos y no configurables: **TOTP según RFC 6238, HMAC-SHA1, 6 dígitos, período de
+30 segundos**, tolerancia ±1 paso (es decir, aproximadamente ±30 segundos de desviación de reloj).
+Esta combinación es compatible con todas las aplicaciones habituales: Google Authenticator, Authy,
+1Password y otras. En la aplicación la entrada se llamará `GameAP` y el nombre de la cuenta será su
+nombre de usuario.
+
+Un código ya utilizado no puede volver a usarse, ni siquiera dentro de su ventana de 30 segundos.
+
+Si el reloj del servidor o del teléfono se desvía más de medio minuto, los códigos dejan de
+coincidir: compruebe la sincronización horaria en ambos lados.
+
+### Códigos de recuperación
+
+Al activar 2FA se emiten **10 códigos de recuperación** de la forma `abcde-fghjk`. El alfabeto no
+contiene vocales ni caracteres fáciles de confundir (`0`, `o`, `1`, `l`, `i`).
+
+* Cada código es de un solo uso.
+* Los códigos se muestran **exactamente una vez**: al activar 2FA. En la base de datos solo se
+ guardan sus hashes, por lo que no hay forma de volver a verlos.
+* Un código de recuperación puede usarse tanto para iniciar sesión como para desactivar 2FA.
+* Regeneración: perfil → regenerar códigos de recuperación; se le pedirá su contraseña. Todos los
+ códigos anteriores quedan invalidados.
+
+Guarde los códigos de inmediato, y no en el mismo gestor de contraseñas que guarda la contraseña del
+panel.
+
+### Pérdida de acceso
+
+**No** existe un comando CLI integrado para restablecer 2FA: ni `gameapctl` ni el propio panel pueden
+desactivar la autenticación de dos factores de otro usuario. Las opciones, en orden:
+
+**1. Un código de recuperación.** Introdúzcalo en lugar del código de la aplicación.
+
+**2. El plazo ha pasado, pero 2FA aún no está activado.** Esto no es una pérdida de acceso: inicie
+sesión como de costumbre y termine de activarla; las páginas necesarias están disponibles.
+
+**3. Eliminar la exigencia por completo.** En `config.env`:
+
+```dotenv
+AUTH_REQUIRE_MFA_FOR_ADMINS=false
+```
+
+y `gameapctl panel restart`. La restricción se levanta y los tokens restringidos ya emitidos se
+convierten en completos.
+
+> Esta opción **no ayudará a quien ya activó TOTP y perdió su dispositivo**: el segundo factor se
+> verifica antes de comprobar la exigencia, por lo que el panel seguirá pidiendo un código.
+
+**4. Mantener el recordatorio, pero eliminar el bloqueo.**
+
+```dotenv
+AUTH_MFA_HARD_FAIL_DAYS=0
+```
+
+**5. Edición de la base de datos.** La única opción que queda cuando se han perdido tanto el
+dispositivo como los códigos de recuperación. Detenga el panel, haga una copia de seguridad de la
+base de datos y ejecute la consulta.
+
+PostgreSQL:
+
+```sql
+UPDATE users
+ SET two_factor_enabled = false,
+ two_factor_secret = NULL,
+ two_factor_recovery_codes = NULL,
+ two_factor_last_used_step = NULL
+ WHERE login = 'admin';
+```
+
+MySQL y SQLite: lo mismo, pero con `two_factor_enabled = 0`.
+
+Para restablecer también la cuenta atrás de 30 días, elimine la clave `mfa_first_shown_at` del campo
+`metadata`. En PostgreSQL, donde este campo es de tipo `JSONB`:
+
+```sql
+UPDATE users SET metadata = metadata - 'mfa_first_shown_at' WHERE login = 'admin';
+```
+
+En MySQL el campo se almacena como texto JSON, y la clave se elimina así:
+
+```sql
+UPDATE users SET metadata = JSON_REMOVE(metadata, '$.mfa_first_shown_at') WHERE login = 'admin';
+```
+
+En SQLite, a partir de la versión 3.38:
+
+```sql
+UPDATE users SET metadata = json_remove(metadata, '$.mfa_first_shown_at') WHERE login = 'admin';
+```
+
+> No limpie el campo `metadata` por completo (`SET metadata = NULL`): además de la cuenta atrás de
+> 2FA puede contener otros datos del usuario, y se perderían.
+
+Después, inicie el panel y active 2FA de nuevo.
+
+## Contraseñas
+
+Requisitos de contraseña: **no menos de 12 ni más de 128 bytes**. Deliberadamente no hay requisitos
+de composición (letras mayúsculas, dígitos, caracteres especiales); en su lugar se utiliza una
+comprobación contra una lista de contraseñas comprometidas.
+
+> El límite se cuenta en bytes, no en caracteres. Una contraseña de 12 letras cirílicas ocupa 24
+> bytes y supera la comprobación con margen de sobra.
+
+La lista de contraseñas comunes se toma de [SecLists](https://github.com/danielmiessler/SecLists)
+(`xato-net-10-million-passwords`), filtrada por longitud, y contiene unas 46 000 entradas. Está
+**integrada en el binario**: el panel no contacta con nada al comprobar una contraseña ni transmite
+nada sobre ella.
+
+| Variable | Valor por defecto | Propósito |
+|-----------------------------|-------------------|------------------------------------------------------------------------|
+| `AUTH_ALLOW_WEAK_PASSWORDS` | `false` | Desactiva solo la comprobación por lista. Los límites de longitud se mantienen |
+| `AUTH_BCRYPT_COST` | `13` | Coste de bcrypt. Rango permitido de 10 a 14; en caso contrario el panel no arrancará |
+
+Las contraseñas se hashean con bcrypt sobre un SHA-256 preliminar, de modo que el límite de 72 bytes
+de bcrypt no trunca las contraseñas largas. Al iniciar sesión, un hash con un coste inferior al
+actual se vuelve a hashear automáticamente; el coste de los hashes ya almacenados no puede
+reducirse, aunque disminuya el valor de la variable.
+
+La comprobación se aplica cuando un administrador crea un usuario, cuando se modifica un usuario y
+cuando se cambia la propia contraseña. No se aplica al iniciar sesión; de lo contrario, los usuarios
+con contraseñas débiles antiguas perderían el acceso.
+
+> La contraseña del primer administrador, establecida con la variable `ADMIN_PASSWORD` durante el
+> llenado inicial de la base de datos, **no** se comprueba. Elija de forma consciente.
+
+## CAPTCHA
+
+Desactivado por defecto. Protege **únicamente** el formulario de inicio de sesión
+(`POST /api/auth/login`); la verificación del segundo factor no está cubierta por el CAPTCHA.
+
+| Variable | Valor por defecto | Propósito |
+|-----------------------|-------------------|----------------------------------------------------------------------|
+| `CAPTCHA_PROVIDER` | `""` | `recaptcha_v2`, `recaptcha_v3` o `turnstile`. Vacío — desactivado |
+| `CAPTCHA_SITE_KEY` | `""` | Clave pública, se envía al navegador |
+| `CAPTCHA_SECRET_KEY` | `""` | Clave secreta, nunca se envía al exterior |
+| `CAPTCHA_MIN_SCORE` | `0.5` | Umbral solo para reCAPTCHA v3, ignorado por los demás proveedores |
+| `CAPTCHA_FAIL_OPEN` | `false` | Si se permite el inicio de sesión cuando el servicio de verificación no está disponible |
+| `CAPTCHA_VERIFY_URL` | `""` | Dirección de verificación personalizada, para proxyar el tráfico saliente |
+
+Con `CAPTCHA_FAIL_OPEN=false`, un servicio de verificación no disponible significa un `503` al
+iniciar sesión en el panel.
+
+> Si define `CAPTCHA_PROVIDER` pero no `CAPTCHA_SECRET_KEY`, el widget aparecerá en el formulario de
+> inicio de sesión, pero la verificación del lado del servidor permanecerá desactivada
+> silenciosamente. Defina ambas variables juntas.
+
+El panel añade por sí mismo los dominios del proveedor seleccionado a la política CSP; no hace falta
+configurar `SECURITY_CSP_EXTRA_SCRIPT_SRC` adicionalmente.
+
+### reCAPTCHA v3
+
+Las claves se emiten en la [consola de reCAPTCHA](https://www.google.com/recaptcha/admin): registre
+el sitio, elija el tipo **reCAPTCHA v3** y especifique el dominio del panel.
+
+```dotenv
+CAPTCHA_PROVIDER=recaptcha_v3
+CAPTCHA_SITE_KEY=6LcExampleSiteKeyExampleSiteKeyExam
+CAPTCHA_SECRET_KEY=6LcExampleSecretKeyExampleSecretKeyEx
+CAPTCHA_MIN_SCORE=0.5
+```
+
+reCAPTCHA v3 no pregunta nada al usuario: devuelve una puntuación de `0.0` a `1.0`, donde uno
+significa casi con seguridad un humano. El inicio de sesión se rechaza si la puntuación es inferior
+a `CAPTCHA_MIN_SCORE`.
+
+Comience con el valor por defecto de `0.5` y ajústelo según lo requieran las circunstancias: si la
+gente se queja de que no puede iniciar sesión, bájelo; si los intentos de adivinar contraseñas
+continúan, súbalo. Un valor superior a `0.7` estorba notablemente a los usuarios con bloqueadores en
+el navegador y en modo incógnito.
+
+El panel debe abrirse en el dominio especificado en la configuración de la clave; de lo contrario la
+verificación fallará. Para varios dominios, enumérelos todos en la consola de reCAPTCHA.
+
+La verificación se realiza con una petición a `https://www.google.com/recaptcha/api/siteverify`;
+esta dirección debe ser accesible desde el servidor del panel.
+
+### Turnstile
+
+Las claves se emiten en el panel de [Cloudflare](https://dash.cloudflare.com/), en la sección
+**Turnstile**. Basta una cuenta gratuita y no hace falta delegar el dominio a Cloudflare.
+
+```dotenv
+CAPTCHA_PROVIDER=turnstile
+CAPTCHA_SITE_KEY=0x4AAAAAAAExampleSiteKey
+CAPTCHA_SECRET_KEY=0x4AAAAAAAExampleSecretKey
+```
+
+`CAPTCHA_MIN_SCORE` no se aplica a Turnstile: el proveedor devuelve solo "aprobado" o "rechazado",
+por lo que no hace falta definir esta variable.
+
+En la mayoría de los casos Turnstile se supera sin que el usuario lo note, y muestra un reto breve
+cuando algo parece sospechoso. El modo del widget (**Managed**, **Non-interactive** o **Invisible**)
+se elige del lado de Cloudflare al crear la clave; no es configurable desde el panel.
+
+La verificación se realiza con una petición a
+`https://challenges.cloudflare.com/turnstile/v0/siteverify`.
+
+reCAPTCHA v2 se configura igual que v3, pero sin `CAPTCHA_MIN_SCORE`.
+
+## Limitación de intentos de inicio de sesión
+
+Los límites están **fijados en el código**; no hay variables de entorno para ellos:
+
+* ventana: 15 minutos;
+* no más de 20 intentos fallidos desde una misma dirección IP;
+* no más de 5 intentos fallidos por nombre de usuario.
+
+El límite se aplica en dos rutas: `POST /api/auth/login` y `POST /api/auth/2fa/verify`. El cliente
+recibe un `429` y una cabecera `Retry-After: 900`. Solo las respuestas `401` incrementan el
+contador; un inicio de sesión exitoso restablece el contador del nombre de usuario, pero no el de la
+dirección IP.
+
+Los contadores se almacenan en la caché del panel. Con `CACHE_DRIVER=memory` (el valor por defecto)
+se restablecen al reiniciar y no se comparten entre varias instancias del panel; para una
+instalación tolerante a fallos utilice `CACHE_DRIVER=redis`.
+
+Aparte de esto, la verificación del segundo factor permite no más de 5 intentos de introducir el
+código por intento de inicio de sesión, tras lo cual hay que introducir la contraseña de nuevo.
+
+El panel no tiene bloqueo de cuentas: la fuerza bruta está limitada únicamente por lo descrito
+arriba.
+
+## Cabeceras HTTP y Content Security Policy
+
+Las cabeceras de seguridad están activadas por defecto.
+
+| Variable | Valor por defecto | Cabecera |
+|-------------------------------------|-----------------------------------|--------------------------------------------------|
+| `SECURITY_HEADERS_ENABLED` | `true` | Interruptor general |
+| `SECURITY_CONTENT_TYPE_OPTIONS` | `true` | `X-Content-Type-Options: nosniff` |
+| `SECURITY_FRAME_OPTIONS` | `SAMEORIGIN` | `X-Frame-Options`; un valor vacío elimina la cabecera |
+| `SECURITY_REFERRER_POLICY` | `strict-origin-when-cross-origin` | `Referrer-Policy` |
+| `SECURITY_HSTS_ENABLED` | `true` | `Strict-Transport-Security` |
+| `SECURITY_HSTS_MAX_AGE` | `31536000` | Duración en segundos, un año por defecto |
+| `SECURITY_HSTS_INCLUDE_SUBDOMAINS` | `false` | Añade `includeSubDomains` |
+| `SECURITY_HSTS_PRELOAD` | `false` | Añade `preload` |
+
+HSTS solo se envía cuando se accede al panel por HTTPS: por una conexión TLS real, por la cabecera
+`X-Forwarded-Proto: https` o con `TLS_FORCE_HTTPS=true`. Trabajar por HTTP simple durante el
+desarrollo no dejará el navegador "atascado".
+
+### Política CSP
+
+| Variable | Valor por defecto | Propósito |
+|-------------------------------------|-------------------|-----------------------------------------------------------------------|
+| `SECURITY_CSP_ENABLED` | `true` | Activa la política |
+| `SECURITY_CSP_REPORT_ONLY` | `false` | Envía `Content-Security-Policy-Report-Only` en lugar de la política de bloqueo |
+| `SECURITY_CSP_POLICY` | `""` | Sustituye por completo la política generada |
+| `SECURITY_CSP_REPORT_URI` | `""` | Añade `report-uri` |
+| `SECURITY_CSP_EXTRA_SCRIPT_SRC` | `""` | Amplía `script-src`, valores separados por comas |
+| `SECURITY_CSP_EXTRA_STYLE_SRC` | `""` | Amplía `style-src` |
+| `SECURITY_CSP_EXTRA_CONNECT_SRC` | `""` | Amplía `connect-src` |
+| `SECURITY_CSP_EXTRA_IMG_SRC` | `""` | Amplía `img-src` |
+| `SECURITY_CSP_EXTRA_FRAME_SRC` | `""` | Amplía `frame-src` |
+| `SECURITY_CSP_EXTRA_FONT_SRC` | `""` | Amplía `font-src` |
+
+La política generada:
+
+```text
+default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self';
+script-src 'self' blob: 'wasm-unsafe-eval' ;
+style-src 'self' 'unsafe-inline';
+img-src 'self' data: blob:;
+font-src 'self';
+connect-src 'self';
+frame-src 'self';
+worker-src 'self' blob:
+```
+
+Por qué contiene relajaciones:
+
+* `'wasm-unsafe-eval'`: al subir archivos, la suma de comprobación SHA-256 se calcula en el
+ navegador mediante WebAssembly. Sin este permiso la subida de archivos dejará de funcionar.
+* `blob:` en `script-src`: así se cargan las partes frontend de los plugins.
+* `'unsafe-inline'` en `style-src`: los estilos de los plugins y de Vue se añaden a la página en
+ línea.
+
+Las direcciones del proveedor de CAPTCHA seleccionado se añaden a `script-src` y `frame-src`
+automáticamente.
+
+> **Un plugin que cargue scripts desde un CDN de terceros será bloqueado por la política.** Añada el
+> dominio necesario a `SECURITY_CSP_EXTRA_SCRIPT_SRC`. Es mejor no usar `SECURITY_CSP_POLICY` para
+> esto: sustituye toda la política, junto con la autorización automática de los dominios de CAPTCHA
+> y los hashes de los scripts en línea del panel.
+
+## Cifrado de secretos
+
+| Variable | Obligatoria | Propósito |
+|------------------|-------------|---------------------------------------------------------------------|
+| `AUTH_SECRET` | sí | Clave de firma de los tokens de sesión. Sin ella el panel no arrancará |
+| `ENCRYPTION_KEY` | no | Clave de cifrado de los secretos en la base de datos |
+
+Ambos valores deben ser aleatorios, no una frase de contraseña. Pero sus requisitos de longitud
+son **distintos**: el panel los trata de forma diferente.
+
+**`AUTH_SECRET` se usa tal cual y se ajusta exactamente a 32 bytes:** un valor más corto se rellena,
+uno más largo se **trunca**, y al registro solo llega una advertencia. Por eso indique exactamente
+32 caracteres:
+
+```bash
+openssl rand -base64 24
+```
+
+No use aquí `openssl rand -hex 32`: obtendrá 64 caracteres, el panel descartará la mitad y la
+robustez seguirá siendo la misma.
+
+**`ENCRYPTION_KEY` se hashea por completo con SHA-256**, su longitud no está limitada y no se pierde
+nada. Aquí puede usar un valor más largo:
+
+```bash
+openssl rand -hex 32
+```
+
+El hasheo conserva la entropía del valor original, pero no la aumenta, así que la clave debe ser
+aleatoria de todos modos. Una frase de contraseña no es segura aquí: puede obtenerse por fuerza
+bruta si el valor cifrado se filtra.
+
+> `AUTH_SECRET` se ajusta silenciosamente a 32 bytes: un valor más corto se rellena, uno más largo
+> se trunca, y solo una advertencia llega al registro. Un `AUTH_SECRET` corto o predecible significa
+> que los tokens de sesión pueden falsificarse.
+
+`ENCRYPTION_KEY` se utiliza para cifrar los secretos TOTP y la contraseña de conexión del daemon en
+la base de datos. Si no está definida, los secretos TOTP se cifran con una clave derivada de
+`AUTH_SECRET`, y la contraseña del daemon se almacena en texto plano; el panel advierte de ello al
+arrancar.
+
+> **No defina `ENCRYPTION_KEY` por primera vez en una instalación en funcionamiento que ya tenga 2FA
+> activado.** La clave de cifrado de los secretos TOTP cambiará de `AUTH_SECRET` a `ENCRYPTION_KEY`,
+> los secretos previamente almacenados quedarán ilegibles y **todos los usuarios tendrán que activar
+> 2FA de nuevo**. Los códigos de recuperación seguirán funcionando: se almacenan por separado.
+>
+> Lo mismo ocurrirá si `ENCRYPTION_KEY` se pierde o se cambia. Guárdela junto con la copia de
+> seguridad de la base de datos: sin ella, parte de los datos de la copia no podrá restaurarse.
+
+Las contraseñas de los usuarios se hashean con bcrypt; los tokens personales y la clave del daemon
+se almacenan como SHA-256: son transformaciones irreversibles y `ENCRYPTION_KEY` no tiene nada que
+ver con ellas.
+
+## Registro de auditoría
+
+| Variable | Valor por defecto | Propósito |
+|--------------------------|-------------------|----------------------------------------------------|
+| `AUDIT_ENABLED` | `true` | Registro de eventos de seguridad |
+| `AUDIT_CLIENT_IP_HEADER` | `""` | Cabecera con la IP real del cliente, por ejemplo `X-Real-IP` |
+
+Qué se registra: inicios de sesión exitosos y fallidos, alcances del límite de intentos, denegaciones
+de acceso, activación y desactivación de 2FA, regeneración de códigos de recuperación, cambios de
+usuarios y asignaciones de roles, creación y revocación de tokens, cambios y eliminación de
+servidores dedicados, operaciones con archivos, instalación y eliminación de plugins.
+
+Cada registro contiene: tipo de evento, categoría, resultado, identificador y nombre del usuario que
+actúa, método de autenticación, dirección IP, User-Agent, método y ruta de la petición,
+identificador de la petición.
+
+> El registro de auditoría es un conjunto de líneas estructuradas del registro de la aplicación con
+> el campo `component=audit`. **No** hay tabla separada en la base de datos, ni archivo separado, ni
+> rotación, ni interfaz de consulta, ni API de lectura. Si los registros necesitan almacenarse y
+> buscarse, configure la recolección del registro del panel con las herramientas estándar de su
+> sistema, por ejemplo, mediante `journald` y un recolector de registros externo.
+>
+> `AUDIT_CLIENT_IP_HEADER` confía en la cabecera especificada procedente de **cualquier** remitente:
+> el panel no tiene lista de proxies de confianza. Active esta variable solo si el proxy inverso
+> sobrescribe garantizadamente la cabecera en las peticiones entrantes. De lo contrario, la
+> dirección IP puede falsearse y, con ella, eludirse el límite de intentos de inicio de sesión por
+> IP.
+
+## Sesiones y tokens
+
+Las sesiones se emiten en formato PASETO v4.local (`AUTH_SERVICE=paseto`, la alternativa es `jwt`).
+Una sesión normal dura 24 horas; con la opción "recordarme", 7 días. Al cerrar sesión, el token
+entra en una lista de revocación que se comprueba en cada petición.
+
+Para los casos en que el token debe pasarse en la dirección de la página —conexiones WebSocket,
+descargas de archivos— se emiten tokens de un solo uso y corta duración con el prefijo `glst_`. Su
+vida útil está limitada a 10 segundos independientemente del valor de
+`AUTH_SHORT_LIVED_TOKEN_TTL`.
+
+El panel no tiene protección CSRF y no la necesita: la autenticación se realiza mediante la cabecera
+`Authorization`, no con cookies.
+
+La lista de orígenes permitidos para acceder a la API desde un navegador se define con la variable
+`HTTP_ALLOWED_ORIGINS` (valores separados por comas). Si está vacía, se permite un único origen
+calculado a partir de `HTTP_HOST`. El carácter `*` no está soportado.
+
+## Subida de archivos
+
+El tipo de un archivo subido se determina por su contenido, no por la extensión ni por la cabecera
+enviada por el cliente. Por defecto se permiten imágenes, archivos de texto, JSON, XML, CSV, YAML y
+PDF. SVG y HTML están prohibidos deliberadamente: pueden contener scripts.
+
+| Variable | Valor por defecto | Propósito |
+|-------------------------------|-------------------|---------------------------------------------------------|
+| `FILES_UPLOAD_ALLOWED_MIMES` | `""` | Amplía la lista de tipos permitidos, no la sustituye |
+| `FILES_UPLOAD_ALLOW_ARCHIVES` | `false` | Permitir archivos comprimidos: zip, tar, gzip, bzip2, 7z, xz |
+| `FILES_UPLOAD_ALLOW_BINARY` | `false` | Permitir archivos binarios arbitrarios |
+
+> La prohibición de archivos comprimidos y binarios es la causa más común de la pregunta "¿por qué
+> no se sube el archivo?". Un archivo comprimido puede contener ejecutables que se desempaquetarán
+> en el servidor dedicado, por eso la subida está prohibida por defecto. Active estas opciones de
+> forma consciente.
+
+Las subidas rechazadas pasan al registro de auditoría con el tipo de archivo detectado y el motivo
+del rechazo. El límite de tamaño por archivo es de 100 MB y no es configurable.
+
+## Plugins
+
+Los plugins se ejecutan en un sandbox de WebAssembly y no tienen acceso directo al sistema. Las
+peticiones de red de los plugins se restringen por separado:
+
+| Variable | Valor por defecto | Propósito |
+|-----------------------------------|-------------------|-----------------------------------------------------------|
+| `PLUGINS_DISABLED` | `false` | Desactivar por completo el mecanismo de plugins |
+| `PLUGIN_HTTP_BLOCK_PRIVATE_IPS` | `true` | Prohibir peticiones a direcciones de redes internas |
+| `PLUGIN_HTTP_ALLOWED_SCHEMES` | `https` | Esquemas permitidos |
+| `PLUGIN_HTTP_ALLOWED_HOSTS` | `""` | Lista de hosts permitidos; vacío — sin restricciones de host |
+| `PLUGIN_HTTP_MAX_TIMEOUT_SECONDS` | `30` | Límite de tiempo de la petición |
+| `PLUGIN_HTTP_MAX_REDIRECTS` | `5` | Límite de redirecciones; cada una se comprueba de nuevo |
+
+Las direcciones de los servicios de metadatos de los proveedores de nube están siempre bloqueadas y
+no pueden desbloquearse. Las cabeceras `Set-Cookie`, `Authorization`, `WWW-Authenticate` y
+`Clear-Site-Data` no se transmiten al plugin.
+
+Consulte los detalles en la página [Plugins](/es/plugins/index.html).
+
+## Qué está fijado en el código
+
+Algunas variables están presentes en la configuración pero no tienen efecto en el comportamiento del
+panel. No confíe en ellas:
+
+* **`AUTH_SESSION_IDLE_TIMEOUT` y `AUTH_SESSION_IDLE_UPDATE_FREQ`**: actualmente no hay cierre de
+ sesión por inactividad. Una sesión dura exactamente su plazo completo: 24 horas o 7 días.
+* **`GRPC_ENABLED`**: no existe tal opción; el servidor gRPC siempre está en funcionamiento. Las
+ versiones antiguas de `gameapctl` añaden esta línea a `config.env`; es inofensiva, pero no hace
+ nada.
+* Los límites de intentos de inicio de sesión están definidos por constantes en el código; no hay
+ variables `RATE_LIMIT*`.
+
+Los porcentajes de cumplimiento de OWASP ASVS en los archivos `docs/security/ASVS.md` y
+`ASVS_L2.md` del repositorio del panel están desactualizados; consulte esta página en su lugar.
+
+## Notificación de vulnerabilidades
+
+Las vulnerabilidades se aceptan a través de un
+[GitHub Security Advisory](https://github.com/gameap/gameap/security/advisories/new) —es el canal
+preferido— o por correo electrónico a `security@gameap.com`.
+
+Plazos: acuse de recibo — 72 horas, evaluación — 14 días, divulgación pública — 90 días.
+Correcciones: críticas — 14 días, altas — 30 días, medias — 60 días, bajas — en la próxima versión.
+
+Una configuración involuntariamente insegura establecida por el propio administrador (por ejemplo,
+`AUTH_ALLOW_WEAK_PASSWORDS=true` o `SECURITY_HEADERS_ENABLED=false`) no se considera una
+vulnerabilidad.
diff --git a/es/sitemap.xml b/es/sitemap.xml
new file mode 100644
index 0000000..1370f6a
--- /dev/null
+++ b/es/sitemap.xml
@@ -0,0 +1,21 @@
+---
+---
+{%- assign prefix = '/es/' -%}
+
+
+{%- assign pages = site.pages | sort: 'url' -%}
+{%- for p in pages -%}
+{%- assign head = p.url | slice: 0, 4 -%}
+{%- if head == prefix and p.layout == 'default' and p.hidden != true -%}
+{%- assign clean_path = p.url | replace: '/en/', '/' | replace: '/ru/', '/' | replace: '/es/', '/' %}
+
+ {{ site.domains.es }}{{ clean_path }}
+
+
+
+
+
+{%- endif -%}
+{%- endfor %}
+
diff --git a/es/troubleshooting.md b/es/troubleshooting.md
new file mode 100644
index 0000000..05792b3
--- /dev/null
+++ b/es/troubleshooting.md
@@ -0,0 +1,291 @@
+---
+title: Solución de problemas
+layout: default
+lang: es
+category: Solución de problemas
+order: 400
+---
+
+Esta página describe algunos posibles errores y cómo solucionarlos.
+
+## Por dónde empezar
+
+Para casi cualquier problema con un servidor de juego, los detalles se pueden encontrar en dos
+lugares.
+
+**Tareas en el panel.** **Administración** → **Tareas de GDaemon**, abra la última tarea: contiene
+el resultado del comando y la salida del error.
+
+**El registro del daemon** en el servidor dedicado:
+
+* Linux — `/var/log/gameap-daemon/output.log`
+* Windows — `C:\gameap\daemon\logs\output.log`
+
+Si el daemon se ejecuta bajo systemd, la misma salida está disponible con:
+
+```shell
+journalctl -u gameap-daemon -n 200 --no-pager
+```
+
+Para más detalles, establezca `log_level: debug` en la configuración del daemon y reinícielo.
+
+## Errores de inicio del servidor
+
+### El estado del servidor se muestra incorrectamente
+
+A veces el servidor se inicia, pero el panel lo muestra como desconectado.
+Consulte [Errores de visualización del estado del servidor](#errores-de-visualización-del-estado-del-servidor).
+
+### Comando de inicio del servidor vacío
+
+Este error ocurre cuando el comando de inicio del servidor de juego está vacío.
+
+Vaya a la página de administración del servidor de juego: **Administración** → **Servidores** →
+seleccione el servidor de juego. O desde la página principal: **Lista de servidores** → seleccione
+el servidor → **Control** → **Administración**.
+
+Busque el campo **Comando de inicio del servidor de juego** e introduzca el comando. Para
+Counter-Strike 1.6 será algo así:
+
+```text
+./hlds_run -game cstrike +ip {ip} +port {port} +map {default_map} +maxplayers {maxplayers} +sys_ticrate {fps}
+```
+
+Consulte la [configuración de servidores de juego](/es/gameap_configure/game_servers.html#comando-de-inicio)
+para más detalles. El comando de inicio predeterminado se puede establecer
+[en la configuración del mod](/es/gameap_configure/games.html#comandos-de-inicio-predeterminados).
+
+### La ventana modal de información no cambia durante mucho tiempo
+
+Un inicio suele tardar menos de 10 segundos. Si la barra de progreso está congelada o el estado no
+cambia durante varios minutos, compruebe si el daemon está en ejecución y conectado al panel:
+
+```shell
+systemctl status gameap-daemon
+```
+
+Si el daemon no está en ejecución:
+
+```shell
+systemctl start gameap-daemon
+```
+
+Si está en ejecución, revise el registro para ver si la conexión con el panel está establecida. Los
+mensajes `gRPC connection failed` y `Reconnecting to panel...` significan que el daemon no pudo
+comunicarse con el panel; consulte
+[El daemon no se conecta al panel](#el-daemon-no-se-conecta-al-panel).
+
+### Server start task is already exists
+
+Este error ocurre cuando ya se ha creado una tarea de inicio y todavía no se ha ejecutado.
+
+Vaya a la página **Tareas de GDaemon**, busque la tarea en estado de espera, ábrala con el botón
+**Ver** y haga clic en **Cancelar**. Después inicie el servidor de nuevo.
+
+Si las tareas se quedan en espera con regularidad, el daemon no las está recogiendo: compruebe que
+está en ejecución y conectado al panel.
+
+## Errores de visualización del estado del servidor
+
+### Desincronización de la hora
+
+El problema se debe a una diferencia de hora entre el servidor del panel y el servidor dedicado.
+Configure la sincronización de la hora en ambos.
+
+Cambiar la zona horaria en Debian y Ubuntu:
+
+```bash
+dpkg-reconfigure tzdata
+```
+
+### El daemon está conectado, pero el estado no se actualiza
+
+El daemon informa del estado de los servidores cada `metrics.collection_interval` (5 segundos por
+defecto) y envía un heartbeat cada 30 segundos. Si los datos permanecen desactualizados durante más
+tiempo, revise el registro del daemon en busca de caídas de la conexión.
+
+## Errores de instalación del servidor
+
+Para encontrar la causa, mire el resultado del comando de instalación: **Administración** →
+**Tareas de GDaemon** → busque la tarea de instalación y ábrala.
+
+Causas comunes:
+
+* [No hay fuente](#no-hay-fuente)
+* [Archivo de instalación formado incorrectamente](#archivo-de-instalación-formado-incorrectamente)
+* [Fuente de instalación incorrecta](#fuente-de-instalación-incorrecta)
+
+### No hay fuente
+
+El panel no sabe desde dónde instalar el servidor de juego. Hay varias formas:
+
+* mediante SteamCMD: el Steam APP ID debe estar configurado en los ajustes del juego;
+* desde un repositorio remoto: debe proporcionarse un enlace a un archivo ZIP o TAR (RAR no está
+ soportado);
+* desde un repositorio local: una ruta a un directorio con archivos o a un archivo ZIP o TAR. Los
+ archivos deben estar ubicados en el servidor dedicado donde se ejecuta el daemon.
+
+La fuente se configura en la página **Administración** → **Juegos** → seleccione el juego →
+**Editar**.
+
+### Archivo de instalación formado incorrectamente
+
+El archivo debe contener los archivos del servidor de juego en su raíz. El error más común es que
+los archivos estén en un directorio anidado.
+
+Un archivo formado incorrectamente para GTA: San Andreas Multiplayer:
+
+
+
+Correcto:
+
+
+
+### Fuente de instalación incorrecta
+
+Para un repositorio local, compruebe que el directorio existe en el servidor dedicado donde se
+ejecuta el daemon.
+
+Para uno remoto, compruebe que el enlace realmente inicia la descarga de un archivo. Los enlaces a
+Yandex Disk, Google Drive y servicios de almacenamiento similares no están soportados: sirven una
+página, no un archivo.
+
+### Failed to install via steamcmd
+
+A veces SteamCMD se interrumpe en mitad de una descarga. El panel puede trabajar con el descargador
+alternativo depot downloader: sustituya el script de SteamCMD:
+
+```shell
+cd /srv/gameap/steamcmd
+mv steamcmd.sh steamcmd.sh.orig
+curl -O https://raw.githubusercontent.com/gameap/steamcmd-depotdownloader/main/steamcmd.sh
+chmod 755 steamcmd.sh && chown gameap:gameap steamcmd.sh
+```
+
+### Se requiere iniciar sesión en una cuenta de Steam
+
+Algunos juegos no se pueden descargar de forma anónima: se necesita una cuenta con una copia
+comprada. Especifíquela en la sección `steam_config` de la configuración del daemon; consulte
+[GameAP Daemon](/es/daemon/daemon.html#cuenta-de-steam).
+
+La autenticación de dos factores debe estar desactivada en esta cuenta; de lo contrario, el daemon
+no podrá iniciar sesión.
+
+## Errores de GameAP Daemon
+
+### El daemon no se conecta al panel
+
+En GameAP 4 la conexión la establece el daemon: se conecta al panel por gRPC en el puerto **31718**.
+El panel no se conecta al daemon, y para ello no se necesitan puertos entrantes en el servidor
+dedicado.
+
+La señal reveladora en el registro del daemon son los mensajes repetidos `gRPC connection failed` y
+`Reconnecting to panel...`.
+
+Qué comprobar:
+
+1. **Accesibilidad del puerto** desde el servidor dedicado:
+
+ ```shell
+ nc -zv panel.example.com 31718
+ ```
+
+ Si la conexión no se puede establecer, el puerto está bloqueado por un firewall o el panel está
+ escuchando en otra dirección.
+
+2. **La dirección del panel** en la configuración del daemon: el parámetro `grpc.address`. Debe
+ apuntar a una dirección accesible desde el servidor dedicado.
+
+3. **Error de verificación del certificado.** Si el registro contiene un mensaje sobre una
+ discrepancia de nombre en el certificado, el daemon se está conectando mediante una dirección que
+ no está en el certificado del panel. Establezca `GRPC_EXTERNAL_HOST` en el panel y reemita el
+ certificado; consulte [GRPC API](/es/daemon/grpc.html).
+
+4. **`registration failed`.** La conexión se establece, pero el panel rechazó el registro: un
+ `ds_id` o `api_key` incorrecto en la configuración del daemon. La solución más sencilla es
+ registrar el daemon de nuevo.
+
+### La consola o el gestor de archivos no funcionan
+
+Si los servidores se inician y se detienen, pero la consola y el gestor de archivos no funcionan,
+el problema es casi siempre la conexión con el panel: los comandos de control pueden haberse
+completado antes de una desconexión, mientras que la consola y los archivos requieren un flujo de
+datos activo.
+
+Compruebe la conexión como se describe en la sección anterior; reinicie el daemon si es necesario:
+
+```shell
+systemctl restart gameap-daemon
+```
+
+### No se puede subir un archivo mediante el gestor de archivos
+
+Por defecto, no se permite subir archivos comprimidos ni archivos binarios arbitrarios: el tipo se
+detecta a partir del contenido del archivo, no de la extensión. Se pueden permitir con las
+variables `FILES_UPLOAD_ALLOW_ARCHIVES` y `FILES_UPLOAD_ALLOW_BINARY`; consulte
+[Seguridad](/es/security.html).
+
+El tamaño máximo de archivo para una subida normal es de 100 MB.
+
+### El daemon no se inicia tras reiniciar el servidor
+
+Active el inicio automático:
+
+```shell
+systemctl enable gameap-daemon
+```
+
+### Task complete with an error
+
+Un error genérico durante la instalación, el inicio, el reinicio o la detención de un servidor de
+juego. Puede haber muchas causas.
+
+Abra **Administración** → **Tareas de GDaemon**, busque la última tarea y mire los detalles.
+Después revise el registro del daemon.
+
+#### No source to install game
+
+No se ha configurado ninguna fuente de instalación para el juego. Consulte
+[No hay fuente](#no-hay-fuente).
+
+## Errores de inicio de sesión en el panel
+
+### Un administrador no puede iniciar sesión después de una actualización
+
+En GameAP 4, la autenticación de dos factores es obligatoria para los administradores: 30 días
+después del primer recordatorio, el inicio de sesión deja de emitir una sesión completa. Esto no es
+un bloqueo de la cuenta: active 2FA e inicie sesión de nuevo.
+
+Los pasos, incluido qué hacer cuando se pierde el dispositivo con los códigos, están en la página
+[Seguridad](/es/security.html).
+
+### Demasiados intentos de inicio de sesión
+
+Después de 20 intentos fallidos desde una dirección o 5 intentos para un mismo usuario, el inicio
+de sesión se bloquea durante 15 minutos con una respuesta `429`. El límite se elimina por sí solo
+cuando pasa el tiempo.
+
+### La contraseña se rechaza al cambiarla
+
+La longitud mínima de la contraseña es de 12 caracteres, y la contraseña se comprueba contra la
+lista de contraseñas comprometidas. Consulte [Seguridad](/es/security.html).
+
+## Búsqueda de la causa
+
+Si su problema no aparece aquí, busque detalles en los registros.
+
+**El registro del daemon**: `/var/log/gameap-daemon/output.log` en Linux,
+`C:\gameap\daemon\logs\output.log` en Windows. Para una salida detallada, establezca
+`log_level: debug` en la configuración del daemon y reinícielo.
+
+**El registro del panel**: la salida del proceso `gameap`. Bajo systemd:
+
+```shell
+journalctl -u gameap -n 200 --no-pager
+```
+
+El nivel de detalle se configura con la variable `LOGGER_LEVEL`; consulte la
+[Referencia de config.env](/es/config.html).
+
+**Tareas**: en el panel, **Administración** → **Tareas de GDaemon**: ahí está el resultado de cada
+comando de inicio, instalación y reinicio.
diff --git a/es/tutorials/additional_games.md b/es/tutorials/additional_games.md
new file mode 100644
index 0000000..ad5523a
--- /dev/null
+++ b/es/tutorials/additional_games.md
@@ -0,0 +1,141 @@
+---
+title: Añadir juegos que faltan
+layout: default
+lang: es
+category: Tutoriales
+order: 299
+---
+
+El panel de control admite el arranque y el control básico de cualquier servidor de juego y aplicación.
+Este manual explica cómo añadir un nuevo juego tomando como ejemplo Sven Co-op. Cada paso incluye explicaciones.
+
+## Añadir un juego
+
+Primero, vaya a la página de añadir juego. Abra el menú **"Administración"** y luego seleccione
+**"Juegos"**.
+
+
+
+A continuación, busque el botón **"Añadir juego"** en la parte superior de la página y haga clic en él.
+
+
+
+Será redirigido a la página para añadir un nuevo juego. Aquí debe especificar algunos datos de su nuevo juego.
+
+
+
+Debe rellenar los siguientes campos:
+* **Código**. Introduzca el nombre abreviado del juego.
+* **Código de inicio**. También puede introducir el nombre abreviado del juego.
+* **Nombre del juego**.
+* **Motor del juego**. Si el juego está escrito en Unity o sin usar un motor, introduzca el nombre abreviado
+del juego.
+* **Versión**. Introduzca el número de versión o un valor semántico, por ejemplo “legacy”, “beta”, etc.
+
+¡Nota! Para habilitar la instalación automática, debe rellenar uno de los siguientes campos:
+* [**Steam APP ID**](/es/gameap_configure/games.html#steam-app-id). Consulte el valor del juego que le interesa en la
+[wiki oficial de Steam](https://developer.valvesoftware.com/wiki/Dedicated_Servers_List) o en la base de datos
+[SteamDB](https://steamdb.info/)
+* [**Repositorio remoto**](/es/gameap_configure/games.html#remote-repository). Un enlace a un archivo por HTTP o FTP.
+* [**Repositorio local**](/es/gameap_configure/games.html#local-repository).
+
+Todos estos campos son opcionales, pero uno de ellos debe tener un valor.
+
+Lea más sobre el significado de los campos en la página [Configuración de juegos, descripción de los campos](/es/gameap_configure/games.html#fields).
+
+## Añadir un mod
+
+Cada juego debe tener al menos un mod.
+
+El mod es una potente herramienta de GameAP: permite habilitar plugins adicionales, configuración o cualquier
+contenido para ampliar las capacidades básicas del servidor. El archivo con los ficheros que especifique para el mod
+se descomprimirá sobre la compilación base del servidor.
+
+Para añadir un nuevo mod a un juego concreto, seleccione el juego en la lista y haga clic en **"Añadir el primer mod"**.
+
+
+
+Si el juego ya tiene al menos un mod, en la parte superior de la página de la lista de juegos seleccione
+**"Añadir mod"**.
+
+En la página para añadir un mod, especifique el nombre del mod según las características del
+modo de juego (GunGame, Jail, etc.) o la disponibilidad de módulos (AMXX, ReAMXX para Counter-Strike,
+IndustrialCraft, BuildCraft para Minecraft, etc.).
+
+
+
+Si dispone de un archivo con plugins adicionales que deben escribirse sobre la compilación base, especifique la ruta
+al mismo en los campos de repositorio local o remoto.
+
+En el campo **repositorio local**, especifique la ruta al archivo o directorio en un servidor dedicado
+que ejecute GameAP Daemon; ejemplo de ruta: `/srv/gameap/repo/svencoop_op4_maps.tar.xz`.
+Consulte los detalles en la página [Configuración de juegos](/es/gameap_configure/games.html#local-repository-1).
+
+En el campo **repositorio remoto**, especifique la URL del archivo HTTP o FTP.
+Ejemplo de ruta: `https://cdn.gameap.com/svencoop/svencoop_op4_maps.tar.xz`.
+Consulte los detalles en la página [Configuración de juegos](/es/gameap_configure/games.html#remote-repository-1).
+Hay archivos listos para usar para muchos juegos en el repositorio de GameAP (`cdn.gameap.com`,
+`cdn.gameap.ru`), pero la lista de ficheros no se puede explorar: el listado de directorios está deshabilitado
+y solo funcionan los enlaces directos. Normalmente es más sencillo tomar la configuración de juegos incluida con el
+botón **Actualizar juegos**: las URL ya están definidas ahí.
+
+## Configuración del mod
+
+Después de crear un mod, puede configurarlo especificando parámetros adicionales como
+"Comandos de inicio predeterminados", variables de inicio y diversos comandos RCON.
+
+Los comandos de inicio predeterminados deben especificarse. Si no los especifica, el comando de inicio estará vacío
+al crear un nuevo servidor de juego, pero es obligatorio; de lo contrario, el servidor no arrancará.
+
+
+
+Ejemplos de comandos de inicio predeterminados para algunos juegos en Linux:
+* Sven Co-op:
+```shell
+./svends_run +ip {ip} +port {port} +maxplayers {maxplayers} +log on +map {default_map}
+```
+* Half-Life:
+```shell
+./hlds_run -game valve +ip {ip} +port {port} +map {default_map} +maxplayers {maxplayers} +sys_ticrate {fps}
+```
+
+* GTA: Multi Theft Auto
+```shell
+./mta-server64 -t -n --ip {ip} --port {port} --maxplayers {maxplayers}
+```
+
+Ejemplos de comandos de inicio predeterminados para algunos juegos en Windows:
+
+* Sven Co-op:
+```shell
+SvenDS +ip {ip} +port {port} +maxplayers {maxplayers} +log on +map {default_map}
+```
+
+* 7 Day To Die
+```shell
+startdedicated.bat
+```
+
+Preste atención a los valores entre llaves `{` y `}`, como `{ip}`, `{port}`, `{maxplayers}`, `{default_map}`,
+`{fps}` y otros. En GameAP se llaman shortcodes; se sustituyen por los valores de las variables del servidor.
+Todos los servidores de juego tienen las llamadas variables básicas, como IP, puertos, ID y UUID. También tienen
+variables adicionales que se especifican en la configuración del mod, entre ellas el número máximo de jugadores,
+el mapa predeterminado, los FPS y otras.
+
+Algunos parámetros solo pueden modificarlos los administradores, y otros están disponibles para los usuarios
+normales. La lista de variables y sus nombres se especifica en la configuración del mod, en la pestaña "Variables".
+
+
+
+Las variables especificadas en el mod para cada servidor de juego pueden modificarse después individualmente en la configuración.
+
+La siguiente pestaña en la configuración del mod es "Comandos RCON". Puede especificar comandos RCON para expulsar
+jugadores, banearlos, cambiar el mapa y otros comandos RCON; se utilizan para la administración avanzada del
+servidor de juego.
+
+
+
+Puede especificar sus propios comandos RCON opcionales en la pestaña Fast RCON. Por ejemplo, el comando de estado
+del servidor o de estadísticas.
+
+
diff --git a/es/tutorials/cs2.md b/es/tutorials/cs2.md
new file mode 100644
index 0000000..1181cc0
--- /dev/null
+++ b/es/tutorials/cs2.md
@@ -0,0 +1,80 @@
+---
+title: Counter-Strike 2
+layout: default
+lang: es
+category: Tutoriales
+order: 201
+---
+
+Counter-Strike 2 es un shooter táctico en primera persona lanzado en 2023,
+desarrollado y publicado por Valve. Es la quinta entrega principal de la
+serie Counter-Strike, que aprovecha el éxito de sus predecesores con
+gráficos actualizados, mecánicas de juego y nuevas funciones.
+
+## Preparación del entorno
+
+GameAP es totalmente compatible con Counter-Strike 2, incluida la gestión
+de jugadores. Para empezar a trabajar con el panel de control de GameAP,
+es necesario instalarlo eligiendo una de las opciones disponibles:
+
+* [Instalación de GameAP en Linux](/es/install/install_on_linux.html)
+* [Instalación de GameAP en Windows](/es/install/install_on_windows.html)
+
+### Instalación de GameAP Daemon
+
+Para alojar un servidor de juego en su máquina virtual o servidor dedicado,
+es necesario instalar GameAP Daemon.
+Durante la instalación de GameAP, puede elegir la opción de realizar
+una instalación completa junto con el Daemon.
+
+En el panel de control, vaya a **"Administración"** → **"Servidores dedicados"**
+→ **"Crear"**.
+Aparecerá una ventana con una oferta de instalación automática.
+Copie el código y ejecútelo en el servidor dedicado.
+
+Después de esto, puede proceder con la instalación del servidor de Counter-Strike.
+
+## Instalación de Counter-Strike en GameAP
+
+Vaya a **Administración** → **Servidores de juego** → **Crear**
+
+
+
+* En el campo "Name", introduzca cualquier nombre de servidor,
+ por ejemplo, "My Counter-Strike 2 Server".
+* En el campo "Game", seleccione Counter-Strike 2.
+* En el campo de modificación, elija la modificación.
+* En el campo "Dedicated Server", seleccione el nodo deseado
+ en el que se ubicará el servidor de juego.
+* En el campo IP, elija la dirección deseada de su servidor;
+ luego puede elegir un puerto libre de su servidor o utilizar el sugerido.
+
+### Token del servidor
+
+Después de la instalación, debe especificar un token único `sv_setsteamaccount`
+para el servidor; sin él, el servidor no funcionará.
+
+Para generarlo, vaya a [https://steamcommunity.com/dev/managegameservers](https://steamcommunity.com/dev/managegameservers).
+
+
+
+Tras la generación, el valor del token aparecerá en la tabla;
+utilice el valor de 32 caracteres:
+
+
+
+Debe copiar el valor del token y especificarlo en la configuración del panel de control.
+Vaya a **Servidores** → seleccione su servidor → **Gestión** → **Configuración**
+
+
+
+Después de esto, puede iniciar su servidor.
+
+## Configuración del servidor de Counter-Strike 2
+
+Para cambiar la configuración del servidor, en el gestor de archivos,
+vaya al directorio `/game/csgo/cfg`, donde encontrará muchos archivos *.cfg.
+
+El archivo de configuración principal del servidor de Counter-Strike 2 es `server.cfg`
+
+
diff --git a/es/tutorials/gta5.md b/es/tutorials/gta5.md
new file mode 100644
index 0000000..b392d0d
--- /dev/null
+++ b/es/tutorials/gta5.md
@@ -0,0 +1,78 @@
+---
+title: Grand Theft Auto V
+layout: default
+lang: es
+category: Tutoriales
+order: 203
+---
+
+Grand Theft Auto V es un popular juego de mundo abierto desarrollado por Rockstar North.
+
+FiveM es una modificación para GTA V que permite jugar en línea en modo multijugador.
+Además de FiveM, también existe una modificación similar llamada RageMP.
+
+Puede crear y administrar su propio servidor de GTA V (FiveM) con la ayuda de GameAP.
+
+## Preparación del entorno
+
+Con GameAP, puede administrar los parámetros principales de su servidor.
+GameAP ofrece soporte completo para servidores de juego FiveM,
+incluida la gestión de jugadores.
+
+La instalación del panel llevará poco tiempo. Dependiendo de su sistema operativo, elija una de las siguientes opciones de instalación del panel de control:
+
+* [Instalación en Linux](/es/install/install_on_linux.html)
+* [Instalación en Windows](/es/install/install_on_windows.html)
+
+### Instalación de GameAP Daemon
+
+Para instalar el servidor de juego en una máquina (VDS), debe instalar GameAP Daemon.
+Durante la instalación de GameAP, puede optar por instalarlo por completo,
+incluido Daemon.
+
+En el panel de control, vaya a **"Administración"** → **"Servidores dedicados"**
+→ **"Crear"**. Aparecerá una ventana con una oferta de instalación automática.
+Copie el código y ejecútelo en el servidor dedicado.
+
+Después de esto, puede proceder a instalar el servidor FiveM.
+
+### Creación de un servidor FiveM en GameAP
+
+Vaya a **Administración** → **Servidores de juego** → **Crear**
+
+
+
+* En el campo "Nombre", introduzca cualquier nombre de servidor, por ejemplo, "My GTA V Server".
+* En el campo "Juego", seleccione FiveM de la lista.
+* En el campo de modificación, seleccione la modificación; por defecto es Vanilla.
+* En el campo "Servidor dedicado", seleccione el nodo deseado donde estará ubicado el servidor de juego.
+* En el campo IP, elija la dirección deseada de su servidor; luego puede elegir un puerto libre para su servidor o utilizar el sugerido.
+
+## Clave del servidor
+
+Después de la instalación, debe especificar una clave que debe obtener de
+[keymaster.fivem.net](https://keymaster.fivem.net)
+
+
+
+Después de la generación, verá un mensaje. Debe copiar el valor de la clave.
+
+
+
+Debe copiar el valor de la clave y especificarlo en
+la configuración del panel de control.
+Vaya a **Servidores** → seleccione su servidor FiveM → **Administración** → **Configuración**
+
+
+
+Ahora puede iniciar su servidor FiveM en el panel.
+
+## Configuración del servidor FiveM
+
+La configuración del servidor FiveM se encuentra en el archivo `server.cfg`,
+que está ubicado en el directorio raíz.
+Puede editar este archivo en el gestor de archivos del panel.
+
+Vaya a **Servidores** → seleccione su servidor FiveM → **Administración** → **Archivos**
+
+
diff --git a/es/tutorials/hytale.md b/es/tutorials/hytale.md
new file mode 100644
index 0000000..574c7d3
--- /dev/null
+++ b/es/tutorials/hytale.md
@@ -0,0 +1,158 @@
+---
+title: Hytale
+layout: default
+lang: es
+category: Tutoriales
+order: 205
+---
+
+Hytale es un juego sandbox desarrollado por Hypixel Studios. El estilo visual pixelado general y la mecánica
+de juego son similares a los de Minecraft, con animaciones y efectos de personajes más avanzados.
+
+El juego incluye numerosos biomas, mobs y objetos, además de compatibilidad con mods.
+
+## Configuración del entorno
+
+Con GameAP, puede crear, administrar y configurar fácilmente un servidor de juego de Hytale.
+
+Primero, debe [instalar GameAP](/es/get_started.html#instalación-del-panel):
+
+* [Instalación de GameAP en Linux](/es/install/install_on_linux.html)
+* [Instalación de GameAP en Windows](/es/install/install_on_windows.html)
+
+### Instalación de GameAP Daemon
+
+GameAP Daemon es un agente responsable de gestionar los servidores de juego en máquinas dedicadas.
+Puede instalarse tanto en la misma máquina donde se ejecuta GameAP como en una máquina independiente.
+
+Al instalar GameAP, puede optar por una instalación completa del panel junto con el Daemon utilizando el indicador `--with-daemon`.
+
+## Instalación del servidor de Hytale en GameAP
+
+Vaya a **Administración** → **Servidores de juego** → **Crear**
+
+
+
+* En el campo "Nombre", introduzca cualquier nombre para su servidor.
+* En el campo "Juego", seleccione la opción Hytale.
+* En el campo "Servidor dedicado", seleccione el nodo deseado donde se alojará el servidor de juego.
+* En el campo IP, seleccione la dirección deseada para su servidor; a continuación, puede elegir un puerto disponible o utilizar el sugerido.
+* Introduzca el puerto del servidor de juego; el valor predeterminado es 5520. No es necesario introducir los puertos rcon y query.
+
+> Hytale funciona sobre QUIC, es decir, sobre **UDP**. Abra y redirija el puerto de juego elegido
+> como UDP en el cortafuegos y en el NAT: si solo se redirige TCP, el servidor arranca pero los
+> clientes no pueden conectarse.
+
+### Configuración después del primer inicio
+
+Después del primer inicio, debe completar varios pasos de autorización.
+
+#### Autorización del descargador de archivos del servidor de juego
+
+Después del primer inicio, debe autorizar el dispositivo en el que se ejecuta el servidor de juego.
+En la consola del servidor de juego, busque la línea con el enlace de autorización, copie el enlace y ábralo en su navegador.
+
+
+
+> ¡Nota! Para descargar los archivos del servidor de juego, debe ser propietario del juego.
+> Si no es propietario del juego, verá el mensaje
+> "error fetching manifest: could not get signed URL for manifest: could not get signed URL: HTTP status: 403 Forbidden"
+
+Durante el proceso de autorización en el sitio web del desarrollador, deberá introducir un código que se enviará a su correo electrónico.
+
+
+
+Después de introducir el código, haga clic en "Verify".
+A continuación, en la ventana emergente, haga clic en "Approve" para conceder acceso a su cuenta.
+
+
+
+Después de esto, verá un mensaje indicando que el dispositivo ha sido autorizado.
+
+
+
+Los archivos del servidor de juego comenzarán a descargarse. Esto puede tardar algún tiempo
+dependiendo de la velocidad de su conexión a internet.
+
+
+
+#### Autorización del servidor de juego
+
+Una vez descargados los archivos del servidor de juego, debe autorizar el propio servidor de juego.
+Para ello, introduzca el comando `/auth login device` en la consola del servidor de juego.
+
+
+
+Después de esto, aparecerá en la consola del servidor de juego una línea con un enlace de autorización,
+similar al proceso de autorización del descargador de archivos.
+Copie el enlace y ábralo en su navegador, luego repita los mismos pasos que en la autorización del descargador de archivos.
+
+Si el proceso se realiza correctamente, verá en la consola del servidor de juego un mensaje indicando
+que la autorización se completó con éxito (`Authentication successful! Mode: OAUTH_DEVICE`)
+
+
+
+#### Autorización automática en cada inicio
+
+De forma predeterminada, tras la autorización, deberá introducir el comando `/auth login device`
+para autorizar el servidor de juego en cada inicio. Verá este mensaje:
+```text
+WARNING: Credentials stored in memory only - they will be lost on restart!
+To persist credentials, run: /auth persistence
+Available types: Memory, Encrypted
+```
+
+Para evitar introducir el comando de autorización cada vez, active la persistencia de credenciales ejecutando el comando:
+```text
+/auth persistence Encrypted
+```
+
+Después de esto, aparecerá un archivo `auth.enc` en el directorio raíz del servidor de juego,
+que almacenará los datos de autorización cifrados.
+
+
+
+### Configuración del servidor de juego
+
+#### Configuración mediante archivos
+
+La mayoría de los ajustes del servidor de juego y de los mundos de Hytale se configuran mediante archivos de configuración.
+
+
+| Ruta | Descripción |
+|---------------------|-------------------------------------------|
+| .cache/ | Caché del servidor de juego |
+| logs/ | Registros del servidor de juego |
+| mods/ | Mods instalados |
+| universe/ | Archivos de mundos y datos de jugadores |
+| bans.json | Jugadores baneados |
+| config.json | Configuración principal del servidor de juego |
+| permissions.json | Configuración de permisos |
+| whitelist.json | Lista blanca |
+
+Los ajustes principales se encuentran en el archivo `config.json`.
+
+
+
+Aquí puede configurar los siguientes parámetros:
+* `ServerName` — el nombre de su servidor que se mostrará en la lista de servidores.
+* `MaxPlayers` — el número máximo de jugadores que pueden estar en el servidor al mismo tiempo.
+* `ServerPassword` — contraseña para acceder a su servidor si desea hacerlo privado.
+* `MaxViewRadius` — la distancia máxima a la que los jugadores pueden ver objetos en el mundo.
+ Establecer un valor demasiado alto puede afectar negativamente al rendimiento del servidor.
+ La distancia de visión es el principal factor que afecta al consumo de RAM.
+* `MOTD` — mensaje del día que se mostrará a los jugadores al conectarse al servidor.
+
+
+Los ajustes y datos de los mundos se encuentran en el directorio `universe/worlds/`, que contiene las carpetas de los mundos.
+Cada carpeta de mundo contiene un archivo `config.json` con los ajustes del mundo, por ejemplo `universe/worlds/default/config.json`:
+
+
+
+Aquí puede configurar la Seed (semilla de generación del mundo), varios parámetros de generación, ajustes de chunks, NPC, PVP
+y otros parámetros.
+
+### Enlaces útiles
+
+* [Sitio web oficial de Hytale](https://hytale.com/)
+* [Manual en el sitio web oficial](https://support.hytale.com/hc/en-us/articles/45326769420827-Hytale-Server-Manual)
diff --git a/es/tutorials/minecraft.md b/es/tutorials/minecraft.md
new file mode 100644
index 0000000..1e92949
--- /dev/null
+++ b/es/tutorials/minecraft.md
@@ -0,0 +1,94 @@
+---
+title: Minecraft
+layout: default
+lang: es
+category: Tutoriales
+order: 200
+---
+
+Minecraft es un popular juego sandbox desarrollado
+por Mojang Studios, una filial de Xbox Game Studios.
+Permite a los jugadores construir, explorar y sobrevivir en
+un mundo generado proceduralmente y compuesto por bloques.
+El juego cuenta con varios modos: supervivencia, creativo
+y aventura. Minecraft tiene una gran comunidad de jugadores
+que crean y comparten diversas creaciones, mods y skins.
+
+## Preparación del entorno
+
+Para comenzar, es necesario [instalar GameAP](/es/get_started.html#instalación-del-panel), lo que tomará unos minutos:
+
+* [Instalación de GameAP en Linux](/es/install/install_on_linux.html)
+* [Instalación de GameAP en Windows](/es/install/install_on_windows.html)
+
+### Instalación de GameAP Daemon
+
+En el nodo donde se alojará el servidor de juego, es necesario
+instalar GameAP Daemon. Durante el proceso de instalación de GameAP,
+puede elegir la opción de instalarlo junto con el Daemon.
+
+En el panel de control, vaya a **Administración** → **Servidores dedicados**
+→ **Crear**.
+Aparecerá una ventana con una oferta de instalación automática.
+Copie el código y ejecútelo en el servidor dedicado.
+
+Después de esto, puede proceder a instalar el servidor de Minecraft.
+
+## Instalación de Minecraft en GameAP
+
+Vaya a **Administración** → **Servidores de juego** → **Crear**
+
+
+
+* En el campo "Name", introduzca cualquier nombre de servidor.
+* En el campo "Game", seleccione Minecraft.
+* En el campo de modificación, recomendamos elegir "Multicore", que permite gestionar fácilmente las versiones del servidor de Minecraft.
+* En el campo "Dedicated Server", seleccione el nodo deseado donde se ubicará el servidor de juego.
+* En el campo IP, elija la dirección deseada de su servidor; luego puede elegir un puerto libre para su servidor o utilizar el sugerido.
+
+Vea un breve vídeo sobre el proceso de instalación de un servidor de Minecraft en GameAP:
+
+
+
+## Gestión del servidor de Minecraft
+
+Para gestionar el servidor de Minecraft, vaya a la sección **Servidores**, luego seleccione su servidor y haga clic en **Gestión**. En esta página podrá iniciar y detener su servidor, ver su consola y enviar comandos.
+
+
+
+### Cambio de la versión del servidor
+
+Si eligió la modificación "Multicore", puede cambiar la versión de su servidor. Para ello, vaya a la página principal de gestión de su servidor y luego seleccione **Configuración**.
+
+
+
+#### Ejemplos de configuración
+
+##### Predeterminada
+
+* Versión de Minecraft: 1.14.3
+* Núcleo: vanilla
+
+##### Forge 1.19
+
+En este ejemplo, utilizaremos un servidor de Minecraft versión 1.19.4 con Forge API para soporte de mods versión 45.0.50
+
+* Versión de Minecraft: 1.19.4
+* Núcleo: forge
+* Versión del mod del núcleo: 45.0.50
+
+##### Spigot
+
+En este ejemplo, utilizaremos un servidor de Minecraft versión 1.19.4 con Spigot API para soporte de plugins.
+
+* Versión de Minecraft: 1.19.4
+* Núcleo: spigot
+
+### server.properties
+
+`server.properties` es el archivo de configuración principal del servidor de Minecraft. Puede editarlo mediante el gestor de archivos de GameAP.
+
+
diff --git a/es/tutorials/quake3.md b/es/tutorials/quake3.md
new file mode 100644
index 0000000..d911f9e
--- /dev/null
+++ b/es/tutorials/quake3.md
@@ -0,0 +1,69 @@
+---
+title: Quake III Arena
+layout: default
+lang: es
+category: Tutoriales
+order: 204
+---
+
+Quake III Arena es un shooter multijugador en primera persona desarrollado por id Software.
+El juego se centra por completo en el combate multijugador y carece de una campaña tradicional para un jugador.
+
+El juego es similar a Unreal Tournament y a otros shooters en primera persona de finales de los años 1990 y principios de los 2000.
+
+## Preparación del entorno
+
+GameAP ofrece soporte para los servidores de juego de Quake III Arena, su configuración y gestión.
+
+* [Instalación de GameAP en Linux](/es/install/install_on_linux.html)
+* [Instalación de GameAP en Windows](/es/install/install_on_windows.html)
+
+### Instalación de GameAP Daemon
+
+GameAP Daemon es un agente responsable de gestionar los servidores de juego en máquinas dedicadas. Para instalar un servidor de juego en una máquina (VDS), es necesario instalar GameAP Daemon.
+
+Al instalar GameAP, puede elegir una instalación completa que incluya el Daemon (utilizando la opción `--with-daemon`).
+
+En el panel de control, vaya a **"Administración"** → **"Servidores dedicados"** → **"Crear"**. Aparecerá una ventana con una oferta de instalación automática. Copie el código y ejecútelo en el servidor dedicado.
+
+Después de esto, puede proceder con la instalación del servidor de Quake III Arena.
+
+## Instalación del servidor de Quake III Arena en GameAP
+
+Vaya a **Administración** → **Servidores de juego** → **Crear**
+
+
+
+* En el campo "Name", introduzca cualquier nombre de servidor, por ejemplo "My Quake III Server".
+* En el campo "Game", seleccione "Quake 3" en la lista desplegable.
+* En el campo "Modification", especifique la modificación deseada; el valor predeterminado es `ioquake3`. También está disponible la opción `quake3e`.
+* En el campo "Dedicated Server", especifique el nodo donde se alojará el servidor de juego. De forma predeterminada, se selecciona el primer nodo disponible.
+* En el campo "IP", seleccione la dirección deseada para su servidor; luego especifique un puerto disponible o deje el sugerido por el sistema.
+
+## Configuración del servidor de Quake III Arena
+
+Para cambiar la configuración del servidor, vaya a la sección **Servidor**, seleccione su servidor y haga clic en **Gestión**. A continuación, abra la pestaña **Configuración**.
+
+
+
+Para que los cambios surtan efecto, es necesario reiniciar el servidor.
+
+### Server Hostname / Nombre del servidor
+
+El nombre de su servidor de Quake III que será visible para todos los jugadores en la lista de servidores al realizar la búsqueda.
+
+### Maximum Players on Server / Número máximo de jugadores en el servidor
+
+El número máximo de jugadores que pueden estar en el servidor al mismo tiempo. El valor predeterminado es 16.
+
+### Default Map of the Server / Mapa predeterminado del servidor
+
+El mapa que se cargará automáticamente al iniciar el servidor. El mapa predeterminado es `q3dm17`, conocido como "The Longest Yard".
+
+### Timelimit in Minutes / Límite de tiempo en minutos
+
+La duración máxima de la ronda en minutos. El valor predeterminado es 20 minutos.
+
+### Frag Limit / Límite de frags
+
+El número de frags (eliminaciones) al alcanzar el cual finaliza la ronda. El valor predeterminado es 20 frags.
diff --git a/es/tutorials/rust.md b/es/tutorials/rust.md
new file mode 100644
index 0000000..20aca42
--- /dev/null
+++ b/es/tutorials/rust.md
@@ -0,0 +1,93 @@
+---
+title: Rust
+layout: default
+lang: es
+category: Tutoriales
+order: 202
+---
+
+Rust es un juego de supervivencia desarrollado por Facepunch Studios.
+Los jugadores deben recolectar recursos, fabricar objetos
+y construir refugios para sobrevivir en un entorno hostil de mundo abierto.
+
+Rust es algo similar a [Minecraft](/es/tutorials/minecraft.html)
+por su sistema de supervivencia y fabricación de objetos.
+
+## Preparación del entorno
+
+GameAP ofrece soporte completo para servidores de juego de Rust, incluida la gestión de jugadores.
+La instalación del panel tomará poco tiempo; elige una de las siguientes
+opciones de instalación del panel de control:
+
+* [Instalación de GameAP en Linux](/es/install/install_on_linux.html)
+* [Instalación de GameAP en Windows](/es/install/install_on_windows.html)
+
+### Instalación de GameAP Daemon
+
+Para instalar el servidor de juego en una máquina (VDS),
+es necesario instalar GameAP Daemon.
+Durante la instalación de GameAP puedes elegir instalarlo por completo,
+incluido Daemon.
+
+En el panel de control, ve a **"Administración"** → **"Servidores dedicados"**
+→ **"Crear"**.
+Aparecerá una ventana con una oferta de instalación automática.
+Copia el código y ejecútalo en el servidor dedicado.
+
+Después de esto, puedes proceder a instalar el servidor de Rust.
+
+## Instalación de un servidor de Rust en GameAP
+
+Ve a **Administración** → **Servidores de juego** → **Crear**
+
+
+
+* En el campo "Nombre", introduce cualquier nombre de servidor, por ejemplo, "My Rust Server".
+* En el campo "Juego", selecciona Rust de la lista.
+* En el campo de modificación, selecciona la modificación; por defecto, Vanilla.
+* En el campo "Servidor dedicado", selecciona el nodo deseado donde se ubicará el servidor de juego.
+* En el campo IP, elige la dirección deseada de tu servidor; luego puedes elegir un puerto libre para tu servidor o usar el sugerido.
+
+## Configuración del servidor de juego
+
+Para cambiar la configuración del servidor, ve a la sección **Servidor**,
+selecciona tu servidor y haz clic en **Administración**. Luego, abre la pestaña **Configuración**.
+
+
+
+Es necesario reiniciar el servidor para que la configuración surta efecto.
+
+### Nombre del servidor
+
+Este es el nombre de tu servidor de Rust, que se mostrará
+a todos los jugadores en el juego en la ventana de búsqueda de servidores.
+
+### Número máximo de jugadores en el servidor
+
+El número máximo de jugadores que pueden jugar en el servidor simultáneamente.
+Por defecto, 32.
+
+### Mapa en el servidor
+
+El mapa en el servidor.
+Por defecto, el mapa generado proceduralmente Procedural Map.
+Valores posibles:
+
+* Procedural Map
+* Barren
+* Craggy Island
+* Hapis
+* Savas Island
+
+### Semilla de generación del mapa
+
+La semilla para generar el Procedural Map.
+
+### Tamaño del mundo
+
+El tamaño del mundo.
+
+### Intervalo de guardado del servidor
+
+El intervalo de guardado del mundo.
+Este es el período durante el cual los datos del servidor se guardarán en el disco.
diff --git a/es/upgrade.md b/es/upgrade.md
new file mode 100644
index 0000000..6b95120
--- /dev/null
+++ b/es/upgrade.md
@@ -0,0 +1,80 @@
+---
+title: Actualización
+layout: default
+lang: es
+category: Instalación de GameAP
+order: 190
+---
+
+Al instalar GameAP, se instala la utilidad `gameapctl`,
+que permite administrar el entorno del panel, incluidas las actualizaciones.
+
+Esta página trata sobre la actualización dentro de la cuarta versión. La migración desde GameAP 3 se describe
+por separado: [Actualización de v3 a v4](/es/upgrade_from_v3_to_v4.html).
+
+Haga una copia de seguridad de la base de datos antes de actualizar: `gameapctl` no la guarda, y una actualización puede cambiar
+el esquema.
+
+> **Autenticación de dos factores obligatoria para los administradores.**
+>
+> En GameAP 4 este requisito está activado por defecto. Después de la actualización, los administradores sin 2FA
+> verán un recordatorio, y transcurridos 30 días el inicio de sesión ya no otorgará una sesión completa hasta que
+> se active 2FA. La cuenta regresiva de cada administrador comienza con su primer inicio de sesión después de la actualización.
+>
+> Para mantener el recordatorio pero eliminar el bloqueo, defina `AUTH_MFA_HARD_FAIL_DAYS=0` en `config.env`.
+> Para desactivar el requisito por completo — `AUTH_REQUIRE_MFA_FOR_ADMINS=false`.
+>
+> Los detalles y qué hacer si pierde el acceso están en la
+> página de [Seguridad](/es/security.html).
+
+## GameAP Web/API
+
+### Linux
+
+Para actualizar el panel, ejecute el comando:
+```shell
+gameapctl panel upgrade
+```
+
+### Windows
+
+Para actualizar el panel en Windows,
+puede ejecutar el comando donde esté instalado `gameapctl`:
+```powershell
+C:\path\to\gameapctl.exe panel upgrade
+```
+
+O use la interfaz gráfica. Ejecute `gameapctl.exe` y, en la ventana del navegador que se abra,
+haga clic en **"Upgrade"** en la sección Web/API.
+
+
+
+### Actualización a una versión específica
+
+Por defecto se instala la última versión estable. Para elegir otra, especifíquela mediante su etiqueta:
+
+```shell
+gameapctl panel upgrade --version=4.2.0
+```
+
+## Actualización de GameAP Daemon
+
+### Linux
+
+Para actualizar el Daemon, ejecute el comando:
+```shell
+gameapctl daemon upgrade
+```
+
+### Windows
+
+Para actualizar GameAP Daemon en Windows, puede ejecutar el comando
+donde esté instalado `gameapctl`:
+```powershell
+C:\path\to\gameapctl.exe daemon upgrade
+```
+
+O use la interfaz gráfica. Ejecute `gameapctl.exe` y, en la ventana del navegador que se abra,
+haga clic en **"Upgrade"** en la sección GameAP Daemon.
+
+
diff --git a/es/upgrade_from_v3_to_v4.md b/es/upgrade_from_v3_to_v4.md
new file mode 100644
index 0000000..cc1fe57
--- /dev/null
+++ b/es/upgrade_from_v3_to_v4.md
@@ -0,0 +1,282 @@
+---
+title: Actualización de v3 a v4
+layout: default
+lang: es
+category: Instalación de GameAP
+order: 191
+---
+
+La actualización desde GameAP 3 conservando la base de datos existente solo es posible a
+las **versiones 4.0 y 4.1**. Estas reconocen una base de datos de la tercera versión,
+amplían su esquema con sus propias tablas y siguen trabajando con los mismos usuarios,
+servidores y juegos.
+
+> **A partir de la versión 4.2, no se admite la actualización in situ.** Las versiones
+> posteriores esperan el esquema de la cuarta versión y no funcionarán con una base de
+> datos de GameAP 3.
+>
+> Si necesita la última versión del panel, instálela sobre una base de datos limpia y
+> migre los datos usted mismo: consulte
+> [Si necesita la última versión](#if-you-need-the-latest-version).
+
+La última versión de la línea compatible es **4.1.2**.
+
+## Antes de actualizar
+
+> **Haga una copia de seguridad de la base de datos manualmente.** `gameapctl` copia el
+> directorio de instalación de v3 y la configuración del servidor web, pero **no guarda
+> la base de datos**, y la actualización cambia su esquema de forma irreversible.
+
+```shell
+# MySQL o MariaDB
+mysqldump -u root -p gameap > gameap-v3-backup.sql
+
+# PostgreSQL
+pg_dump -U gameap gameap > gameap-v3-backup.sql
+
+# SQLite: basta con copiar el archivo
+cp /var/www/gameap/database.sqlite gameap-v3-backup.sqlite
+```
+
+Compruebe que la copia de seguridad no esté vacía y solo entonces continúe.
+
+### Qué bases de datos se pueden actualizar in situ
+
+| SGBD en GameAP 3 | Actualización in situ |
+|------------------|-----------------------|
+| MySQL, MariaDB | sí |
+| SQLite | sí |
+| PostgreSQL | **no** |
+
+Para MySQL y SQLite, el panel reconoce una base de datos existente de GameAP 3 y no
+intenta crear las tablas de nuevo. Para PostgreSQL no existe tal reconocimiento: la
+primera migración intentará crear tablas que ya existen y fallará.
+
+Si GameAP 3 funciona con PostgreSQL, instale GameAP 4 sobre una base de datos limpia y
+migre los datos usted mismo.
+
+## Actualización a 4.1
+
+`gameapctl` se instala junto con el panel y puede realizar la transición.
+
+```shell
+gameapctl self-update
+gameapctl panel upgrade
+```
+
+La utilidad detectará que está instalada la tercera versión y realizará la actualización.
+
+> **Compruebe qué versión instala `gameapctl`.** Al pasar desde la tercera versión,
+> ignora el flag `--version` e instala la última versión estable de la línea 4.x. Si
+> resulta ser 4.2 o más reciente, la base de datos de producción no debe actualizarse.
+>
+> Pruebe primero la transición en una copia de la base de datos: el procedimiento se
+> describe en [Prueba en una copia de la base de datos](#testing-on-a-database-copy).
+> Asegúrese de que se instaló 4.0 o 4.1 y solo entonces actualice la instalación de
+> producción.
+
+Qué ocurre durante la actualización:
+
+1. Se lee el archivo `.env` de la instalación de GameAP 3 y se toman de él los
+ parámetros de conexión a la base de datos.
+2. El directorio de instalación de v3 y la configuración del servidor web se copian a un
+ directorio temporal. La ruta de la copia se muestra en la consola: anótela.
+3. Se crea la configuración de GameAP 4 `config.env`, con la misma base de datos
+ conectada y nuevas claves `AUTH_SECRET` y `ENCRYPTION_KEY` generadas.
+4. GameAP 4 se instala y se inicia.
+
+En el primer inicio, el panel aplica las migraciones: detecta que la base de datos
+pertenece a la tercera versión, omite la creación de tablas y añade solo lo que le falta
+a la cuarta versión.
+
+### gameapctl no está instalado
+
+Descárguelo desde la [página de releases](https://github.com/gameap/gameapctl/releases)
+o con un comando que sustituye la versión correcta:
+
+```shell
+VERSION=$(curl -sL https://api.github.com/repos/gameap/gameapctl/releases/latest \
+ | grep -m1 '"tag_name"' | cut -d'"' -f4)
+curl -OL "https://github.com/gameap/gameapctl/releases/download/${VERSION}/gameapctl-${VERSION}-linux-amd64.tar.gz"
+tar xvfz "gameapctl-${VERSION}-linux-amd64.tar.gz" -C /usr/local/bin
+```
+
+## Qué cambia con la transición
+
+**Ya no se necesitan un servidor web ni PHP.** GameAP 4 es un único ejecutable con una
+interfaz web integrada. Tras la actualización, nginx o Apache pueden conservarse como
+proxy inverso o eliminarse por completo.
+
+**El puerto predeterminado es 8025**, no 80.
+
+**Las contraseñas de los usuarios siguen funcionando.** Las versiones 4.0 y 4.1
+verifican las contraseñas del mismo modo que la tercera versión. No es necesario pedir
+a los usuarios que cambien sus contraseñas.
+
+**Los daemons siguen funcionando con el protocolo antiguo.** En las versiones 4.0 y 4.1
+el panel intercambia datos con GameAP Daemon del mismo modo que la tercera versión y se
+conecta al propio daemon. La configuración de los servidores dedicados no requiere
+cambios, y el puerto del daemon (`31717` por defecto) debe permanecer abierto.
+
+> No ejecute `gameapctl daemon upgrade --switch-to-grpc` después de actualizar a 4.1.
+> El intercambio de datos por gRPC solo apareció en la versión 4.2, y un panel 4.1 no
+> podrá conectarse a un daemon de ese tipo.
+
+## Reversión
+
+Si algo no funciona después de la actualización:
+
+1. Detenga GameAP 4: `gameapctl panel stop`.
+2. Restaure la base de datos desde la copia de seguridad realizada antes de la
+ actualización.
+3. Recupere el directorio de instalación de v3 desde la copia temporal cuya ruta mostró
+ `gameapctl`.
+4. Restaure la configuración del servidor web e inícielo.
+
+> Una reversión sin copia de seguridad de la base de datos es imposible: el esquema ha
+> cambiado y GameAP 3 ya no funcionará con ella.
+
+## Prueba en una copia de la base de datos {#testing-on-a-database-copy}
+
+Antes de actualizar la instalación de producción, conviene ejecutar la transición en una
+copia de la base de datos. Esto también muestra qué versión exacta instalará
+`gameapctl`.
+
+> No apunte la instalación de prueba a la base de datos de producción: en el primer
+> inicio el panel aplicará migraciones sobre ella y GameAP 3 dejará de funcionar.
+
+Cree una copia de la base de datos:
+
+```shell
+mysqldump -u root -p gameap > /tmp/gameap.sql
+mysql -u root -p -e "CREATE DATABASE gameap_v4_test"
+mysql -u root -p gameap_v4_test < /tmp/gameap.sql
+```
+
+Descargue una versión de la línea 4.1 desde la
+[página de releases](https://github.com/gameap/gameap/releases) y desempaquétela:
+
+```shell
+curl -OL https://github.com/gameap/gameap/releases/download/v4.1.2/gameap-v4.1.2-linux-amd64.tar.gz
+tar xvfz gameap-v4.1.2-linux-amd64.tar.gz -C /usr/bin
+```
+
+Cree el archivo de configuración `/etc/gameap/config.env`:
+
+```dotenv
+DATABASE_DRIVER=mysql
+DATABASE_URL=gameap:password@tcp(127.0.0.1:3306)/gameap_v4_test?parseTime=true
+
+AUTH_SECRET=replace_with_32_random_bytes
+ENCRYPTION_KEY=replace_with_32_random_bytes
+
+HTTP_PORT=8025
+```
+
+Genere las claves con `openssl rand -base64 24` para `AUTH_SECRET` y `openssl rand -hex 32` para
+`ENCRYPTION_KEY`: la primera se ajusta exactamente a 32 bytes, la segunda se hashea por completo.
+
+Ejecute:
+
+```shell
+gameap --env /etc/gameap/config.env
+```
+
+El panel estará disponible en el puerto 8025. La lista completa de parámetros de
+configuración está en la [referencia de config.env](/es/config.html).
+
+Asegúrese de que los datos están intactos: los usuarios pueden iniciar sesión, y los
+servidores de juego y los juegos se muestran. Después de eso puede actualizar la
+instalación de producción.
+
+### Configuración de un servicio systemd
+
+Para una gestión cómoda de la instalación de prueba, cree un servicio aparte.
+
+Cree un usuario y un directorio:
+
+```shell
+useradd -r -s /usr/sbin/nologin -d /var/lib/gameap gameap
+mkdir -p /var/lib/gameap
+chown gameap:gameap /var/lib/gameap
+```
+
+Luego el archivo `/etc/systemd/system/gameap.service`:
+
+```ini
+[Unit]
+Description=GameAP - Game Server Control Panel
+Documentation=https://docs.gameap.com
+After=network.target
+Wants=network-online.target
+Requires=network.target
+
+[Service]
+Type=simple
+User=gameap
+Group=gameap
+
+WorkingDirectory=/var/lib/gameap
+
+ExecStart=/usr/bin/gameap
+
+# Permitir el enlace a puertos privilegiados
+AmbientCapabilities=CAP_NET_BIND_SERVICE
+
+# Parada controlada
+ExecStop=/bin/kill -TERM $MAINPID
+KillMode=mixed
+KillSignal=SIGTERM
+TimeoutStopSec=30
+
+# Política de reinicio
+Restart=always
+RestartSec=5
+StartLimitInterval=60
+StartLimitBurst=3
+
+EnvironmentFile=/etc/gameap/config.env
+
+RuntimeDirectory=gameap
+PIDFile=/run/gameap/gameap.pid
+
+# Permisos del sistema de archivos
+ProtectSystem=strict
+ProtectHome=true
+PrivateTmp=true
+
+ReadWritePaths=/var/lib/gameap
+
+# Registro
+StandardOutput=journal
+StandardError=journal
+SyslogIdentifier=gameap
+
+[Install]
+WantedBy=multi-user.target
+```
+
+Habilite e inicie el servicio:
+
+```shell
+systemctl daemon-reload
+systemctl enable gameap
+systemctl start gameap
+```
+
+## Si necesita la última versión {#if-you-need-the-latest-version}
+
+No se puede actualizar desde GameAP 3 directamente a 4.2 o más reciente. El
+procedimiento es el siguiente:
+
+1. Instale la versión actual de GameAP 4 sobre una base de datos **limpia**, dejando
+ intacta la instalación de producción; consulte
+ [Instalación en Linux](/es/install/install_on_linux.html).
+2. Migre los datos de GameAP 3: vuelva a crear los usuarios, servidores dedicados,
+ juegos y servidores de juego, manualmente o a través de la
+ [API](https://openapi.gameap.io/).
+3. Asegúrese de que todo funciona y solo entonces retire la instalación antigua.
+
+Los archivos de los servidores de juego no necesitan moverse a ninguna parte:
+permanecen en el servidor dedicado; basta con describir los servidores en el nuevo
+panel con los mismos directorios y puertos.
diff --git a/es/users.md b/es/users.md
new file mode 100644
index 0000000..6d8a763
--- /dev/null
+++ b/es/users.md
@@ -0,0 +1,172 @@
+---
+title: Usuarios, roles y permisos
+layout: default
+lang: es
+category: Administración
+order: 332
+---
+
+El acceso en el panel se basa en tres conceptos:
+
+* **Permiso** — una acción permitida concreta, por ejemplo, "iniciar un servidor de juego".
+* **Rol** — un conjunto de permisos con nombre. Un rol se asigna a un usuario.
+* **Permiso directo** — un permiso otorgado a un usuario sin pasar por los roles.
+
+Los permisos pueden otorgarse de forma global o vincularse a un objeto concreto — con mayor
+frecuencia a un único servidor de juego. Así es como un usuario obtiene acceso a su propio
+servidor y a nada más.
+
+## Roles predeterminados
+
+Durante la inicialización de la base de datos se crean dos roles:
+
+| Rol | Nombre en la interfaz | Permisos |
+|---------|-----------------------|---------------------------------|
+| `admin` | Administrador | `admin roles & permissions` |
+| `user` | Usuario | Ninguno |
+
+El primer usuario creado recibe el rol `admin`. El rol `user` no contiene ningún permiso: a un
+usuario normal se le da acceso no mediante el rol, sino mediante permisos sobre servidores de
+juego concretos.
+
+> El permiso **`admin roles & permissions`** otorgado de forma global es lo que convierte a
+> alguien en administrador. Controla el acceso a la sección "Administración" y también determina
+> a quién se aplica la autenticación de dos factores obligatoria — consulte
+> [Seguridad](/es/security.html).
+>
+> En el panel no existe un rol especial de "superadministrador", y el usuario con `id = 1` no
+> tiene privilegios por defecto: solo importa disponer de este permiso.
+
+## Permisos de servidor de juego
+
+| Permiso | Nombre en la interfaz |
+|----------------------------|-------------------------------------------|
+| `game-server-common` | Capacidad común del servidor de juego |
+| `game-server-start` | Iniciar servidor de juego |
+| `game-server-stop` | Detener servidor de juego |
+| `game-server-restart` | Reiniciar servidor de juego |
+| `game-server-pause` | Pausar servidor de juego |
+| `game-server-update` | Actualizar servidor de juego |
+| `game-server-files` | Acceso al gestor de archivos |
+| `game-server-tasks` | Acceso al planificador de tareas |
+| `game-server-settings` | Acceso a la configuración |
+| `game-server-console-view` | Acceso de lectura a la consola del servidor |
+| `game-server-console-send` | Acceso para enviar comandos a la consola |
+| `game-server-rcon-console` | Consola RCON |
+| `game-server-rcon-players` | Gestión de jugadores RCON |
+| `game-server-metrics` | Acceso a las métricas del servidor |
+
+`game-server-common` proporciona acceso básico al servidor: verlo en la lista y abrir su
+página. Sin él, los demás permisos son prácticamente inútiles — empiece por este.
+
+Los permisos de lectura y escritura de la consola están separados: `game-server-console-view`
+solo permite verla, `game-server-console-send` — enviar comandos. Lo mismo ocurre con RCON: el
+acceso a la consola RCON y la gestión de jugadores se otorgan por separado.
+
+Además de los permisos de servidor de juego, existen los permisos genéricos `create`, `view`,
+`edit` y `delete` — se aplican a las secciones del panel.
+
+## Otorgar permisos sobre un servidor concreto
+
+**Administración** → **Usuarios** → seleccione un usuario → **Editar**.
+
+El formulario tiene dos bloques:
+
+* **Roles** — `Administrador` o `Usuario`.
+* **Servidores de juego** — una lista de servidores, en la que se pueden marcar los permisos
+ necesarios para cada uno.
+
+Los permisos otorgados aquí están vinculados al servidor concreto: el usuario obtiene acceso a
+él y a nada más.
+
+Lo mismo se hace a través de la API:
+
+```http
+GET /api/users/{id}/servers — los servidores del usuario
+GET /api/users/{id}/servers/{server}/permissions — permisos sobre el servidor
+PUT /api/users/{id}/servers/{server}/permissions — cambiar permisos
+```
+
+Sus propios permisos sobre un servidor pueden consultarse con
+`GET /api/servers/{server}/abilities`.
+
+## Cómo se comprueba el acceso
+
+Los permisos de un usuario se recopilan de dos fuentes: los otorgados directamente y los que
+provienen de todos sus roles. Después se aplican dos reglas.
+
+**Una denegación es más fuerte que una concesión.** Si existe un registro de denegación para el
+permiso en cuestión, el acceso no se concede, sin importar cuántas concesiones se hayan emitido
+por otras vías ni en qué orden estén registradas.
+
+**El ámbito debe coincidir.** Un permiso otorgado para un servidor no tiene efecto sobre otro.
+Una denegación establecida en un servidor tampoco afecta a los demás.
+
+Un permiso puede otorgarse en tres ámbitos:
+
+| Ámbito | Significado |
+|-----------------------------|-------------------------------------------------------------------|
+| Global | Se aplica en todas partes |
+| Sobre un tipo de objeto | Se aplica a todos los servidores de juego, todos los servidores dedicados, etc. |
+| Sobre un objeto concreto | Se aplica solo a este servidor |
+
+Las comprobaciones que requieren acceso global — la comprobación de administrador, por ejemplo —
+tienen en cuenta **únicamente un permiso otorgado de forma global**. Una concesión vinculada a
+un único servidor no convierte a nadie en administrador.
+
+> Revocar un permiso en la interfaz no siempre significa simplemente eliminar el registro. Si
+> tras la eliminación el usuario siguiera recibiendo el permiso a través de un rol, el panel
+> establece además una denegación explícita — de lo contrario, el permiso volvería a través del
+> rol.
+
+### Caché
+
+Los resultados de las comprobaciones se almacenan en caché durante el tiempo indicado en
+`RBAC_CACHE_TTL` (30 segundos por defecto). Los cambios de permisos realizados a través de la
+interfaz y la API vacían la caché inmediatamente. Las ediciones hechas directamente en la base
+de datos no vacían la caché — esos cambios surten efecto dentro del tiempo configurado.
+
+## Gestión de usuarios
+
+| Método y ruta | Propósito |
+|--------------------------|---------------------------------|
+| `GET /api/users` | Lista de usuarios |
+| `POST /api/users` | Crear un usuario |
+| `GET /api/users/{id}` | Detalles del usuario |
+| `PUT /api/users/{id}` | Actualizar un usuario |
+| `DELETE /api/users/{id}` | Eliminar un usuario |
+
+La lista de usuarios está disponible solo para los administradores.
+
+La contraseña establecida al crear o actualizar un usuario pasa por la comprobación de la
+política de contraseñas: al menos 12 bytes y no debe figurar en la lista de contraseñas
+comprometidas. Consulte [Seguridad](/es/security.html) para más detalles.
+
+En el panel no hay autoregistro de usuarios: las cuentas las crea un administrador.
+
+## Tareas habituales
+
+### Dar a un usuario acceso a un único servidor
+
+1. Cree un usuario: **Administración** → **Usuarios** → **Crear**.
+2. Asígnele el rol `Usuario`.
+3. En el bloque "Servidores de juego", seleccione el servidor y marque los permisos. El
+ conjunto mínimo funcional: `game-server-common`, `game-server-start`, `game-server-stop`,
+ `game-server-restart`.
+4. Añada según sea necesario: el gestor de archivos, la consola, RCON, las tareas.
+
+### Convertir a un usuario en administrador
+
+Asigne el rol `Administrador`. Contiene el permiso `admin roles & permissions`, que otorga
+acceso completo.
+
+> A partir de ese momento, el requisito de autenticación de dos factores se aplica al usuario:
+> en su próximo inicio de sesión verá un recordatorio y, tras 30 días, una exigencia de
+> activarla. Consulte [Seguridad](/es/security.html).
+
+### Revocar el acceso sin eliminar la cuenta
+
+Elimine los permisos sobre los servidores y deje el rol `Usuario`. El usuario podrá iniciar
+sesión, pero no verá ningún servidor. Las sesiones activas no se terminan con esto — siguen
+funcionando hasta que caducan (24 horas, o 7 días para los inicios de sesión con "recordarme"
+activado).
diff --git a/es/websocket.md b/es/websocket.md
new file mode 100644
index 0000000..4626a13
--- /dev/null
+++ b/es/websocket.md
@@ -0,0 +1,169 @@
+---
+title: WebSocket y métricas
+layout: default
+lang: es
+category: Administración
+order: 337
+---
+
+A través de WebSocket el panel sirve todo lo que se actualiza en tiempo real: el progreso de
+las tareas, la consola del servidor de juego y las métricas. La interfaz funciona a través de
+estas mismas conexiones.
+
+## Conexión
+
+Las conexiones se abren en seis direcciones:
+
+| Dirección | Qué sirve |
+|--------------------------------------|----------------------------------------------------|
+| `/api/ws/tasks/{id}` | Progreso de la tarea y su salida |
+| `/api/ws/servers/{server}/console` | La consola del servidor de juego, en ambos sentidos|
+| `/api/ws/servers/{server}/attach` | Una sesión interactiva con el servidor de juego |
+| `/api/ws/servers/{server}/metrics` | Métricas del servidor de juego |
+| `/api/ws/nodes/{id}/metrics` | Métricas del servidor dedicado |
+| `/api/ws/nodes/metrics` | Métricas de todos los servidores dedicados |
+
+### Autorización
+
+El token se pasa en un parámetro de consulta porque no se pueden establecer cabeceras al abrir
+un WebSocket desde un navegador:
+
+```text
+wss://panel.example.com:8025/api/ws/servers/1/console?token=
+```
+
+**Solo se aceptan tokens de corta duración** con el prefijo `glst_` **en el parámetro `token`**.
+Un token de acceso personal no se puede pasar ahí, de modo que una clave de acceso de larga
+duración no acabará en la URL ni por error.
+
+> El propio token de corta duración sí permanece en la dirección y puede quedar registrado en los
+> registros de un proxy inverso, de un servidor web o de los sistemas de observabilidad. Que sea de
+> un solo uso y viva 10 segundos deja esa entrada sin valor, pero si los registros se conservan
+> mucho tiempo, es mejor recortar de ellos el parámetro `token`.
+
+Obtención de un token de corta duración:
+
+```bash
+curl -X POST https://panel.example.com:8025/api/auth/short-lived-token \
+ -H "Authorization: Bearer "
+```
+
+El token es de un solo uso y no vive más de 10 segundos, por lo que debe solicitarse
+inmediatamente antes de abrir la conexión. Consulte [API y tokens](/es/api.html) para más
+detalles.
+
+## Formato de los frames
+
+Todos los mensajes son JSON con la misma estructura:
+
+```json
+{
+ "type": "task.status",
+ "payload": { },
+ "ts": 1711234567
+}
+```
+
+| Campo | Descripción |
+|-----------|----------------------------------------------|
+| `type` | Tipo de mensaje |
+| `payload` | Datos, dependen del tipo |
+| `ts` | Marca de tiempo, en segundos Unix |
+
+### Tipos de mensajes
+
+**Tareas** (`/api/ws/tasks/{id}`):
+
+| Tipo | Cuándo llega |
+|-----------------|------------------------------------|
+| `task.status` | Cambió el estado de la tarea |
+| `task.output` | Apareció nueva salida del comando |
+| `task.complete` | La tarea finalizó |
+
+**Consola** (`/api/ws/servers/{server}/console`):
+
+| Tipo | Dirección | Propósito |
+|-------------------|------------------|--------------------------------------|
+| `console.history` | Desde el panel | Salida acumulada al conectar |
+| `console.command` | Hacia el panel | Envío de un comando al servidor |
+
+**Sesión interactiva** (`/api/ws/servers/{server}/attach`):
+
+| Tipo | Dirección | Propósito |
+|-----------------|---------------|---------------------------------|
+| `attach.input` | Hacia el panel | Entrada en la sesión |
+| `attach.detach` | Hacia el panel | Desconectar sin detener el servidor |
+
+**Métricas**:
+
+| Tipo | Propósito |
+|------------------------|-----------------------------------------------------|
+| `metrics.replay` | Valores acumulados del período de retención |
+| `metrics.replay.done` | Valores acumulados entregados; siguen los valores en vivo |
+| `metrics.error` | Error de recolección de métricas |
+
+Al conectarse a las métricas, el panel primero sirve el historial del período de retención y
+luego envía los nuevos valores a medida que llegan. El cambio se marca con un frame
+`metrics.replay.done`.
+
+> En la consola del servidor de juego, la contraseña RCON está enmascarada: el servidor de
+> juego la muestra como parte de la línea de comandos de inicio.
+
+## Series de métricas
+
+Las métricas son recolectadas por el daemon y pasadas al panel. El intervalo de muestreo y el
+tiempo de retención se configuran en la configuración del daemon con
+`metrics.collection_interval` (5 segundos por defecto) y `metrics.retention_duration` (10
+minutos por defecto); consulte [GameAP Daemon](/es/daemon/daemon.html#recolección-de-métricas).
+
+### Servidor de juego
+
+| Serie | Valor |
+|---------------------------------------------|--------------------------------------------|
+| `gameap_server_up` | Si el servidor está en ejecución |
+| `gameap_server_cpu_usage_percent` | Uso de CPU, porcentaje |
+| `gameap_server_memory_usage_bytes` | Memoria utilizada, bytes |
+| `gameap_server_memory_limit_bytes` | Límite de memoria, bytes |
+| `gameap_server_memory_usage_percent` | Memoria utilizada, porcentaje del límite |
+| `gameap_server_network_receive_bytes_total` | Bytes de red recibidos, acumulativo |
+| `gameap_server_network_transmit_bytes_total`| Bytes de red transmitidos, acumulativo |
+| `gameap_server_block_io_read_bytes_total` | Bytes de disco leídos, acumulativo |
+| `gameap_server_block_io_write_bytes_total` | Bytes de disco escritos, acumulativo |
+| `gameap_server_process_pids` | Número de procesos del servidor |
+
+Las métricas de límite de memoria solo las proporcionan los gestores de procesos que pueden
+aplicarlo: systemd, Docker y Podman.
+
+### Servidor dedicado
+
+| Serie | Valor |
+|-------------------------------------------|------------------------------------------------|
+| `gameap_node_cpu_usage_percent` | Uso de CPU, porcentaje |
+| `gameap_node_memory_usage_bytes` | Memoria utilizada, bytes |
+| `gameap_node_memory_total_bytes` | Memoria total, bytes |
+| `gameap_node_memory_usage_percent` | Memoria utilizada, porcentaje |
+| `gameap_node_swap_usage_bytes` | Swap utilizado, bytes |
+| `gameap_node_swap_total_bytes` | Swap total, bytes |
+| `gameap_node_disk_usage_bytes` | Disco utilizado, bytes |
+| `gameap_node_disk_total_bytes` | Disco total, bytes |
+| `gameap_node_disk_usage_percent` | Disco utilizado, porcentaje |
+| `gameap_node_network_receive_bytes_total` | Bytes de red recibidos, acumulativo |
+| `gameap_node_network_transmit_bytes_total`| Bytes de red transmitidos, acumulativo |
+| `gameap_node_load1` | Carga promedio de 1 minuto |
+| `gameap_node_load5` | Carga promedio de 5 minutos |
+| `gameap_node_load15` | Carga promedio de 15 minutos |
+| `gameap_node_uptime_seconds_total` | Tiempo de actividad, segundos |
+
+La carga promedio no se recolecta en Windows.
+
+Qué interfaces de red y unidades de disco incluir se define con los parámetros `if_list` y
+`drives_list` en la configuración del daemon.
+
+## Limitaciones
+
+Las métricas se mantienen en la RAM del daemon no más de `metrics.retention_duration` (se
+permite de 10 a 60 minutos) y se pierden cuando el daemon se reinicia. El panel no tiene
+almacenamiento a largo plazo ni exportación a sistemas de monitorización.
+
+La recolección de métricas se desactiva con `metrics.enabled: false` en la configuración del
+daemon.
diff --git a/images/flags/spain.png b/images/flags/spain.png
new file mode 100644
index 0000000..16df04f
Binary files /dev/null and b/images/flags/spain.png differ
diff --git a/index.md b/index.md
index 2aa34e8..19331e8 100644
--- a/index.md
+++ b/index.md
@@ -9,5 +9,6 @@ lang: en
Select your language:
-*  [English](/en)
-*  [Russian](/ru)
\ No newline at end of file
+*  [English](/en)
+*  [Russian](/ru)
+*  [Spanish](/es)
diff --git a/ru/daemon/process_managers.md b/ru/daemon/process_managers.md
index f3d2d0c..0b42924 100644
--- a/ru/daemon/process_managers.md
+++ b/ru/daemon/process_managers.md
@@ -24,8 +24,8 @@ process_manager:
```
**Доступные менеджеры:**
-- Linux: `systemd` (по умолчанию), `docker`, `podman`, `tmux`
-- Windows: `shawl` (по умолчанию), `winsw`
+- Linux: `systemd` (по умолчанию), `docker`, `podman`, `tmux`, `simple`
+- Windows: `shawl` (по умолчанию), `winsw`, `simple`
## Linux
@@ -216,3 +216,37 @@ WinSW не требует дополнительной конфигурации.
process_manager:
name: winsw
```
+
+## Linux и Windows
+
+### Simple
+
+Simple не обращается ни к какой системной службе. Он запускает игровой сервер через шаблоны команд,
+заданные для выделенного сервера, и не знает о процессе ничего сверх того, что сообщают эти команды.
+
+На Linux демон переключается на него автоматически, если недоступны ни systemd, ни tmux.
+На Windows его можно выбрать явно.
+
+| Функция | |
+|----------------------------------------|----|
+| Запуск, остановка, перезапуск серверов | ✅ |
+| Статистика | ❌ |
+| Лимиты ресурсов (CPU / RAM) | ❌ |
+| Чтение консоли | ✅ |
+| Отправка команд в консоль | ✅ |
+| Изоляция | ❌ |
+
+Демон не запоминает идентификатор процесса, поэтому в панель попадает только признак «сервер
+запущен» — графиков CPU, памяти и сети нет, лимиты ресурсов применить нельзя. Выбирайте Simple
+только тогда, когда в системе не доступно ничего другого.
+
+#### Конфигурация Simple
+
+Simple не требует дополнительной конфигурации.
+
+##### Пример конфигурации
+
+```yaml
+process_manager:
+ name: simple
+```
diff --git a/ru/gameap_configure/games.md b/ru/gameap_configure/games.md
index edd7b44..fd29d5c 100644
--- a/ru/gameap_configure/games.md
+++ b/ru/gameap_configure/games.md
@@ -107,7 +107,7 @@ RAR архивы не поддерживаются.
У каждой игры может быть множество модификаций, каждая из которых обладает своими особенностями — настройками,
параметрами запуска, конфигурационными файлами и т.п.
-Для добавления новой игры необходимо перейти на страницу **"Администрирование"** → **"Игры"**, затем выбрать
+Для добавления новой модификации необходимо перейти на страницу **"Администрирование"** → **"Игры"**, затем выбрать
**"Добавить модификацию"**
### Описание полей
diff --git a/ru/get_started.md b/ru/get_started.md
index 7dfa45d..c61700f 100644
--- a/ru/get_started.md
+++ b/ru/get_started.md
@@ -29,7 +29,7 @@ bash <(curl -s https://gameap.com/install.sh) --with-daemon
## Добавление выделенного сервера
Добавьте новый выделенный сервер (VDS), на который затем будете устанавливать игровые серверы.
-После установки панели зайдите в неё и в меню выберите **"Администрирование"** **"Выделенные серверы"** → **"Создать"**. После
+После установки панели зайдите в неё и в меню выберите **"Администрирование"** → **"Выделенные серверы"** → **"Создать"**. После
чего откроется окошко с инструкцией, следуйте ей.

diff --git a/ru/https.md b/ru/https.md
index 18ff384..c33f287 100644
--- a/ru/https.md
+++ b/ru/https.md
@@ -208,6 +208,11 @@ ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory
внутри файлового хранилища панели. Если `FILES_DRIVER=s3`, они попадают в S3 — это то, что позволяет
нескольким экземплярам панели использовать один сертификат.
+Одного общего хранилища для нескольких экземпляров мало: блокировка, не дающая им запросить
+сертификат одновременно, работает через Redis и включается только при `CACHE_DRIVER=redis`. При
+кэше в памяти блокировка локальная для каждого экземпляра, и они будут мешать друг другу.
+См. [Несколько экземпляров панели](/ru/multi_instance.html).
+
## Состояние сертификата
Текущее состояние доступно администратору по адресу `GET /api/admin/letsencrypt/status`:
diff --git a/ru/install/install_on_linux.md b/ru/install/install_on_linux.md
index c6dafd9..ce7a6ae 100644
--- a/ru/install/install_on_linux.md
+++ b/ru/install/install_on_linux.md
@@ -74,11 +74,16 @@ bash <(curl -s https://gameap.com/install.sh) \
```shell
bash <(curl -s https://gameap.com/install.sh) \
--non-interactive \
- --host=127.0.0.1 \
+ --host=panel.example.com \
--port=8025 \
--database=sqlite
```
+> В `--host` указывайте адрес, по которому панель будет доступна, — доменное имя или внешний IP
+> сервера. Значение попадает в `HTTP_HOST`, из него панель выводит адрес прослушивания: при
+> `--host=127.0.0.1` слушатели HTTP и gRPC поднимутся только на локальной петле, и подключиться
+> не смогут ни администраторы извне, ни демоны с других машин.
+
Основные флаги:
| Флаг | Назначение |
diff --git a/ru/plugins/development.md b/ru/plugins/development.md
index d1d1444..be57bac 100644
--- a/ru/plugins/development.md
+++ b/ru/plugins/development.md
@@ -47,7 +47,7 @@ order: 342
| `author` | Автор |
| `license` | Лицензия (например, `MIT`) |
| `homepage` | Ссылка на страницу плагина |
-| `required_permissions` | Заявляемые разрешения (в текущей версии панели не проверяются) |
+| `required_permissions` | Разрешения, заявляемые плагином: записываются при установке и проверяются при каждом вызове host-функции (см. ниже) |
| `api_version` | Версия Plugin API, обязательно `"1"` |
**Требования к `id`.** Используйте стабильный идентификатор из символов base32-алфавита `a-z2-7` без дефисов. Панель нормализует id: строка с дефисами или другими символами будет заменена хешем, из-за чего сломаются пути `/api/plugins/{id}/...` и `/plugins/{id}/...`. Избегайте и чисто цифровых id: такой идентификатор трактуется как десятичный числовой ID (разбор id сначала пытается распарсить строку как число). Примеры корректных id реальных плагинов: `hexeditor4jm2`, `ezvdsxmlu6fbk`, `dshdabjp2l73a`.
@@ -100,16 +100,24 @@ Pre-события доставляются синхронно и блокиру
| `gameap-gamemods` | `find_game_mods`, `get_game_mod` | Моды игр |
| `gameap-daemontasks` | `find_daemon_tasks`, `create_daemon_task` | Задачи демона |
| `gameap-serversettings` | `find_server_settings`, `save_server_setting` | Настройки игровых серверов |
-| `gameap-nodefs` | `read_dir`, `mk_dir`, `copy`, `move`, `download`, `upload`, `remove`, `get_file_info`, `chmod` | Файловые операции на ноде |
+| `gameap-nodefs` | `read_dir`, `mk_dir`, `copy`, `move`, `download`, `upload`, `remove`, `get_file_info`, `chmod`, `hash`, `create_archive`, `extract_archive`, `start_create_archive`, `start_extract_archive`, `cancel_archive`, `get_archive_operation` | Файловые операции на ноде |
| `gameap-nodecmd` | `execute_command` | Выполнение команды на ноде |
Особенности:
* `gameap-http` проксирует запросы через панель с защитой от SSRF: по умолчанию разрешена только схема `https`, приватные и служебные IP блокируются, тело ответа ограничено 10 МБ. Политика настраивается переменными окружения `PLUGIN_HTTP_*` (см. [Установка и управление](/ru/plugins/management.html)).
* `gameap-storage` — персистентное хранилище плагина, изолированное по `plugin_id`, с опциональной привязкой записей к сущности (`entity_type`, `entity_id`). Используйте его для настроек плагина: отдельный механизм конфигурации (`config` в `InitializeRequest`) в текущей версии панели не задействован.
-* `gameap-nodefs` и `gameap-nodecmd` — работа с файлами и командами на выделенном сервере (ноде) через GameAP Daemon.
+* `gameap-nodefs` и `gameap-nodecmd` — работа с файлами и командами на выделенном сервере (ноде) через GameAP Daemon. Для чтения (`read_dir`, `download`, `get_file_info`, `hash`, `get_archive_operation`) нужно разрешение `files_read`; всё, что пишет, — включая `chmod` и работу с архивами — требует `files`, которое включает в себя `files_read`.
+* `gameap-nodefs.download` без `offset`/`length` возвращает файл целиком одним сообщением и ограничен `PLUGIN_NODEFS_MAX_INLINE`; файл больше лимита отклоняется с ошибкой, называющей оба размера. Указание окна `offset`/`length` позволяет читать такой файл по частям: в лимит должно укладываться только окно, а `length` больше лимита отклоняется, а не обрезается молча. В ответе повторяется `offset` и приходит `total_size`, а `length: 0` вместе с `offset` читает столько, сколько позволяет лимит, — поэтому цикл постраничного чтения сдвигается на длину `content`, пока не дойдёт до `total_size`. Чтение с позиции в конце файла или за ним не ошибка: в ответе приходит пустой `content`.
-**Важно:** host-функции выполняются с правами самой панели, без дополнительных проверок. Поэтому установка плагинов доверяется только администраторам — устанавливайте плагины только из источников, которым доверяете.
+**Проверки при вызове host-функции.** Host-функции выполняются с правами самой панели, но вызов проходит не бесконтрольно:
+
+* **Разрешения.** Привилегированные модули закрыты разрешениями, записанными для плагина: `files_read`, `files`, `manage_servers`, `node_commands`, `listen_events`, `manage_rbac`, `secrets`, `ssh`, `manage_nodes` (`manage_games`, `manage_game_mods` и `manage_users` зарезервированы под операции записи, которых пока нет). Более широкое разрешение удовлетворяет более узкое: `files` включает `files_read`. Проверка пока переходная — `PLUGIN_PERMISSIONS_ENFORCE` по умолчанию `false`, поэтому разрешения записываются, показываются и редактируются, но все проверки пока проходят; в будущем выпуске значением по умолчанию станет `true`, так что заявляйте нужные плагину разрешения уже сейчас. Изменяющие вызовы `gameap-nodes` проверяют своё разрешение независимо от этой настройки. Отклонённый вызов отвечает `plugin permission required`.
+* **Пути.** Каждый путь, передаваемый в `gameap-nodefs`, `work_dir` для `gameap-nodecmd` и файл ноды, на который ссылается HTTP-ответ, проверяются на панели до обращения к демону. Путь с сегментом `..` или байтом NUL отклоняется всегда. `PLUGIN_NODEFS_PATH_POLICY` дополнительно может ограничить пути каталогом `work_path` ноды или каталогами игровых серверов на ней; в любом ограничивающем режиме остаётся открытым служебный каталог самого плагина — `/.plugins/`, и рабочие файлы на ноде следует держать именно там. Отклонённый вызов отвечает `path policy: <причина>: <путь>`.
+* **Ограничение частоты.** У каждого плагина своя корзина токенов по классам: `gameap-nodefs` — 50 вызовов/с (всплеск 200), `gameap-http` — 20/с (50), `gameap-ssh` — 20/с (60), `gameap-rbac` — 10/с (50), `gameap-nodecmd` и управление серверами — 5/с (20). Отклонённый вызов отвечает `rate limited: ...`; за это плагин никогда не отключается.
+* **Журнал аудита.** Привилегированные операции — управление серверами, команды на ноде, запись файлов, SSH, изменения RBAC — записываются с плагином в роли инициатора, включая отказы.
+
+Установка плагинов при этом по-прежнему доверяется только администраторам — устанавливайте плагины только из источников, которым доверяете.
## Ограничения рантайма
@@ -121,6 +129,7 @@ Pre-события доставляются синхронно и блокиру
| Размер загружаемого `.wasm`-файла | 100 МБ |
| Тело HTTP-запроса к плагину | 1 МБ |
| Тело ответа `gameap-http` | 10 МБ |
+| Одно сообщение `download` / `upload` в `gameap-nodefs` | 32 МБ (`PLUGIN_NODEFS_MAX_INLINE`); `download` с окном измеряется по окну |
При превышении таймаута вызова панель отключает плагин до перезагрузки. Из WASM недоступны файловая система и сеть — только через host-функции.
diff --git a/ru/plugins/frontend.md b/ru/plugins/frontend.md
index 823e477..c85d905 100644
--- a/ru/plugins/frontend.md
+++ b/ru/plugins/frontend.md
@@ -63,7 +63,9 @@ order: 343
Особенности:
* Для `server-tabs` доступна проверка прав `checkPermission: { type: 'hasServerPermissions', permissions: [...] }` — вкладка показывается, только если у пользователя есть все перечисленные права на сервер (плагинные права имеют вид `plugin:{id}:...`, например `plugin:ezvdsxmlu6fbk:manage`).
-* Редакторы файлов регистрируются с match-правилами (`fileName`, `extensions`, `pathContains`, `fullPath`, `gameCode` и др.): редактор с наибольшей специфичностью становится редактором по умолчанию для файла. Файлы больше 1 МБ плагинными редакторами не открываются. Компонент редактора получает props `content`, `filePath`, `fileName`, `extension`, `pluginId`, `gameCode` и `gameName` и эмитит события `save` и `close`; сохранение файла на сервер выполняет сама панель.
+* Редакторы файлов регистрируются с match-правилами (`fileName`, `extensions`, `pathContains`, `fullPath`, `gameCode` и др.): редактор с наибольшей специфичностью становится редактором по умолчанию для файла и открывается по двойному клику. Редактор с `contextMenuOnly: true` редактором по умолчанию не становится никогда и предлагается только в контекстном меню — это нужно редактору, который подходит ко всем файлам (`allFiles: true`), чтобы он не забрал себе все предпросмотры. `menuLabel` заменяет формулировку «Edit with …» у пункта меню, а `checkPermission: { type: 'hasServerPermissions', permissions: [...] }` скрывает пункт от пользователей без перечисленных прав на сервер.
+* Компонент редактора получает props `content`, `filePath`, `fileName`, `extension`, `fileSize`, `fileMtime`, `disk`, `pluginId`, `gameCode` и `gameName` и эмитит события `save` и `close`; сохранение файла на сервер выполняет сама панель.
+* Панель скачивает файл до монтирования редактора и не делает этого для файлов больше 1 МБ. Редактору, объявившему `contentType: 'none'`, prop `content` не передаётся вовсе: он сам загружает то, что ему нужно, поэтому ограничение размера на него не распространяется и так можно открыть файл любого размера; `fileSize` и `fileMtime` приходят из листинга каталога, так что такой редактор может рассказать о файле, не скачивая его. При `contentType: 'text'` (по умолчанию) содержимое приходит строкой, при `'binary'` — `ArrayBuffer`.
## Переводы
diff --git a/ru/sitemap.xml b/ru/sitemap.xml
index d893a02..0989026 100644
--- a/ru/sitemap.xml
+++ b/ru/sitemap.xml
@@ -8,11 +8,12 @@
{%- for p in pages -%}
{%- assign head = p.url | slice: 0, 4 -%}
{%- if head == prefix and p.layout == 'default' and p.hidden != true -%}
-{%- assign clean_path = p.url | replace: '/ru/', '/' | replace: '/en/', '/' %}
+{%- assign clean_path = p.url | replace: '/ru/', '/' | replace: '/en/', '/' | replace: '/es/', '/' %}
{{ site.domains.ru }}{{ clean_path }}
+
{%- endif -%}
diff --git a/ru/tutorials/hytale.md b/ru/tutorials/hytale.md
index 599711d..29603da 100644
--- a/ru/tutorials/hytale.md
+++ b/ru/tutorials/hytale.md
@@ -40,6 +40,10 @@ GameAP Daemon является агентом, отвечающим за упр
использовать предложенный.
* Введите порт игрового сервера, по умолчанию 5520. Вводить rcon и query порт не нужно.
+> Hytale работает по QUIC, то есть по **UDP**. Открывайте и пробрасывайте выбранный игровой порт
+> как UDP в файрволе и на NAT — при пробросе только TCP сервер запустится, но клиенты к нему не
+> подключатся.
+
### Настройка после первого запуска
После первого запуска вам необходимо выполнить несколько шагов для авторизации.
diff --git a/ru/tutorials/rust.md b/ru/tutorials/rust.md
index 414639f..dc234da 100644
--- a/ru/tutorials/rust.md
+++ b/ru/tutorials/rust.md
@@ -73,6 +73,7 @@ GameAP обеспечивает полную поддержку игрового
Карта на сервере. По умолчанию процедурно генерируемая карта Procedural Map
Возможные значения:
+* Procedural Map
* Barren
* Craggy Island
* Hapis