<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Blog de GameAP</title><link>https://blog.gameap.com/es/</link><description>Últimas publicaciones del blog de GameAP</description><language>es-ES</language><generator>Hugo 0.164.0</generator><atom:link href="https://blog.gameap.com/es/index.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Sat, 08 Aug 2026 13:00:00 +0300</lastBuildDate><item><title>¿Cómo publicar su plugin?</title><link>https://blog.gameap.com/es/guides/publishing-plugins/</link><pubDate>Sat, 08 Aug 2026 13:00:00 +0300</pubDate><guid>https://blog.gameap.com/es/guides/publishing-plugins/</guid><description>Publicación de sus plugins de GameAP en plugins.gameap.dev: creación del plugin, subida de versiones desde el dashboard y desde CI, moderación.</description><content:encoded>&lt;p&gt;Los plugins de GameAP se publican en &lt;a href="https://plugins.gameap.dev" target="_blank" rel="noopener"&gt;plugins.gameap.dev&lt;/a&gt; — desde ahí
los usuarios los instalan directamente desde el panel. Esta guía recorre todo el camino: desde la
creación de la ficha del plugin hasta la publicación automática de nuevas versiones desde CI.&lt;/p&gt;
&lt;p&gt;Se presupone que su plugin ya está escrito y se compila en un &lt;code&gt;.wasm&lt;/code&gt;. También necesitará una
cuenta en plugins.gameap.dev: un registro normal con confirmación del correo electrónico, o inicio
de sesión a través de GitHub, GitLab o Google.&lt;/p&gt;
&lt;p&gt;Hay dos formas de subir una versión: manualmente a través del dashboard y a través de la API desde
CI. Es más fácil empezar con el dashboard y, cuando la compilación se estabilice, pasar a CI.&lt;/p&gt;
&lt;h2 id="paso-1-cuenta-de-desarrollador"&gt;Paso 1. Cuenta de desarrollador &lt;a class="hanchor" href="#paso-1-cuenta-de-desarrollador" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Publicar plugins requiere una cuenta con estado de desarrollador. Se concede gratis e
inmediatamente — basta con aceptar las reglas de publicación.&lt;/p&gt;
&lt;p&gt;En el catálogo de plugins pulse &lt;strong&gt;Publish your plugin&lt;/strong&gt; — el botón de la esquina superior derecha.&lt;/p&gt;
&lt;figure&gt;&lt;a class="zoom" href="https://blog.gameap.com/es/guides/publishing-plugins/publish-plugins.es.png" target="_blank" rel="noopener"&gt;&lt;picture&gt;
 &lt;source type="image/webp" srcset="https://blog.gameap.com/es/guides/publishing-plugins/publish-plugins.es_hu_5159adbc8240679d.webp 480w, https://blog.gameap.com/es/guides/publishing-plugins/publish-plugins.es_hu_e87690acd34e62fd.webp 800w, https://blog.gameap.com/es/guides/publishing-plugins/publish-plugins.es_hu_d5e0ccee4c05e9e0.webp 1200w" sizes="(min-width: 860px) 808px, 100vw"&gt;
 &lt;img src="https://blog.gameap.com/es/guides/publishing-plugins/publish-plugins.es_hu_948bf2e9ae7b63c.png" srcset="https://blog.gameap.com/es/guides/publishing-plugins/publish-plugins.es_hu_2603f5b6f5388b0d.png 480w, https://blog.gameap.com/es/guides/publishing-plugins/publish-plugins.es_hu_1ef640ba0eb0d06.png 800w, https://blog.gameap.com/es/guides/publishing-plugins/publish-plugins.es_hu_948bf2e9ae7b63c.png 1200w" sizes="(min-width: 860px) 808px, 100vw" width="1200" height="756" alt="El botón «Publish your plugin» en el catálogo de plugins" loading="lazy" decoding="async"&gt;
 &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;El catálogo de plugins&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Se abrirá la página con las reglas de publicación. En resumen, lo que usted acepta:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Plugins funcionales y de calidad&lt;/strong&gt; — solo plugins operativos y probados, que hagan lo que
promete su descripción.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nada de código malicioso&lt;/strong&gt; — sin puertas traseras, mineros ocultos ni lógica ofuscada;
cualquier recopilación de datos de los usuarios debe describirse explícitamente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Derechos y licencias&lt;/strong&gt; — usted es propietario del código o está autorizado a distribuirlo,
las licencias de terceros se respetan y la licencia del plugin está indicada.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compatibilidad con GameAP&lt;/strong&gt; — el plugin funciona con las versiones actuales del panel y cumple
los requisitos del formato.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actualizaciones y soporte&lt;/strong&gt; — los errores críticos y las vulnerabilidades se corrigen en plazos
razonables, y los reportes de los usuarios reciben respuesta.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Moderación&lt;/strong&gt; — cada plugin y cada versión pasan una revisión, y un plugin que infrinja las
reglas puede ser retirado en cualquier momento.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sin spam ni duplicados&lt;/strong&gt; — sin nombres engañosos ni relleno de palabras clave.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El texto completo está en la propia página, y vale la pena leerlo entero.&lt;/p&gt;
&lt;!-- captura de pantalla: la página de reglas de publicación con el botón "Agree" → publish-rules.ru.png / publish-rules.en.png --&gt;
&lt;p&gt;Pulse &lt;strong&gt;Agree&lt;/strong&gt; al final de la página. El rol se concede al instante, sin revisión ni espera: en la
barra lateral aparecerá la sección &lt;strong&gt;Developer&lt;/strong&gt; con los apartados Dashboard, My Plugins, New
Plugin y Support, y llegará un correo informándole de que ya es desarrollador.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Importante.&lt;/strong&gt; Las reglas solo pueden aceptarse con un correo electrónico verificado: de lo
contrario, el botón &lt;strong&gt;Agree&lt;/strong&gt; permanece inactivo y la página muestra un recordatorio para
confirmar la dirección. Las cuentas creadas a través de GitHub, GitLab o Google se consideran
verificadas de inmediato.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;El botón &lt;strong&gt;Publish your plugin&lt;/strong&gt; es visible para todos, incluidos los visitantes que no han iniciado
sesión. Sin sesión, la página de reglas muestra &lt;strong&gt;Log in to agree&lt;/strong&gt; — al identificarse volverá a
las reglas.&lt;/p&gt;
&lt;h2 id="paso-2-creación-del-plugin"&gt;Paso 2. Creación del plugin &lt;a class="hanchor" href="#paso-2-creaci%c3%b3n-del-plugin" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Seleccione &lt;strong&gt;New Plugin&lt;/strong&gt; en el menú de la izquierda — se abrirá el formulario de creación.&lt;/p&gt;
&lt;figure&gt;&lt;a class="zoom" href="https://blog.gameap.com/es/guides/publishing-plugins/new-plugin.es.png" target="_blank" rel="noopener"&gt;&lt;picture&gt;
 &lt;source type="image/webp" srcset="https://blog.gameap.com/es/guides/publishing-plugins/new-plugin.es_hu_66c90ae02a704edc.webp 480w, https://blog.gameap.com/es/guides/publishing-plugins/new-plugin.es_hu_ed73830ca4a7af69.webp 800w, https://blog.gameap.com/es/guides/publishing-plugins/new-plugin.es_hu_5b9135d573b083bb.webp 1200w" sizes="(min-width: 860px) 808px, 100vw"&gt;
 &lt;img src="https://blog.gameap.com/es/guides/publishing-plugins/new-plugin.es_hu_ed7a4092b897f55.png" srcset="https://blog.gameap.com/es/guides/publishing-plugins/new-plugin.es_hu_df39b9d938ca9d05.png 480w, https://blog.gameap.com/es/guides/publishing-plugins/new-plugin.es_hu_2ce3df4183fea9ad.png 800w, https://blog.gameap.com/es/guides/publishing-plugins/new-plugin.es_hu_ed7a4092b897f55.png 1200w" sizes="(min-width: 860px) 808px, 100vw" width="1200" height="662" alt="El formulario de creación de un plugin en el dashboard de plugins.gameap.dev" loading="lazy" decoding="async"&gt;
 &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;Creación de un nuevo plugin&lt;/figcaption&gt;&lt;/figure&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Campo&lt;/th&gt;
					&lt;th&gt;Obligatorio&lt;/th&gt;
					&lt;th&gt;Qué indicar&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Name&lt;/td&gt;
					&lt;td&gt;sí&lt;/td&gt;
					&lt;td&gt;Hasta 255 caracteres&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Summary&lt;/td&gt;
					&lt;td&gt;sí&lt;/td&gt;
					&lt;td&gt;Una frase para la tarjeta del catálogo, hasta 500 caracteres&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Description&lt;/td&gt;
					&lt;td&gt;sí&lt;/td&gt;
					&lt;td&gt;Descripción completa en Markdown — el campo tiene una barra de formato&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Category&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Una de las tres siguientes&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Labels&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Palabras clave que facilitan encontrar el plugin&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;License&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Texto libre: &lt;code&gt;MIT&lt;/code&gt;, &lt;code&gt;GPL-3.0&lt;/code&gt;, &lt;code&gt;Proprietary&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Source URL&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Enlace a las fuentes, si están abiertas&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Repository URL&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Enlace al repositorio&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Homepage URL&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;El sitio web del plugin o del autor&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Las categorías:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Server Management&lt;/strong&gt; — la mayoría de los plugins. Todo lo que añade acciones sobre un servidor
de juegos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Files&lt;/strong&gt; — extensiones del gestor de archivos: editores, visores de formatos concretos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrations&lt;/strong&gt; — plugins que amplían sustancialmente lo que el panel puede hacer, por ejemplo
la gestión de bases de datos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El icono no se puede subir en este paso — el formulario lo advierte explícitamente; el control de
subida aparece justo después de la creación.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Importante.&lt;/strong&gt; Source URL y Repository URL afectan a algo más que la ficha. Si ninguno de los
dos está relleno, &lt;strong&gt;cada&lt;/strong&gt; versión tendrá que incluir un archivo con el código fuente — los
moderadores necesitan algo con lo que verificar la compilación. Rellene al menos uno de ellos si
sus fuentes son públicas.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="paso-3-el-id-del-plugin"&gt;Paso 3. El ID del plugin &lt;a class="hanchor" href="#paso-3-el-id-del-plugin" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tras pulsar &lt;strong&gt;Create&lt;/strong&gt; llegará a la página de edición del plugin. Su primer bloque es el
&lt;strong&gt;Plugin ID&lt;/strong&gt; — un campo de solo lectura con un botón para copiar.&lt;/p&gt;
&lt;!-- captura de pantalla: parte superior de la página de edición con el campo "Plugin ID" y su botón de copiar → plugin-id.ru.png / plugin-id.en.png --&gt;
&lt;p&gt;Este ID hay que escribirlo en el código fuente del plugin — en el campo &lt;code&gt;id&lt;/code&gt; de la estructura
&lt;code&gt;PluginInfo&lt;/code&gt; que devuelve el método &lt;code&gt;GetInfo&lt;/code&gt;. Así es como el panel asocia un plugin instalado por
el usuario con su entrada en el marketplace.&lt;/p&gt;
&lt;p&gt;El ID se genera una sola vez al crear el plugin y &lt;strong&gt;no puede cambiarse&lt;/strong&gt;. Internamente es un número
de 64 bits (8 bytes aleatorios) escrito en base32 — 13 caracteres latinos y dígitos, por ejemplo
&lt;code&gt;fmqnme42gg7da&lt;/code&gt;. El mismo ID aparece en la URL de la página del plugin y en la dirección del
endpoint de CI.&lt;/p&gt;
&lt;p&gt;Cómo se ve en los plugins existentes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/gameap/plugin-mysql/blob/main/src/lib.rs" target="_blank" rel="noopener"&gt;plugin-mysql&lt;/a&gt; — la constante &lt;code&gt;PLUGIN_ID&lt;/code&gt; (&lt;code&gt;fmqnme42gg7da&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gameap/plugin-goldsrc-addons/blob/main/src/lib.rs" target="_blank" rel="noopener"&gt;plugin-goldsrc-addons&lt;/a&gt; — &lt;code&gt;PLUGIN_ID&lt;/code&gt; (&lt;code&gt;ezvdsxmlu6fbk&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gameap/plugin-minecraft-modrinth/blob/main/assembly/constants.ts" target="_blank" rel="noopener"&gt;plugin-minecraft-modrinth&lt;/a&gt; — &lt;code&gt;PLUGIN_ID&lt;/code&gt; (&lt;code&gt;dshdabjp2l73a&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Importante.&lt;/strong&gt; Escriba el ID y recompile el &lt;code&gt;.wasm&lt;/code&gt; antes de subir la primera versión. Las
versiones son inmutables: no se puede volver a subir un archivo con el mismo número, habría que
incrementar la versión.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="paso-4-icono-y-traducciones"&gt;Paso 4. Icono y traducciones &lt;a class="hanchor" href="#paso-4-icono-y-traducciones" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En esa misma página de edición se puede subir un icono: JPG, PNG, WebP o SVG, hasta 2 MB; se
recomienda 128×128.&lt;/p&gt;
&lt;!-- captura de pantalla: el control de subida del icono en la página de edición → icon-upload.ru.png / icon-upload.en.png --&gt;
&lt;p&gt;La página del plugin tiene un bloque &lt;strong&gt;Translations&lt;/strong&gt;. El catálogo es bilingüe y cada visitante ve
la ficha en su propio idioma: sin traducción, un usuario ruso recibirá la descripción en inglés y
viceversa. El nombre, el resumen y la descripción se traducen por separado para cada idioma —
cinco minutos de trabajo que cambian notablemente el aspecto de la ficha.&lt;/p&gt;
&lt;!-- captura de pantalla: el bloque "Translations" en la página del plugin → translations.ru.png / translations.en.png --&gt;
&lt;h2 id="paso-5-subida-de-una-versión"&gt;Paso 5. Subida de una versión &lt;a class="hanchor" href="#paso-5-subida-de-una-versi%c3%b3n" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En la página del plugin busque la tarjeta &lt;strong&gt;Versions&lt;/strong&gt; y pulse &lt;strong&gt;Upload Version&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;a class="zoom" href="https://blog.gameap.com/es/guides/publishing-plugins/upload-version.es.png" target="_blank" rel="noopener"&gt;&lt;picture&gt;
 &lt;source type="image/webp" srcset="https://blog.gameap.com/es/guides/publishing-plugins/upload-version.es_hu_243bccfe940fb645.webp 480w, https://blog.gameap.com/es/guides/publishing-plugins/upload-version.es_hu_aa693942bf347d41.webp 800w, https://blog.gameap.com/es/guides/publishing-plugins/upload-version.es_hu_1f9f52625438df09.webp 1200w" sizes="(min-width: 860px) 808px, 100vw"&gt;
 &lt;img src="https://blog.gameap.com/es/guides/publishing-plugins/upload-version.es_hu_a143f910d4449488.png" srcset="https://blog.gameap.com/es/guides/publishing-plugins/upload-version.es_hu_fb2b83c72ead6ea2.png 480w, https://blog.gameap.com/es/guides/publishing-plugins/upload-version.es_hu_9724bcb241379e5.png 800w, https://blog.gameap.com/es/guides/publishing-plugins/upload-version.es_hu_a143f910d4449488.png 1200w" sizes="(min-width: 860px) 808px, 100vw" width="1200" height="611" alt="El formulario de subida de una versión del plugin" loading="lazy" decoding="async"&gt;
 &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;Subida de una nueva versión&lt;/figcaption&gt;&lt;/figure&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Campo&lt;/th&gt;
					&lt;th&gt;Obligatorio&lt;/th&gt;
					&lt;th&gt;Qué indicar&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Version&lt;/td&gt;
					&lt;td&gt;sí&lt;/td&gt;
					&lt;td&gt;Versión semántica: &lt;code&gt;1.0.0&lt;/code&gt;, &lt;code&gt;1.2.0-beta.1&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Plugin File&lt;/td&gt;
					&lt;td&gt;sí&lt;/td&gt;
					&lt;td&gt;El &lt;code&gt;.wasm&lt;/code&gt; compilado, hasta 100 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GPG Signature&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Firma separada &lt;code&gt;.sig&lt;/code&gt; o &lt;code&gt;.asc&lt;/code&gt;, hasta 1 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Source Code Archive&lt;/td&gt;
					&lt;td&gt;depende&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;.zip&lt;/code&gt; o &lt;code&gt;.tar.gz&lt;/code&gt;, hasta 50 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Changelog&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Qué cambió respecto a la versión anterior, en Markdown&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Stable Release&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;Un interruptor, &lt;strong&gt;activado por defecto&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Min GameAP Version&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;La versión del panel a partir de la cual funciona el plugin&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;La &lt;strong&gt;firma GPG&lt;/strong&gt; es opcional pero bienvenida: el servidor la almacena tal cual y nunca la verifica —
existe para que los usuarios puedan comprobar por sí mismos la autenticidad del archivo.&lt;/p&gt;
&lt;p&gt;El &lt;strong&gt;archivo de código fuente&lt;/strong&gt; es obligatorio cuando el plugin no tiene rellenos ni Source URL ni
Repository URL — el formulario cambia por sí solo la etiqueta del campo de «(Optional)» a
«(Required)». El archivo se usa solo para la moderación y la verificación de la compilación; nunca
se publica ni es accesible a través de ningún endpoint público.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Importante.&lt;/strong&gt; Una versión subida a través del dashboard permanece como &lt;strong&gt;borrador&lt;/strong&gt;. Hasta que
no la envíe a revisión, nadie la ve.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="capturas-de-pantalla-de-la-versión"&gt;Capturas de pantalla de la versión &lt;a class="hanchor" href="#capturas-de-pantalla-de-la-versi%c3%b3n" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Justo después de la subida se abre un segundo paso — &lt;strong&gt;Add Screenshots&lt;/strong&gt;. Las capturas pertenecen a
una versión concreta: JPG, PNG o WebP, hasta 10, de 5 MB cada una. El paso se puede omitir y
retomar más tarde desde la página de la versión.&lt;/p&gt;
&lt;h2 id="paso-6-envío-a-revisión"&gt;Paso 6. Envío a revisión &lt;a class="hanchor" href="#paso-6-env%c3%ado-a-revisi%c3%b3n" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Los plugins pasan por moderación — conforme a las mismas reglas de publicación que aceptó en el
paso 1. La cabecera de la página del plugin tiene un botón &lt;strong&gt;Submit for Review&lt;/strong&gt; — solo es visible
mientras el plugin está en estado Draft o Rejected.&lt;/p&gt;
&lt;!-- captura de pantalla: cabecera de la página del plugin con el botón "Submit for Review" y la tabla de versiones con insignias de estado → submit-review.ru.png / submit-review.en.png --&gt;
&lt;p&gt;Un plugin sin ninguna versión no puede enviarse — suba primero una versión. Enviar una &lt;em&gt;versión&lt;/em&gt; a
revisión desde la tabla de versiones también mueve el propio plugin de Draft a Pending Review, así
que normalmente no hace falta pulsar el botón de la cabecera por separado.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Estado&lt;/th&gt;
					&lt;th&gt;Qué significa&lt;/th&gt;
					&lt;th&gt;Qué se puede hacer&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Draft&lt;/td&gt;
					&lt;td&gt;Creado, no enviado a ninguna parte&lt;/td&gt;
					&lt;td&gt;Editar, enviar a revisión, eliminar&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pending Review&lt;/td&gt;
					&lt;td&gt;Esperando a un moderador&lt;/td&gt;
					&lt;td&gt;Esperar&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Rejected&lt;/td&gt;
					&lt;td&gt;Un moderador lo devolvió&lt;/td&gt;
					&lt;td&gt;Corregirlo y enviarlo de nuevo&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Released&lt;/td&gt;
					&lt;td&gt;Disponible para todos en el catálogo&lt;/td&gt;
					&lt;td&gt;Subir nuevas versiones&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Approved&lt;/td&gt;
					&lt;td&gt;Un estado intermedio en el modelo de datos&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Deprecated&lt;/td&gt;
					&lt;td&gt;Marcado como obsoleto&lt;/td&gt;
					&lt;td&gt;Aún instalable, pero señalizado&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Retracted&lt;/td&gt;
					&lt;td&gt;Retirado de la publicación&lt;/td&gt;
					&lt;td&gt;No disponible para instalar&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;La aprobación mueve el plugin y la versión directamente a &lt;strong&gt;Released&lt;/strong&gt;. Solo los objetos en ese
estado son visibles públicamente: si un plugin está publicado pero su única versión sigue pendiente
de revisión, no hay nada que instalar.&lt;/p&gt;
&lt;p&gt;El motivo del rechazo llega por correo electrónico — la página del plugin solo muestra el estado,
así que revise su bandeja de entrada.&lt;/p&gt;
&lt;h2 id="tokens-de-despliegue"&gt;Tokens de despliegue &lt;a class="hanchor" href="#tokens-de-despliegue" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para publicar versiones desde CI se necesita un token de despliegue. Está al final de la página del
plugin, en la tarjeta &lt;strong&gt;Deploy Tokens&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;a class="zoom" href="https://blog.gameap.com/es/guides/publishing-plugins/token-deploy.es.png" target="_blank" rel="noopener"&gt;&lt;picture&gt;
 &lt;source type="image/webp" srcset="https://blog.gameap.com/es/guides/publishing-plugins/token-deploy.es_hu_197cb25595fb5b5f.webp 480w, https://blog.gameap.com/es/guides/publishing-plugins/token-deploy.es_hu_a9f5978f4b4c86b9.webp 800w, https://blog.gameap.com/es/guides/publishing-plugins/token-deploy.es_hu_1699d5db1124bb17.webp 1200w" sizes="(min-width: 860px) 808px, 100vw"&gt;
 &lt;img src="https://blog.gameap.com/es/guides/publishing-plugins/token-deploy.es_hu_4faa04302751dca0.png" srcset="https://blog.gameap.com/es/guides/publishing-plugins/token-deploy.es_hu_dbe4db4c7c810656.png 480w, https://blog.gameap.com/es/guides/publishing-plugins/token-deploy.es_hu_beffba6e035fdfb3.png 800w, https://blog.gameap.com/es/guides/publishing-plugins/token-deploy.es_hu_4faa04302751dca0.png 1200w" sizes="(min-width: 860px) 808px, 100vw" width="1200" height="611" alt="La tarjeta «Deploy Tokens» al final de la página del plugin" loading="lazy" decoding="async"&gt;
 &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;La tarjeta de tokens de despliegue&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Pulse &lt;strong&gt;Create Token&lt;/strong&gt; y rellene el diálogo: un nombre (&lt;code&gt;GitHub Actions&lt;/code&gt;, por ejemplo) y,
opcionalmente, una fecha de caducidad.&lt;/p&gt;
&lt;figure&gt;&lt;a class="zoom" href="https://blog.gameap.com/es/guides/publishing-plugins/token-deploy-create-form.es.png" target="_blank" rel="noopener"&gt;&lt;picture&gt;
 &lt;source type="image/webp" srcset="https://blog.gameap.com/es/guides/publishing-plugins/token-deploy-create-form.es_hu_9247ea9ef3fc28dc.webp 480w, https://blog.gameap.com/es/guides/publishing-plugins/token-deploy-create-form.es_hu_bc8c408f37c39612.webp 800w" sizes="(min-width: 860px) 808px, 100vw"&gt;
 &lt;img src="https://blog.gameap.com/es/guides/publishing-plugins/token-deploy-create-form.es_hu_c4886a088e2fce6d.png" srcset="https://blog.gameap.com/es/guides/publishing-plugins/token-deploy-create-form.es_hu_e53a113cd49f24d.png 480w, https://blog.gameap.com/es/guides/publishing-plugins/token-deploy-create-form.es_hu_c4886a088e2fce6d.png 800w" sizes="(min-width: 860px) 808px, 100vw" width="800" height="514" alt="El diálogo de creación del token de despliegue con los campos “Name” y “Expires At”" loading="lazy" decoding="async"&gt;
 &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;Creación de un token de despliegue&lt;/figcaption&gt;&lt;/figure&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Importante.&lt;/strong&gt; El token se muestra exactamente una vez, justo después de su creación. En el
servidor solo se guarda su hash y el valor no se puede recuperar — cópielo directamente en los
secretos de su CI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Lo que conviene saber sobre los tokens:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un token está vinculado a un solo plugin y solo puede subir versiones de ese plugin. No abre ni
otros plugins ni el resto de la API.&lt;/li&gt;
&lt;li&gt;El formato es &lt;code&gt;gapd_&lt;/code&gt; más 43 caracteres, 48 en total. El prefijo lo detectan fácilmente los
escáneres de secretos.&lt;/li&gt;
&lt;li&gt;La tabla muestra el prefijo del token, la fecha de creación, el último uso y la caducidad —
práctico para averiguar qué token sigue en uso.&lt;/li&gt;
&lt;li&gt;La revocación es inmediata: un pipeline con un token revocado fallará en su próxima ejecución.&lt;/li&gt;
&lt;li&gt;Un mismo plugin puede tener hasta 20 tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="publicación-desde-ci"&gt;Publicación desde CI &lt;a class="hanchor" href="#publicaci%c3%b3n-desde-ci" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El endpoint de CI acepta una nueva versión sin inicio de sesión interactivo — se autentica con un
token de despliegue.&lt;/p&gt;
&lt;h3 id="subida-con-curl"&gt;Subida con curl &lt;a class="hanchor" href="#subida-con-curl" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl --fail-with-body -sS &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$GAMEAP_DEPLOY_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -F &lt;span class="s2"&gt;&amp;#34;version=1.2.3&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -F &lt;span class="s2"&gt;&amp;#34;file=@build/plugin.wasm&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -F &lt;span class="s2"&gt;&amp;#34;signature=@build/plugin.wasm.asc&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -F &lt;span class="s2"&gt;&amp;#34;changelog=Corregido un fallo al reiniciar el servidor&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -F &lt;span class="s2"&gt;&amp;#34;min_gameap_version=4.1.0&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -F &lt;span class="s2"&gt;&amp;#34;is_stable=true&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;https://plugins.gameap.dev/api/ci/plugins/&lt;/span&gt;&lt;span class="nv"&gt;$GAMEAP_PLUGIN_ID&lt;/span&gt;&lt;span class="s2"&gt;/versions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;La petición es un &lt;code&gt;POST&lt;/code&gt; con &lt;code&gt;multipart/form-data&lt;/code&gt;. Los campos del formulario:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Campo&lt;/th&gt;
					&lt;th&gt;Obligatorio&lt;/th&gt;
					&lt;th&gt;Descripción&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;version&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;sí&lt;/td&gt;
					&lt;td&gt;Versión semántica, p. ej. &lt;code&gt;1.2.3&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;file&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;sí&lt;/td&gt;
					&lt;td&gt;El &lt;code&gt;.wasm&lt;/code&gt; compilado, hasta 100 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;signature&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Firma GPG separada, hasta 1 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;source&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;depende&lt;/td&gt;
					&lt;td&gt;Archivo de fuentes &lt;code&gt;.zip&lt;/code&gt;/&lt;code&gt;.tar.gz&lt;/code&gt; hasta 50 MB; obligatorio cuando el plugin no tiene ni Source URL ni Repository URL&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;changelog&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Notas de la versión; cómodo pasarlas desde un archivo: &lt;code&gt;-F &amp;quot;changelog=&amp;lt;CHANGELOG.md&amp;quot;&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;min_gameap_version&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Versión mínima de GameAP&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;min_plugin_api_version&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Versión mínima del Plugin API (el formulario del dashboard no tiene este campo)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;is_stable&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;true&lt;/code&gt; o &lt;code&gt;1&lt;/code&gt; marca la versión como estable&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;submit&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;no&lt;/td&gt;
					&lt;td&gt;Por defecto &lt;code&gt;true&lt;/code&gt;; &lt;code&gt;false&lt;/code&gt; mantiene la versión como borrador&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;No ponga una barra final en la URL.&lt;/strong&gt; El router responde a &lt;code&gt;.../versions/&lt;/code&gt; con un &lt;code&gt;301&lt;/code&gt;, y curl
no reenvía el cuerpo del &lt;code&gt;POST&lt;/code&gt; en una redirección — la subida se convertiría silenciosamente en un
&lt;code&gt;GET&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Una respuesta exitosa es un &lt;code&gt;201&lt;/code&gt; con un cuerpo como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;plugin_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;fmqnme42gg7da&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.2.3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;file_size&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1048576&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;file_hash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;9f86d081884c7d65...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;has_source&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pending_review&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="github-actions"&gt;GitHub Actions &lt;a class="hanchor" href="#github-actions" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Este workflow se dispara cuando se publica un release de GitHub, compila el plugin, lo firma y lo
envía a plugins.gameap.dev. El cuerpo del release se convierte en el changelog, y la versión se
marca como estable solo cuando el release no es un pre-release.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Publish plugin&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;published]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build plugin&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;make wasm &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# debe producir build/plugin.wasm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Sign plugin&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GPG_SIGNING_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.GPG_SIGNING_KEY }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; printf &amp;#39;%s&amp;#39; &amp;#34;$GPG_SIGNING_KEY&amp;#34; | gpg --batch --import
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; gpg --batch --yes --detach-sign --armor \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -o build/plugin.wasm.asc build/plugin.wasm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Publish to plugins.gameap.dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GAMEAP_DEPLOY_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.GAMEAP_DEPLOY_TOKEN }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GAMEAP_PLUGIN_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ vars.GAMEAP_PLUGIN_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ github.event.release.tag_name }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;RELEASE_BODY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ github.event.release.body }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;PRERELEASE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ github.event.release.prerelease }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; VERSION=&amp;#34;${TAG#v}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; printf &amp;#39;%s&amp;#39; &amp;#34;$RELEASE_BODY&amp;#34; &amp;gt; &amp;#34;$RUNNER_TEMP/changelog.md&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ARGS=(--fail-with-body -sS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -H &amp;#34;Authorization: Bearer ${GAMEAP_DEPLOY_TOKEN}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -F &amp;#34;version=${VERSION}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -F &amp;#34;file=@build/plugin.wasm&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -F &amp;#34;signature=@build/plugin.wasm.asc&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -F &amp;#34;changelog=&amp;lt;${RUNNER_TEMP}/changelog.md&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; if [ &amp;#34;$PRERELEASE&amp;#34; != &amp;#34;true&amp;#34; ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ARGS+=(-F &amp;#34;is_stable=true&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl &amp;#34;${ARGS[@]}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;https://plugins.gameap.dev/api/ci/plugins/${GAMEAP_PLUGIN_ID}/versions&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;VERSION=&amp;quot;${TAG#v}&amp;quot;&lt;/code&gt; elimina la &lt;code&gt;v&lt;/code&gt; inicial para que el tag &lt;code&gt;v1.2.3&lt;/code&gt; se publique como versión
&lt;code&gt;1.2.3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La versión de producción de este workflow, con compilación del frontend, caché de Cargo y
comprobación del estado HTTP, está en &lt;a href="https://github.com/gameap/plugin-mysql/blob/main/.github/workflows/release.yml" target="_blank" rel="noopener"&gt;plugin-mysql&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="configuración-del-repositorio-de-github"&gt;Configuración del repositorio de GitHub &lt;a class="hanchor" href="#configuraci%c3%b3n-del-repositorio-de-github" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;El token y el ID del plugin no deben estar en el código — pertenecen a la configuración del
repositorio. Abra &lt;strong&gt;Settings → Secrets and variables (en la sección Security and quality) →
Actions&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;a class="zoom" href="https://blog.gameap.com/es/guides/publishing-plugins/github-secrets-sidebar.en.png" target="_blank" rel="noopener"&gt;&lt;picture&gt;
 &lt;source type="image/webp" srcset="https://blog.gameap.com/es/guides/publishing-plugins/github-secrets-sidebar.en_hu_9541da5da7d0384f.webp 480w, https://blog.gameap.com/es/guides/publishing-plugins/github-secrets-sidebar.en_hu_d3ee64d4baa6a46f.webp 800w" sizes="(min-width: 860px) 808px, 100vw"&gt;
 &lt;img src="https://blog.gameap.com/es/guides/publishing-plugins/github-secrets-sidebar.en_hu_9963f0bb4b582211.png" srcset="https://blog.gameap.com/es/guides/publishing-plugins/github-secrets-sidebar.en_hu_a2eced0c8f3ab9c4.png 480w, https://blog.gameap.com/es/guides/publishing-plugins/github-secrets-sidebar.en_hu_9963f0bb4b582211.png 800w" sizes="(min-width: 860px) 808px, 100vw" width="800" height="844" alt="El elemento «Secrets and variables → Actions» en el menú lateral de la configuración del repositorio de GitHub" loading="lazy" decoding="async"&gt;
 &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;La sección de configuración del repositorio&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;En la pestaña &lt;strong&gt;Secrets&lt;/strong&gt; pulse &lt;strong&gt;New repository secret&lt;/strong&gt; y añada:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Nombre&lt;/th&gt;
					&lt;th&gt;Valor&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;GAMEAP_DEPLOY_TOKEN&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;El token de despliegue que copió al crearlo (empieza por &lt;code&gt;gapd_&lt;/code&gt;)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;GPG_SIGNING_KEY&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Una clave privada GPG en formato ASCII-armor, si firma sus compilaciones&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;figure&gt;&lt;a class="zoom" href="https://blog.gameap.com/es/guides/publishing-plugins/github-secrets.en.png" target="_blank" rel="noopener"&gt;&lt;picture&gt;
 &lt;source type="image/webp" srcset="https://blog.gameap.com/es/guides/publishing-plugins/github-secrets.en_hu_dff430a18e15af5b.webp 480w, https://blog.gameap.com/es/guides/publishing-plugins/github-secrets.en_hu_531cc5e7a73438e1.webp 800w, https://blog.gameap.com/es/guides/publishing-plugins/github-secrets.en_hu_6fdea19099fffc55.webp 1200w" sizes="(min-width: 860px) 808px, 100vw"&gt;
 &lt;img src="https://blog.gameap.com/es/guides/publishing-plugins/github-secrets.en_hu_58a93b3c3b3364b1.png" srcset="https://blog.gameap.com/es/guides/publishing-plugins/github-secrets.en_hu_88a534958aec6989.png 480w, https://blog.gameap.com/es/guides/publishing-plugins/github-secrets.en_hu_8367254b45013968.png 800w, https://blog.gameap.com/es/guides/publishing-plugins/github-secrets.en_hu_58a93b3c3b3364b1.png 1200w" sizes="(min-width: 860px) 808px, 100vw" width="1200" height="575" alt="La pestaña Secrets con los secretos GAMEAP_DEPLOY_TOKEN y GPG_SIGNING_KEY" loading="lazy" decoding="async"&gt;
 &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;Secretos del repositorio&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;En la pestaña &lt;strong&gt;Variables&lt;/strong&gt; pulse &lt;strong&gt;New repository variable&lt;/strong&gt; y añada:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Nombre&lt;/th&gt;
					&lt;th&gt;Valor&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;GAMEAP_PLUGIN_ID&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;El ID del plugin, p. ej. &lt;code&gt;fmqnme42gg7da&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;figure&gt;&lt;a class="zoom" href="https://blog.gameap.com/es/guides/publishing-plugins/github-variables.en.png" target="_blank" rel="noopener"&gt;&lt;picture&gt;
 &lt;source type="image/webp" srcset="https://blog.gameap.com/es/guides/publishing-plugins/github-variables.en_hu_f38ec3bfa362fe8b.webp 480w, https://blog.gameap.com/es/guides/publishing-plugins/github-variables.en_hu_d954d987a2cc5fb6.webp 800w, https://blog.gameap.com/es/guides/publishing-plugins/github-variables.en_hu_869e19eb01ccc276.webp 1200w" sizes="(min-width: 860px) 808px, 100vw"&gt;
 &lt;img src="https://blog.gameap.com/es/guides/publishing-plugins/github-variables.en_hu_4d3d26a67a5a6e9d.png" srcset="https://blog.gameap.com/es/guides/publishing-plugins/github-variables.en_hu_b1080eb19826b888.png 480w, https://blog.gameap.com/es/guides/publishing-plugins/github-variables.en_hu_47d81d0f089d30fe.png 800w, https://blog.gameap.com/es/guides/publishing-plugins/github-variables.en_hu_4d3d26a67a5a6e9d.png 1200w" sizes="(min-width: 860px) 808px, 100vw" width="1200" height="524" alt="La pestaña Variables con la variable GAMEAP_PLUGIN_ID" loading="lazy" decoding="async"&gt;
 &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;Variables del repositorio&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;El ID del plugin es el mismo identificador base32 que escribió en &lt;code&gt;PluginInfo&lt;/code&gt;. Cópielo con el
botón junto al campo Plugin ID en la página de edición, o desde el bloque Details en la página del
plugin.&lt;/p&gt;
&lt;p&gt;El ID va en variables y no en secretos a propósito: no es un secreto, y tenerlo visible en los logs
de CI facilita mucho el diagnóstico de fallos. El token, en cambio, siempre es un secreto.&lt;/p&gt;
&lt;p&gt;Así se exporta una clave privada GPG para &lt;code&gt;GPG_SIGNING_KEY&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpg --armor --export-secret-keys SU_ID_DE_CLAVE
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Publique la clave pública donde los usuarios puedan encontrarla — en el README del plugin, por
ejemplo — para que la firma sea verificable.&lt;/p&gt;
&lt;h3 id="dashboard-y-ci-valores-por-defecto-distintos"&gt;Dashboard y CI: valores por defecto distintos &lt;a class="hanchor" href="#dashboard-y-ci-valores-por-defecto-distintos" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Qué&lt;/th&gt;
					&lt;th&gt;Dashboard&lt;/th&gt;
					&lt;th&gt;CI&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Stable release&lt;/td&gt;
					&lt;td&gt;El interruptor está activado por defecto&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;is_stable&lt;/code&gt; por defecto es &lt;strong&gt;false&lt;/strong&gt; — pásalo explícitamente&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Envío a revisión&lt;/td&gt;
					&lt;td&gt;Manual, pulsando un botón&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;submit&lt;/code&gt; por defecto es &lt;strong&gt;true&lt;/strong&gt; — la versión va a revisión de inmediato&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Capturas de pantalla&lt;/td&gt;
					&lt;td&gt;Se ofrecen justo después de la subida&lt;/td&gt;
					&lt;td&gt;No soportadas, añádalas a través del dashboard&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Si lo único que quiere de CI es subir el archivo y terminar los metadatos a mano, pase
&lt;code&gt;submit=false&lt;/code&gt; y la versión permanecerá como borrador.&lt;/p&gt;
&lt;h3 id="errores"&gt;Errores &lt;a class="hanchor" href="#errores" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Código&lt;/th&gt;
					&lt;th&gt;Causa&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;400&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;ID de plugin inválido, un multipart mal formado o no se pasó &lt;code&gt;file&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;401&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;El token falta, es inválido o ha caducado; la cabecera no tiene la forma &lt;code&gt;Bearer gapd_...&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;403&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;El token es válido pero fue emitido para otro plugin&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;409&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Ya existe una versión con ese número&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;422&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;La versión no pasa la comprobación de semver; el archivo de fuentes falta, es demasiado grande o no es &lt;code&gt;.zip&lt;/code&gt;/&lt;code&gt;.tar.gz&lt;/code&gt;; se superan los límites de tamaño del archivo o de la firma&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Los cuerpos de error tienen una forma común:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;version already exists&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;version already exists&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;http_code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Tras un error &lt;code&gt;5xx&lt;/code&gt;, no reintente la subida a ciegas.&lt;/strong&gt; Escribir la versión y enviarla a revisión
no son una única transacción: la versión puede que ya exista, y un reintento devuelve &lt;code&gt;409&lt;/code&gt;. Abra
el dashboard y compruebe qué pasó realmente.&lt;/p&gt;
&lt;h2 id="actualización-y-retirada-de-versiones"&gt;Actualización y retirada de versiones &lt;a class="hanchor" href="#actualizaci%c3%b3n-y-retirada-de-versiones" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una nueva versión se sube exactamente igual que la primera — a través del dashboard o desde CI. Las
versiones ya subidas son inmutables: volver a subir un archivo con el mismo número no es posible,
el intento devuelve &lt;code&gt;409&lt;/code&gt;. Si la compilación salió mal, incremente la versión de parche.&lt;/p&gt;
&lt;p&gt;Cada fila de la tabla de versiones tiene sus propias acciones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Edit&lt;/strong&gt; — cambiar el changelog y los metadatos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Submit for Review&lt;/strong&gt; — para borradores y versiones rechazadas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deprecate&lt;/strong&gt; — para versiones publicadas. La versión sigue disponible pero se marca como
obsoleta.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retract&lt;/strong&gt; — para versiones publicadas y obsoletas. La versión se retira de la publicación.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Solo se puede eliminar un plugin en estado Draft. Un plugin publicado no se puede eliminar — se
puede retractar.&lt;/p&gt;
&lt;h2 id="lista-de-comprobación"&gt;Lista de comprobación &lt;a class="hanchor" href="#lista-de-comprobaci%c3%b3n" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;El correo está verificado, las reglas de publicación aceptadas y la cuenta tiene estado de
desarrollador.&lt;/li&gt;
&lt;li&gt;El plugin está creado, y el nombre y ambas descripciones están rellenos.&lt;/li&gt;
&lt;li&gt;Source URL o Repository URL están rellenos — de lo contrario, cada versión necesita un archivo
de fuentes.&lt;/li&gt;
&lt;li&gt;El ID del plugin está escrito en &lt;code&gt;PluginInfo.id&lt;/code&gt; y el &lt;code&gt;.wasm&lt;/code&gt; se recompiló después.&lt;/li&gt;
&lt;li&gt;El icono está subido y las traducciones de la ficha están en su sitio.&lt;/li&gt;
&lt;li&gt;La versión está subida, con firma y capturas de pantalla adjuntas.&lt;/li&gt;
&lt;li&gt;La versión está enviada a revisión, no dejada como borrador.&lt;/li&gt;
&lt;li&gt;Para CI: un token de despliegue creado y guardado en los secretos, el ID del plugin en las
variables.&lt;/li&gt;
&lt;li&gt;El workflow no tiene barra final en la URL y pasa &lt;code&gt;is_stable&lt;/code&gt; explícitamente.&lt;/li&gt;
&lt;li&gt;Tras el release, se confirma que el plugin y la versión están en estado Released.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Pruebas de carga de paneles de control de servidores de juegos</title><link>https://blog.gameap.com/es/news/game-panels-performance-tests/</link><pubDate>Mon, 03 Aug 2026 13:00:00 +0300</pubDate><guid>https://blog.gameap.com/es/news/game-panels-performance-tests/</guid><description>GameAP 3 y 4, PufferPanel, Pterodactyl y Pelican bajo carga: el banco de pruebas, la metodología y los resultados de las pruebas de rendimiento de las API.</description><content:encoded>&lt;p&gt;Este artículo trata sobre las pruebas de carga de cinco populares paneles de control de servidores de juegos: &lt;strong&gt;GameAP 3&lt;/strong&gt;, &lt;strong&gt;GameAP 4&lt;/strong&gt;, &lt;strong&gt;PufferPanel&lt;/strong&gt;, &lt;strong&gt;Pterodactyl&lt;/strong&gt; y &lt;strong&gt;Pelican&lt;/strong&gt;. Todos los paneles se prueban en el mismo hardware, en condiciones lo más parecidas posible; allí donde las condiciones no pudieron igualarse, se indica explícitamente en las &lt;a href="#limitaciones"&gt;limitaciones&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Aviso.&lt;/strong&gt; Este artículo se publica en el blog de GameAP, y su autor es un desarrollador; GameAP 3 y GameAP 4 son dos de los cinco paneles sometidos a prueba. Esto supone un conflicto de intereses, y es legítimo esperar parcialidad. Para contrarrestarla, toda la prueba está construida para ser verificable: los scripts de k6, las configuraciones, los informes sin procesar de k6 y las métricas de monitorización de cada ejecución están publicados en un &lt;a href="https://github.com/et-nik/game-panels-benchmark" target="_blank" rel="noopener"&gt;repositorio de GitHub&lt;/a&gt;. La metodología puede examinarse paso a paso, y la prueba puede repetirse en su propio hardware. Si cree que algún panel recibió una configuración incorrecta o que alguna conclusión está forzada, abra un issue: se volverá a comprobar y se publicará una corrección. Y una nota para quienes vinieron por la respuesta corta: sí, GameAP 4 obtuvo aquí las mejores cifras.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;En resumen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Con carga típica (10–100 usuarios concurrentes), los cinco paneles funcionan sin un solo error; lo que difiere es la latencia: milisegundos de un solo dígito para los paneles Go, alrededor de 9 ms para GameAP 3, decenas de milisegundos para Pterodactyl y Pelican.&lt;/li&gt;
&lt;li&gt;El techo de rendimiento en este banco de pruebas:
&lt;ul&gt;
&lt;li&gt;1126 peticiones/s para GameAP 4&lt;/li&gt;
&lt;li&gt;696 peticiones/s para PufferPanel&lt;/li&gt;
&lt;li&gt;394 peticiones/s para GameAP 3&lt;/li&gt;
&lt;li&gt;93 peticiones/s para Pterodactyl&lt;/li&gt;
&lt;li&gt;y 76 peticiones/s para Pelican&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Bajo sobrecarga, los paneles fallan de forma distinta: los paneles Go empiezan a devolver errores; los paneles PHP no devuelven ningún error, pero los tiempos de respuesta crecen hasta segundos y decenas de segundos.&lt;/li&gt;
&lt;li&gt;La mayor sorpresa: en saturación, MySQL en Pterodactyl y Pelican consume casi un núcleo de CPU completo, mientras que en GameAP 3, con el mismo MySQL y los mismos ajustes, la base de datos está tres veces menos ocupada con el doble de peticiones por segundo.&lt;/li&gt;
&lt;/ul&gt;
&lt;style&gt;
.bench-chart { --c-g4:#2a78d6; --c-pp:#eb6834; --c-g3:#1baf7a; --c-pt:#eda100; --c-pl:#e87ba4; --c-app:#008300; --c-db:#4a3aa7; }
[data-theme="dark"] .bench-chart { --c-g4:#3987e5; --c-pp:#d95926; --c-g3:#199e70; --c-pt:#c98500; --c-pl:#d55181; --c-app:#008300; --c-db:#9085e9; }
@media (prefers-color-scheme: dark) {
 :root:not([data-theme]) .bench-chart { --c-g4:#3987e5; --c-pp:#d95926; --c-g3:#199e70; --c-pt:#c98500; --c-pl:#d55181; --c-app:#008300; --c-db:#9085e9; }
}
&lt;/style&gt;
&lt;svg class="bench-chart" viewBox="0 0 720 268" role="img" aria-label="Techo de rendimiento: GameAP 4 — 1126 peticiones por segundo, PufferPanel — 696, GameAP 3 — 394, Pterodactyl — 93, Pelican — 76. Cuanto mayor, mejor" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
&lt;text x="8.0" y="16.0" fill="var(--fg)" font-size="13" text-anchor="start" font-weight="600"&gt;Techo de rendimiento&lt;/text&gt;
&lt;text x="712.0" y="16.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;cuanto mayor, mejor&lt;/text&gt;
&lt;text x="8.0" y="32.0" fill="var(--muted)" font-size="11" text-anchor="start"&gt;peticiones por segundo · mediana de tres ejecuciones&lt;/text&gt;
&lt;line x1="66" y1="198.4" x2="700" y2="198.4" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="58.0" y="202.4" fill="var(--muted)" font-size="11" text-anchor="end"&gt;250&lt;/text&gt;
&lt;line x1="66" y1="160.8" x2="700" y2="160.8" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="58.0" y="164.8" fill="var(--muted)" font-size="11" text-anchor="end"&gt;500&lt;/text&gt;
&lt;line x1="66" y1="123.2" x2="700" y2="123.2" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="58.0" y="127.2" fill="var(--muted)" font-size="11" text-anchor="end"&gt;750&lt;/text&gt;
&lt;line x1="66" y1="85.6" x2="700" y2="85.6" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="58.0" y="89.6" fill="var(--muted)" font-size="11" text-anchor="end"&gt;1 000&lt;/text&gt;
&lt;line x1="66" y1="48.0" x2="700" y2="48.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="58.0" y="52.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;1 250&lt;/text&gt;
&lt;line x1="66" y1="236" x2="700" y2="236" stroke="var(--muted)" stroke-width="1"/&gt;
&lt;text x="58.0" y="240.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;0&lt;/text&gt;
&lt;path d="M91.4,236.0 v-165.4 a4,4 0 0 1 4,-4 h68.0 a4,4 0 0 1 4,4 v165.4 z" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x: 1 126 peticiones/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="129.4" y="58.6" fill="var(--fg)" font-size="12" text-anchor="middle" font-weight="600"&gt;1 126&lt;/text&gt;
&lt;text x="129.4" y="256.0" fill="var(--fg)" font-size="12" text-anchor="middle"&gt;GameAP 4.x&lt;/text&gt;
&lt;path d="M218.2,236.0 v-100.7 a4,4 0 0 1 4,-4 h68.0 a4,4 0 0 1 4,4 v100.7 z" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel: 696 peticiones/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="256.2" y="123.3" fill="var(--fg)" font-size="12" text-anchor="middle" font-weight="600"&gt;696&lt;/text&gt;
&lt;text x="256.2" y="256.0" fill="var(--fg)" font-size="12" text-anchor="middle"&gt;PufferPanel&lt;/text&gt;
&lt;path d="M345.0,236.0 v-55.3 a4,4 0 0 1 4,-4 h68.0 a4,4 0 0 1 4,4 v55.3 z" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x: 394 peticiones/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="383.0" y="168.7" fill="var(--fg)" font-size="12" text-anchor="middle" font-weight="600"&gt;394&lt;/text&gt;
&lt;text x="383.0" y="256.0" fill="var(--fg)" font-size="12" text-anchor="middle"&gt;GameAP 3.x&lt;/text&gt;
&lt;path d="M471.8,236.0 v-10.0 a4,4 0 0 1 4,-4 h68.0 a4,4 0 0 1 4,4 v10.0 z" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl: 93 peticiones/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="509.8" y="214.0" fill="var(--fg)" font-size="12" text-anchor="middle" font-weight="600"&gt;93&lt;/text&gt;
&lt;text x="509.8" y="256.0" fill="var(--fg)" font-size="12" text-anchor="middle"&gt;Pterodactyl&lt;/text&gt;
&lt;path d="M598.6,236.0 v-7.4 a4,4 0 0 1 4,-4 h68.0 a4,4 0 0 1 4,4 v7.4 z" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican: 76 peticiones/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="636.6" y="216.6" fill="var(--fg)" font-size="12" text-anchor="middle" font-weight="600"&gt;76&lt;/text&gt;
&lt;text x="636.6" y="256.0" fill="var(--fg)" font-size="12" text-anchor="middle"&gt;Pelican&lt;/text&gt;
&lt;/svg&gt;
&lt;h2 id="objetivos"&gt;Objetivos &lt;a class="hanchor" href="#objetivos" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Encontrar el punto de ruptura: el límite hasta el cual un panel sigue funcionando correctamente.&lt;/li&gt;
&lt;li&gt;Comparar las alternativas en condiciones lo más parecidas posible.&lt;/li&gt;
&lt;li&gt;Comparar GameAP 3 y GameAP 4: qué consiguió exactamente la reescritura de PHP a Go.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Además de la comparación general de los cinco paneles, tres duelos directos resultan de especial interés:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GameAP 3.x vs GameAP 4.x&lt;/strong&gt;: una reescritura completa en Go con una arquitectura actualizada.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GameAP 4.x vs PufferPanel&lt;/strong&gt;: una comparación de paneles escritos en Go.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pterodactyl vs Pelican&lt;/strong&gt;: el panel original frente a su fork.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="los-paneles"&gt;Los paneles &lt;a class="hanchor" href="#los-paneles" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Panel&lt;/th&gt;
					&lt;th&gt;Versión&lt;/th&gt;
					&lt;th&gt;Stack&lt;/th&gt;
					&lt;th&gt;Base de datos&lt;/th&gt;
					&lt;th&gt;Daemon&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 4.x&lt;/td&gt;
					&lt;td&gt;4.x&lt;/td&gt;
					&lt;td&gt;Go&lt;/td&gt;
					&lt;td&gt;PostgreSQL&lt;/td&gt;
					&lt;td&gt;gameap-daemon&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;PufferPanel&lt;/td&gt;
					&lt;td&gt;3.x&lt;/td&gt;
					&lt;td&gt;Go&lt;/td&gt;
					&lt;td&gt;PostgreSQL&lt;/td&gt;
					&lt;td&gt;integrado&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 3.x&lt;/td&gt;
					&lt;td&gt;3.x&lt;/td&gt;
					&lt;td&gt;PHP 8.4 / Laravel&lt;/td&gt;
					&lt;td&gt;MySQL 8.0&lt;/td&gt;
					&lt;td&gt;gameap-daemon&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pterodactyl&lt;/td&gt;
					&lt;td&gt;1.11.x&lt;/td&gt;
					&lt;td&gt;PHP 8.4 / Laravel&lt;/td&gt;
					&lt;td&gt;MySQL 8.0&lt;/td&gt;
					&lt;td&gt;Wings (Docker)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pelican&lt;/td&gt;
					&lt;td&gt;1.0.x&lt;/td&gt;
					&lt;td&gt;PHP 8.4 / Laravel, fork de Pterodactyl&lt;/td&gt;
					&lt;td&gt;MySQL 8.0&lt;/td&gt;
					&lt;td&gt;Wings (Docker)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Los tres paneles PHP ejecutan además Redis (caché y sesiones). Las versiones se registran a nivel de rama mayor a fecha de abril de 2026, cuando se realizaron las ejecuciones.&lt;/p&gt;
&lt;h2 id="qué-se-pone-a-prueba"&gt;Qué se pone a prueba &lt;a class="hanchor" href="#qu%c3%a9-se-pone-a-prueba" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;latencia HTTP/API en distintos niveles de concurrencia: pruebas de carga (10–100 VUs);&lt;/li&gt;
&lt;li&gt;comportamiento bajo sobrecarga (degradación, errores, consumo de recursos): pruebas de estrés (800–1200 VUs);&lt;/li&gt;
&lt;li&gt;el techo de rendimiento sin pausas entre peticiones: pruebas de rendimiento.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fuera del alcance de este artículo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;peticiones de escritura;&lt;/li&gt;
&lt;li&gt;UX, funcionalidades, seguridad, ecosistema;&lt;/li&gt;
&lt;li&gt;el trabajo con servidores de juegos reales y sus archivos;&lt;/li&gt;
&lt;li&gt;estabilidad a largo plazo: no se ejecutaron pruebas soak ni spike.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="banco-de-pruebas"&gt;Banco de pruebas &lt;a class="hanchor" href="#banco-de-pruebas" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Servidor (bare-metal, Selectel):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU: Intel Xeon E-2456 (Raptor Lake, 6C/12T, 3,3 GHz base / 5,1 GHz turbo, 18 MB L3);&lt;/li&gt;
&lt;li&gt;RAM: 32 GB DDR5 ECC (2×16 GB, 4400 MT/s);&lt;/li&gt;
&lt;li&gt;almacenamiento: 2× Samsung 990 PRO 1TB NVMe en mdadm RAID1;&lt;/li&gt;
&lt;li&gt;hipervisor: Proxmox VE 9.1.5 sobre Debian 13, kernel 6.17.9-1-pve.&lt;/li&gt;
&lt;/ul&gt;
&lt;svg viewBox="0 0 720 330" role="img" aria-label="Diagrama del banco de pruebas: un servidor bare-metal con Proxmox aloja cuatro máquinas virtuales — k6-runner, panel, daemon y monitorización" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
 &lt;defs&gt;
 &lt;marker id="ar-a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"&gt;&lt;path d="M0,0L10,5L0,10z" fill="var(--accent)"/&gt;&lt;/marker&gt;
 &lt;marker id="ar-m" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"&gt;&lt;path d="M0,0L10,5L0,10z" fill="var(--muted)"/&gt;&lt;/marker&gt;
 &lt;/defs&gt;
 &lt;text x="8" y="14" fill="var(--muted)"&gt;Bare-metal · Selectel · Xeon E-2456 · 32 GB DDR5 · Proxmox VE 9&lt;/text&gt;
 &lt;rect x="8" y="26" width="704" height="296" rx="10" fill="none" stroke="var(--border)"/&gt;
 &lt;rect x="36" y="56" width="176" height="92" rx="8" fill="var(--surface)" stroke="var(--border)"/&gt;
 &lt;text x="52" y="82" fill="var(--fg)" font-weight="600" font-size="13"&gt;k6-runner&lt;/text&gt;
 &lt;text x="52" y="104" fill="var(--muted)"&gt;4 vCPU · 4 GB&lt;/text&gt;
 &lt;text x="52" y="124" fill="var(--muted)"&gt;generador de carga&lt;/text&gt;
 &lt;rect x="272" y="56" width="176" height="92" rx="8" fill="var(--surface)" stroke="var(--border)"/&gt;
 &lt;text x="288" y="82" fill="var(--fg)" font-weight="600" font-size="13"&gt;Panel (API)&lt;/text&gt;
 &lt;text x="288" y="104" fill="var(--muted)"&gt;4 vCPU · 8 GB · 40 GB&lt;/text&gt;
 &lt;text x="288" y="124" fill="var(--muted)"&gt;Ubuntu 24.04 LTS&lt;/text&gt;
 &lt;rect x="508" y="56" width="176" height="92" rx="8" fill="var(--surface)" stroke="var(--border)"/&gt;
 &lt;text x="524" y="82" fill="var(--fg)" font-weight="600" font-size="13"&gt;Daemon / agente&lt;/text&gt;
 &lt;text x="524" y="104" fill="var(--muted)"&gt;6 vCPU · 12 GB · 80 GB&lt;/text&gt;
 &lt;text x="524" y="124" fill="var(--muted)"&gt;Ubuntu 24.04 LTS&lt;/text&gt;
 &lt;rect x="36" y="200" width="176" height="92" rx="8" fill="var(--surface)" stroke="var(--border)"/&gt;
 &lt;text x="52" y="226" fill="var(--fg)" font-weight="600" font-size="13"&gt;Monitorización&lt;/text&gt;
 &lt;text x="52" y="248" fill="var(--muted)"&gt;2 vCPU · 4 GB&lt;/text&gt;
 &lt;text x="52" y="268" fill="var(--muted)"&gt;Prometheus + Grafana&lt;/text&gt;
 &lt;line x1="212" y1="102" x2="266" y2="102" stroke="var(--accent)" stroke-width="1.5" marker-end="url(#ar-a)"/&gt;
 &lt;text x="239" y="92" fill="var(--accent)" font-size="11" text-anchor="middle"&gt;HTTP&lt;/text&gt;
 &lt;line x1="448" y1="102" x2="502" y2="102" stroke="var(--muted)" stroke-width="1.5" marker-end="url(#ar-m)"/&gt;
 &lt;path d="M 596 148 L 596 246 L 218 246" fill="none" stroke="var(--muted)" stroke-dasharray="4 4" marker-end="url(#ar-m)"/&gt;
 &lt;path d="M 360 148 L 360 246" fill="none" stroke="var(--muted)" stroke-dasharray="4 4"/&gt;
 &lt;text x="430" y="264" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;métricas: node_exporter → Prometheus&lt;/text&gt;
&lt;/svg&gt;
&lt;h3 id="configuración-de-las-máquinas-virtuales"&gt;Configuración de las máquinas virtuales &lt;a class="hanchor" href="#configuraci%c3%b3n-de-las-m%c3%a1quinas-virtuales" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;VM&lt;/th&gt;
					&lt;th&gt;vCPU&lt;/th&gt;
					&lt;th&gt;RAM&lt;/th&gt;
					&lt;th&gt;Disco&lt;/th&gt;
					&lt;th&gt;Rol&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Panel&lt;/td&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;8 GB&lt;/td&gt;
					&lt;td&gt;40 GB&lt;/td&gt;
					&lt;td&gt;panel de control (API), Ubuntu 24.04 LTS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Daemon&lt;/td&gt;
					&lt;td&gt;6&lt;/td&gt;
					&lt;td&gt;12 GB&lt;/td&gt;
					&lt;td&gt;80 GB&lt;/td&gt;
					&lt;td&gt;daemon/agente de servidores de juegos, Ubuntu 24.04 LTS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;k6-runner&lt;/td&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;4 GB&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;generador de carga&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Monitorización&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;4 GB&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;Prometheus + Grafana&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="ajustes-del-host"&gt;Ajustes del host &lt;a class="hanchor" href="#ajustes-del-host" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;gobernador de CPU: performance;&lt;/li&gt;
&lt;li&gt;C-states limitados a C1;&lt;/li&gt;
&lt;li&gt;Turbo Boost activado;&lt;/li&gt;
&lt;li&gt;swap desactivado en todas las máquinas virtuales.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="metodología"&gt;Metodología &lt;a class="hanchor" href="#metodolog%c3%ada" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Los paneles se prueban de uno en uno: mientras un panel está en prueba, todas las demás VM están apagadas para evitar interferencias. Durante una ejecución, lo único que funciona es el panel (API), el daemon de servidores de juegos, el generador de carga k6 y la pila de monitorización.&lt;/p&gt;
&lt;p&gt;Cada panel pasó por &lt;strong&gt;tres ejecuciones completas e independientes&lt;/strong&gt; de toda la serie de perfiles (18–19 de abril de 2026). Todas las cifras de este artículo son &lt;strong&gt;medianas de las tres ejecuciones&lt;/strong&gt;, salvo que se indique explícitamente lo contrario.&lt;/p&gt;
&lt;p&gt;La secuencia para cada panel: arrancar sus dos VM (panel y daemon) → reiniciar los servicios → una ejecución de calentamiento del escenario smoke (1 VU, 30 s) → los perfiles de carga en orden. Antes de cada perfil se reinician los servicios del panel; después de cada perfil hay una pausa de 60 segundos. Los conjuntos de servicios reiniciados difieren entre paneles: para los paneles PHP son php-fpm, nginx y MySQL (más Redis en GameAP 3); para PufferPanel, la propia aplicación; para GameAP 4, solo nginx. Esta es una asimetría de la metodología; más detalles en las &lt;a href="#limitaciones"&gt;limitaciones&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="perfiles-de-carga"&gt;Perfiles de carga &lt;a class="hanchor" href="#perfiles-de-carga" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Perfil&lt;/th&gt;
					&lt;th&gt;VUs&lt;/th&gt;
					&lt;th&gt;Duración&lt;/th&gt;
					&lt;th&gt;Tipo de prueba&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;smoke&lt;/td&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;30 s&lt;/td&gt;
					&lt;td&gt;latencia sin concurrencia&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;baseline&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;4,5 min&lt;/td&gt;
					&lt;td&gt;carga&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;load&lt;/td&gt;
					&lt;td&gt;20 → 50 → 100&lt;/td&gt;
					&lt;td&gt;11 min&lt;/td&gt;
					&lt;td&gt;carga&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress&lt;/td&gt;
					&lt;td&gt;50 → 100 → 200 → 400 → 800&lt;/td&gt;
					&lt;td&gt;10 min&lt;/td&gt;
					&lt;td&gt;estrés&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;200 → 500 → 1000, mantenido 5 min&lt;/td&gt;
					&lt;td&gt;9 min&lt;/td&gt;
					&lt;td&gt;estrés&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;200 → 500 → 800 → 1200, mantenido 5 min&lt;/td&gt;
					&lt;td&gt;10 min&lt;/td&gt;
					&lt;td&gt;estrés&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;max-throughput&lt;/td&gt;
					&lt;td&gt;100, sin think-time&lt;/td&gt;
					&lt;td&gt;2 min 40 s&lt;/td&gt;
					&lt;td&gt;rendimiento&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="escenario"&gt;Escenario &lt;a class="hanchor" href="#escenario" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Cada iteración realiza tres peticiones GET, las que los usuarios reales hacen con más frecuencia: la lista de servidores, los detalles de un servidor aleatorio de esa lista y su estado. Hay una pausa de 0,3–0,8 s entre peticiones y una pausa de 1–3 s al final de la iteración. El perfil max-throughput ejecuta las mismas tres peticiones sin pausas. No hay peticiones que modifiquen datos (POST, PUT, DELETE, etc.) en la prueba.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Panel&lt;/th&gt;
					&lt;th&gt;Lista&lt;/th&gt;
					&lt;th&gt;Detalles&lt;/th&gt;
					&lt;th&gt;Estado&lt;/th&gt;
					&lt;th&gt;Elementos en la respuesta de lista&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 3.x&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers/{id}&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;la misma petición que detalles¹&lt;/td&gt;
					&lt;td&gt;102&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 4.x&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers/{id}&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers/{id}/status&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;102&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;PufferPanel&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers/{id}&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/servers/{id}/status&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;20 (primera página)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pterodactyl&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/client&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/client/servers/{id}&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;…/{id}/resources&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;50 (primera página)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pelican&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/client&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;/api/client/servers/{id}&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;…/{id}/resources&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;50 (primera página)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;¹ GameAP 3.x no tiene un endpoint de estado separado: se repite la petición de detalles.&lt;/p&gt;
&lt;p&gt;Cada panel recibió 100 servidores de juegos ficticios idénticos (un script de relleno que imprime la hora; los servidores nunca se instalaron ni se iniciaron). Las respuestas de las API de lista difieren, sin embargo: GameAP devuelve todos los registros de una vez (la base de datos del banco de pruebas contenía 102), Pterodactyl y Pelican devuelven la primera página de 50, PufferPanel la primera página de 20. El tamaño medio de respuesta en el perfil load es de 13–16 KB por petición para cuatro paneles y de 1,6 KB para PufferPanel. Por tanto, una comparación directa de la latencia de &lt;code&gt;list_servers&lt;/code&gt; entre paneles no es del todo justa: es una de las principales salvedades de esta prueba.&lt;/p&gt;
&lt;h3 id="autenticación-y-límites"&gt;Autenticación y límites &lt;a class="hanchor" href="#autenticaci%c3%b3n-y-l%c3%admites" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;GameAP 3/4, Pterodactyl y Pelican utilizan una clave de API (Bearer). PufferPanel utiliza OAuth2 client credentials: el token se obtiene una vez en &lt;code&gt;setup()&lt;/code&gt; y lo reutilizan todos los VUs. La propia petición OAuth (~58 ms) aparece en las estadísticas de cada perfil, porque &lt;code&gt;setup()&lt;/code&gt; se ejecuta en cada invocación de k6. En el corto perfil smoke (31–34 peticiones) eleva la latencia media de PufferPanel de ~2 a ~3,7 ms, mientras la mediana no se mueve. En baseline es una petición de ~2 200, aproximadamente +0,03 ms (~2 %) en la media, con la mediana y los percentiles intactos; en los perfiles más largos el efecto es despreciable.&lt;/p&gt;
&lt;p&gt;En Pterodactyl y Pelican, los límites de tasa de la API se elevaron a 10 000 peticiones por minuto; de lo contrario, la prueba habría topado con el limitador y no con el panel.&lt;/p&gt;
&lt;h3 id="ajustes-de-los-paneles-php-idénticos-en-gameap-3-pterodactyl-y-pelican"&gt;Ajustes de los paneles PHP (idénticos en GameAP 3, Pterodactyl y Pelican) &lt;a class="hanchor" href="#ajustes-de-los-paneles-php-id%c3%a9nticos-en-gameap-3-pterodactyl-y-pelican" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;; PHP-FPM&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;pm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;dynamic, pm.max_children = 50, pm.start_servers = 10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;; OPcache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;opcache.memory_consumption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;256, opcache.jit = tracing, opcache.jit_buffer_size = 128M&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;; MySQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;innodb_buffer_pool_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2G, max_connections = 200, innodb_flush_log_at_trx_commit = 2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="herramientas"&gt;Herramientas &lt;a class="hanchor" href="#herramientas" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;k6 v1.7.1; el tiempo de espera de petición por defecto es de 60 s;&lt;/li&gt;
&lt;li&gt;Prometheus, node_exporter 1.8.2 (métricas de VM, paso de 15 s), process_exporter 0.8.7 (métricas de procesos, paso de 30 s).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="el-modelo-de-carga-de-bucle-cerrado"&gt;El modelo de carga de bucle cerrado &lt;a class="hanchor" href="#el-modelo-de-carga-de-bucle-cerrado" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;k6 utiliza un modelo de bucle cerrado: un usuario virtual no envía la siguiente petición hasta haber recibido la respuesta a la anterior. Cuando un panel se ralentiza, la intensidad de carga real desciende automáticamente. En un sistema abierto —usuarios reales, paneles que se actualizan automáticamente, integraciones— las peticiones seguirían llegando con independencia de las respuestas, y a un panel degradado le iría aún peor. Téngalo en cuenta al leer los resultados de estrés: las cifras de los paneles ralentizados son una estimación optimista. Más sobre el efecto: &lt;a href="https://www.scylladb.com/2021/04/22/on-coordinated-omission/" target="_blank" rel="noopener"&gt;coordinated omission&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="observaciones-del-montaje-del-banco-de-pruebas"&gt;Observaciones del montaje del banco de pruebas &lt;a class="hanchor" href="#observaciones-del-montaje-del-banco-de-pruebas" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Recursos de la VM del panel durante el perfil smoke: 1 VU, con la base de datos ya cargada con 100 servidores; pico en la ventana del perfil, mediana de tres ejecuciones:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Panel&lt;/th&gt;
					&lt;th&gt;CPU&lt;/th&gt;
					&lt;th&gt;RAM&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 4.x&lt;/td&gt;
					&lt;td&gt;0,4 %&lt;/td&gt;
					&lt;td&gt;449 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;PufferPanel&lt;/td&gt;
					&lt;td&gt;0,6 %&lt;/td&gt;
					&lt;td&gt;462 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 3.x&lt;/td&gt;
					&lt;td&gt;3,2 %&lt;/td&gt;
					&lt;td&gt;1 049 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pterodactyl&lt;/td&gt;
					&lt;td&gt;2,3 %&lt;/td&gt;
					&lt;td&gt;1 096 MB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pelican&lt;/td&gt;
					&lt;td&gt;2,9 %&lt;/td&gt;
					&lt;td&gt;1 177 MB&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;La RAM corresponde a toda la máquina virtual (MemTotal − MemAvailable), incluidos el SO, la base de datos y los servicios auxiliares.&lt;/p&gt;
&lt;p&gt;Observaciones recogidas durante el montaje del banco de pruebas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pterodactyl&lt;/strong&gt; y &lt;strong&gt;Pelican&lt;/strong&gt; no pueden funcionar detrás de NAT, ni el panel ni Wings: el navegador se conecta a Wings directamente por HTTP/HTTPS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pelican&lt;/strong&gt; requiere reconstruir su caché de configuración tras los cambios de ajustes; de lo contrario, el panel falla.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="resultados"&gt;Resultados &lt;a class="hanchor" href="#resultados" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Todas las cifras siguientes son medianas de tres ejecuciones; los informes sin procesar de cada ejecución están en el &lt;a href="https://github.com/et-nik/game-panels-benchmark" target="_blank" rel="noopener"&gt;repositorio&lt;/a&gt;. Sobre la reproducibilidad: en los perfiles baseline, load y max-throughput, la latencia mediana difiere entre ejecuciones como máximo un 6,1 %, y el RPS como máximo un 2,7 %. El smoke es demasiado corto (28–34 peticiones por ejecución), así que la dispersión de su mediana alcanza el 22 %. En los perfiles de estrés el comportamiento es, como era de esperar, menos estable: la mediana de stress-1200 de GameAP 4 en las distintas ejecuciones, por ejemplo, fue 23,7 / 24,6 / 31,9 ms.&lt;/p&gt;
&lt;h3 id="latencia-por-nivel-de-carga"&gt;Latencia por nivel de carga &lt;a class="hanchor" href="#latencia-por-nivel-de-carga" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;svg class="bench-chart" viewBox="0 0 720 430" role="img" aria-label="Latencia mediana por nivel de carga. Los paneles Go se mantienen en milisegundos hasta 800–1000 VUs, los paneles PHP suben a segundos" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
&lt;text x="8.0" y="16.0" fill="var(--fg)" font-size="13" text-anchor="start" font-weight="600"&gt;Latencia mediana por nivel de carga&lt;/text&gt;
&lt;text x="712.0" y="16.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;cuanto menor, mejor&lt;/text&gt;
&lt;text x="8.0" y="32.0" fill="var(--muted)" font-size="11" text-anchor="start"&gt;ms, escala logarítmica · mediana de tres ejecuciones&lt;/text&gt;
&lt;line x1="56" y1="355.9" x2="540" y2="355.9" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="50.0" y="359.9" fill="var(--muted)" font-size="11" text-anchor="end"&gt;1 ms&lt;/text&gt;
&lt;line x1="56" y1="285.2" x2="540" y2="285.2" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="50.0" y="289.2" fill="var(--muted)" font-size="11" text-anchor="end"&gt;10 ms&lt;/text&gt;
&lt;line x1="56" y1="214.6" x2="540" y2="214.6" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="50.0" y="218.6" fill="var(--muted)" font-size="11" text-anchor="end"&gt;100 ms&lt;/text&gt;
&lt;line x1="56" y1="143.9" x2="540" y2="143.9" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="50.0" y="147.9" fill="var(--muted)" font-size="11" text-anchor="end"&gt;1 s&lt;/text&gt;
&lt;line x1="56" y1="73.3" x2="540" y2="73.3" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="50.0" y="77.3" fill="var(--muted)" font-size="11" text-anchor="end"&gt;10 s&lt;/text&gt;
&lt;line x1="56" y1="384" x2="540" y2="384" stroke="var(--muted)" stroke-width="1"/&gt;
&lt;text x="96.3" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1&lt;/text&gt;
&lt;text x="177.0" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;10&lt;/text&gt;
&lt;text x="257.7" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;100&lt;/text&gt;
&lt;text x="338.3" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;800&lt;/text&gt;
&lt;text x="419.0" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1000&lt;/text&gt;
&lt;text x="499.7" y="400.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1200&lt;/text&gt;
&lt;text x="298.0" y="416.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;máximo de VUs objetivo del perfil&lt;/text&gt;
&lt;polyline points="96.3,352.7 177.0,361.9 257.7,371.9 338.3,373.1 419.0,364.2 499.7,257.6" fill="none" stroke="var(--c-g4)" stroke-width="2" stroke-linejoin="round"/&gt;
&lt;circle cx="96.3" cy="352.7" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 1 VUs: 1,11 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="177.0" cy="361.9" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 10 VUs: 0,82 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="257.7" cy="371.9" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 100 VUs: 0,59 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="338.3" cy="373.1" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 800 VUs: 0,57 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="419.0" cy="364.2" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 1000 VUs: 0,76 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="499.7" cy="257.6" r="3.5" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 1200 VUs: 24,6 ms&lt;/title&gt;&lt;/circle&gt;
&lt;polyline points="96.3,334.4 177.0,342.6 257.7,346.3 338.3,344.6 419.0,210.6 499.7,215.5" fill="none" stroke="var(--c-pp)" stroke-width="2" stroke-linejoin="round"/&gt;
&lt;circle cx="96.3" cy="334.4" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 1 VUs: 2,01 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="177.0" cy="342.6" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 10 VUs: 1,54 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="257.7" cy="346.3" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 100 VUs: 1,37 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="338.3" cy="344.6" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 800 VUs: 1,45 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="419.0" cy="210.6" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 1000 VUs: 114 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="499.7" cy="215.5" r="3.5" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 1200 VUs: 97,2 ms&lt;/title&gt;&lt;/circle&gt;
&lt;polyline points="96.3,263.7 177.0,287.7 257.7,289.2 338.3,254.0 419.0,131.9 499.7,123.3" fill="none" stroke="var(--c-g3)" stroke-width="2" stroke-linejoin="round"/&gt;
&lt;circle cx="96.3" cy="263.7" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 1 VUs: 20,2 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="177.0" cy="287.7" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 10 VUs: 9,24 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="257.7" cy="289.2" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 100 VUs: 8,78 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="338.3" cy="254.0" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 800 VUs: 27,7 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="419.0" cy="131.9" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 1000 VUs: 1 481 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="499.7" cy="123.3" r="3.5" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x — 1200 VUs: 1 958 ms&lt;/title&gt;&lt;/circle&gt;
&lt;polyline points="96.3,254.9 177.0,277.8 257.7,262.9 338.3,139.8 419.0,81.4 499.7,71.9" fill="none" stroke="var(--c-pt)" stroke-width="2" stroke-linejoin="round"/&gt;
&lt;circle cx="96.3" cy="254.9" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 1 VUs: 26,9 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="177.0" cy="277.8" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 10 VUs: 12,7 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="257.7" cy="262.9" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 100 VUs: 20,7 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="338.3" cy="139.8" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 800 VUs: 1 144 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="419.0" cy="81.4" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 1000 VUs: 7 664 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="499.7" cy="71.9" r="3.5" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl — 1200 VUs: 10 455 ms&lt;/title&gt;&lt;/circle&gt;
&lt;polyline points="96.3,252.6 177.0,270.8 257.7,233.2 338.3,131.1 419.0,71.8 499.7,65.7" fill="none" stroke="var(--c-pl)" stroke-width="2" stroke-linejoin="round"/&gt;
&lt;circle cx="96.3" cy="252.6" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 1 VUs: 29,0 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="177.0" cy="270.8" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 10 VUs: 16,0 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="257.7" cy="233.2" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 100 VUs: 54,5 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="338.3" cy="131.1" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 800 VUs: 1 520 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="419.0" cy="71.8" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 1000 VUs: 10 501 ms&lt;/title&gt;&lt;/circle&gt;
&lt;circle cx="499.7" cy="65.7" r="3.5" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican — 1200 VUs: 12 807 ms&lt;/title&gt;&lt;/circle&gt;
&lt;rect x="550" y="60.7" width="9" height="9" rx="2" fill="var(--c-pl)"/&gt;
&lt;text x="564.0" y="69.7" fill="var(--fg)" font-size="12" text-anchor="start"&gt;Pelican&lt;/text&gt;
&lt;rect x="550" y="75.7" width="9" height="9" rx="2" fill="var(--c-pt)"/&gt;
&lt;text x="564.0" y="84.7" fill="var(--fg)" font-size="12" text-anchor="start"&gt;Pterodactyl&lt;/text&gt;
&lt;rect x="550" y="118.3" width="9" height="9" rx="2" fill="var(--c-g3)"/&gt;
&lt;text x="564.0" y="127.3" fill="var(--fg)" font-size="12" text-anchor="start"&gt;GameAP 3.x&lt;/text&gt;
&lt;rect x="550" y="210.5" width="9" height="9" rx="2" fill="var(--c-pp)"/&gt;
&lt;text x="564.0" y="219.5" fill="var(--fg)" font-size="12" text-anchor="start"&gt;PufferPanel&lt;/text&gt;
&lt;rect x="550" y="252.6" width="9" height="9" rx="2" fill="var(--c-g4)"/&gt;
&lt;text x="564.0" y="261.6" fill="var(--fg)" font-size="12" text-anchor="start"&gt;GameAP 4.x&lt;/text&gt;
&lt;/svg&gt;
&lt;p&gt;Latencia mediana, ms:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Perfil&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;smoke (1 VU)&lt;/td&gt;
					&lt;td&gt;1,11&lt;/td&gt;
					&lt;td&gt;2,01&lt;/td&gt;
					&lt;td&gt;20,2&lt;/td&gt;
					&lt;td&gt;26,9&lt;/td&gt;
					&lt;td&gt;29,0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;baseline (10 VUs)&lt;/td&gt;
					&lt;td&gt;0,82&lt;/td&gt;
					&lt;td&gt;1,54&lt;/td&gt;
					&lt;td&gt;9,24&lt;/td&gt;
					&lt;td&gt;12,7&lt;/td&gt;
					&lt;td&gt;16,0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;load (hasta 100 VUs)&lt;/td&gt;
					&lt;td&gt;0,59&lt;/td&gt;
					&lt;td&gt;1,37&lt;/td&gt;
					&lt;td&gt;8,78&lt;/td&gt;
					&lt;td&gt;20,7&lt;/td&gt;
					&lt;td&gt;54,5&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (hasta 800 VUs)&lt;/td&gt;
					&lt;td&gt;0,57&lt;/td&gt;
					&lt;td&gt;1,45&lt;/td&gt;
					&lt;td&gt;27,7&lt;/td&gt;
					&lt;td&gt;1 144&lt;/td&gt;
					&lt;td&gt;1 520&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;0,76&lt;/td&gt;
					&lt;td&gt;114&lt;/td&gt;
					&lt;td&gt;1 481&lt;/td&gt;
					&lt;td&gt;7 664&lt;/td&gt;
					&lt;td&gt;10 501&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;24,6&lt;/td&gt;
					&lt;td&gt;97,2&lt;/td&gt;
					&lt;td&gt;1 958&lt;/td&gt;
					&lt;td&gt;10 455&lt;/td&gt;
					&lt;td&gt;12 807&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Percentil 95, ms:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Perfil&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;smoke (1 VU)&lt;/td&gt;
					&lt;td&gt;1,69&lt;/td&gt;
					&lt;td&gt;4,98&lt;/td&gt;
					&lt;td&gt;29,2&lt;/td&gt;
					&lt;td&gt;87,0&lt;/td&gt;
					&lt;td&gt;106&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;baseline (10 VUs)&lt;/td&gt;
					&lt;td&gt;1,45&lt;/td&gt;
					&lt;td&gt;2,08&lt;/td&gt;
					&lt;td&gt;19,9&lt;/td&gt;
					&lt;td&gt;79,4&lt;/td&gt;
					&lt;td&gt;97,0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;load (hasta 100 VUs)&lt;/td&gt;
					&lt;td&gt;1,20&lt;/td&gt;
					&lt;td&gt;1,85&lt;/td&gt;
					&lt;td&gt;13,2&lt;/td&gt;
					&lt;td&gt;154&lt;/td&gt;
					&lt;td&gt;473&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (hasta 800 VUs)&lt;/td&gt;
					&lt;td&gt;2,92&lt;/td&gt;
					&lt;td&gt;58,2&lt;/td&gt;
					&lt;td&gt;908&lt;/td&gt;
					&lt;td&gt;9 146&lt;/td&gt;
					&lt;td&gt;11 221&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;15,2&lt;/td&gt;
					&lt;td&gt;319&lt;/td&gt;
					&lt;td&gt;1 583&lt;/td&gt;
					&lt;td&gt;13 607&lt;/td&gt;
					&lt;td&gt;15 247&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;283&lt;/td&gt;
					&lt;td&gt;330&lt;/td&gt;
					&lt;td&gt;2 078&lt;/td&gt;
					&lt;td&gt;14 144&lt;/td&gt;
					&lt;td&gt;17 477&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Qué muestra esto.&lt;/p&gt;
&lt;p&gt;Hasta 100 VUs inclusive, los cinco paneles responden sin un solo error, y la única cuestión es la velocidad: los paneles Go se mantienen dentro de los 2 ms de mediana, GameAP 3 en torno a 9 ms, Pterodactyl 13–21 ms, Pelican 16–55 ms. La mediana de Pelican ya sube en el perfil load (54,5 ms, p95 — 473 ms): como mostrará la sección de recursos, a 100 VUs prácticamente se ha quedado sin CPU.&lt;/p&gt;
&lt;p&gt;En los perfiles de estrés los grupos divergen radicalmente. GameAP 4 supera 800 y 1000 VUs sin degradación de la mediana (0,6–0,8 ms), PufferPanel se ralentiza hasta 114 ms a 1000 VUs y empieza a devolver errores, GameAP 3 aguanta 800 VUs con una mediana de 28 ms (p95 — 0,9 s) y pasa a 1,5–2 s por encima de 1000. Pterodactyl y Pelican ya responden en segundos a 800 VUs, y en 8–13 s (mediana) a 1000–1200.&lt;/p&gt;
&lt;p&gt;Obsérvese la brecha entre la mediana y el p95 en los paneles PHP incluso a baja carga: la mediana de baseline de Pterodactyl es 12,7 ms mientras que su p95 es 79 ms. Su cola de respuestas lentas es larga incluso allí donde el panel en conjunto da la talla.&lt;/p&gt;
&lt;h3 id="techo-de-rendimiento"&gt;Techo de rendimiento &lt;a class="hanchor" href="#techo-de-rendimiento" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;El perfil max-throughput: 100 VUs ejecutan las mismas tres peticiones sin pausas — una rampa de subida de 30 s, 2 min de mantenimiento, una rampa de bajada de 10 s. El RPS se calcula como la media de toda la ventana, rampas incluidas.&lt;/p&gt;
&lt;svg class="bench-chart" viewBox="0 0 720 268" role="img" aria-label="Techo de rendimiento (max-throughput)" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
&lt;text x="8.0" y="16.0" fill="var(--fg)" font-size="13" text-anchor="start" font-weight="600"&gt;Techo de rendimiento (max-throughput)&lt;/text&gt;
&lt;text x="712.0" y="16.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;cuanto mayor, mejor&lt;/text&gt;
&lt;text x="8.0" y="32.0" fill="var(--muted)" font-size="11" text-anchor="start"&gt;peticiones por segundo · 100 VUs, sin think-time · mediana de tres ejecuciones · 0% de errores en todos los paneles&lt;/text&gt;
&lt;line x1="118.0" y1="48" x2="118.0" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="118.0" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;line x1="226.4" y1="48" x2="226.4" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="226.4" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;250&lt;/text&gt;
&lt;line x1="334.8" y1="48" x2="334.8" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="334.8" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;500&lt;/text&gt;
&lt;line x1="443.2" y1="48" x2="443.2" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="443.2" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;750&lt;/text&gt;
&lt;line x1="551.6" y1="48" x2="551.6" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="551.6" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1 000&lt;/text&gt;
&lt;line x1="660.0" y1="48" x2="660.0" y2="238" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="660.0" y="252.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1 250&lt;/text&gt;
&lt;text x="110.0" y="76.0" fill="var(--fg)" font-size="12" text-anchor="end"&gt;GameAP 4.x&lt;/text&gt;
&lt;path d="M118.0,60.0 h484.0 a4,4 0 0 1 4,4 v14.0 a4,4 0 0 1 -4,4 h-484.0 z" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x: 1 126 peticiones/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="614.0" y="76.0" fill="var(--fg)" font-size="12" text-anchor="start" font-weight="600"&gt;1 126&lt;/text&gt;
&lt;text x="110.0" y="114.0" fill="var(--fg)" font-size="12" text-anchor="end"&gt;PufferPanel&lt;/text&gt;
&lt;path d="M118.0,98.0 h297.6 a4,4 0 0 1 4,4 v14.0 a4,4 0 0 1 -4,4 h-297.6 z" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel: 696 peticiones/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="427.6" y="114.0" fill="var(--fg)" font-size="12" text-anchor="start" font-weight="600"&gt;696&lt;/text&gt;
&lt;text x="110.0" y="152.0" fill="var(--fg)" font-size="12" text-anchor="end"&gt;GameAP 3.x&lt;/text&gt;
&lt;path d="M118.0,136.0 h166.9 a4,4 0 0 1 4,4 v14.0 a4,4 0 0 1 -4,4 h-166.9 z" fill="var(--c-g3)"&gt;&lt;title&gt;GameAP 3.x: 394 peticiones/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="296.9" y="152.0" fill="var(--fg)" font-size="12" text-anchor="start" font-weight="600"&gt;394&lt;/text&gt;
&lt;text x="110.0" y="190.0" fill="var(--fg)" font-size="12" text-anchor="end"&gt;Pterodactyl&lt;/text&gt;
&lt;path d="M118.0,174.0 h36.5 a4,4 0 0 1 4,4 v14.0 a4,4 0 0 1 -4,4 h-36.5 z" fill="var(--c-pt)"&gt;&lt;title&gt;Pterodactyl: 93 peticiones/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="166.5" y="190.0" fill="var(--fg)" font-size="12" text-anchor="start" font-weight="600"&gt;93&lt;/text&gt;
&lt;text x="110.0" y="228.0" fill="var(--fg)" font-size="12" text-anchor="end"&gt;Pelican&lt;/text&gt;
&lt;path d="M118.0,212.0 h29.0 a4,4 0 0 1 4,4 v14.0 a4,4 0 0 1 -4,4 h-29.0 z" fill="var(--c-pl)"&gt;&lt;title&gt;Pelican: 76 peticiones/s&lt;/title&gt;&lt;/path&gt;
&lt;text x="159.0" y="228.0" fill="var(--fg)" font-size="12" text-anchor="start" font-weight="600"&gt;76&lt;/text&gt;
&lt;/svg&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Panel&lt;/th&gt;
					&lt;th&gt;RPS&lt;/th&gt;
					&lt;th&gt;Media, ms&lt;/th&gt;
					&lt;th&gt;Mediana, ms&lt;/th&gt;
					&lt;th&gt;p95, ms&lt;/th&gt;
					&lt;th&gt;Errores&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 4.x&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;1 126&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;77,6&lt;/td&gt;
					&lt;td&gt;68,9&lt;/td&gt;
					&lt;td&gt;179&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;PufferPanel&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;696&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;126&lt;/td&gt;
					&lt;td&gt;121&lt;/td&gt;
					&lt;td&gt;257&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GameAP 3.x&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;394&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;222&lt;/td&gt;
					&lt;td&gt;241&lt;/td&gt;
					&lt;td&gt;302&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pterodactyl&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;93&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;941&lt;/td&gt;
					&lt;td&gt;766&lt;/td&gt;
					&lt;td&gt;1 825&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pelican&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;76&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;1 160&lt;/td&gt;
					&lt;td&gt;945&lt;/td&gt;
					&lt;td&gt;2 291&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Ningún panel devolvió un solo error, y sin embargo el rendimiento de los extremos difiere 15 veces (1126 frente a 76 peticiones/s). Conviene contrastarlo con la utilización de CPU: en este perfil cada panel funciona a su límite (95–100 % de la CPU de la VM — véase la &lt;a href="#recursos-y-cuellos-de-botella"&gt;tabla de recursos&lt;/a&gt;). Misma utilización, resultados separados por múltiplos: comparar paneles por «carga de CPU» carece de sentido; lo que importa es cuánto trabajo realiza un panel por núcleo.&lt;/p&gt;
&lt;h3 id="comportamiento-bajo-sobrecarga"&gt;Comportamiento bajo sobrecarga &lt;a class="hanchor" href="#comportamiento-bajo-sobrecarga" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;svg class="bench-chart" viewBox="0 0 720 316" role="img" aria-label="Peticiones fallidas en los perfiles de estrés. Solo los paneles Go devuelven errores; los paneles PHP no devuelven ninguno, pero se ralentizan" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
&lt;text x="8.0" y="16.0" fill="var(--fg)" font-size="13" text-anchor="start" font-weight="600"&gt;Peticiones fallidas en los perfiles de estrés&lt;/text&gt;
&lt;text x="712.0" y="16.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;cuanto menor, mejor&lt;/text&gt;
&lt;text x="8.0" y="32.0" fill="var(--muted)" font-size="11" text-anchor="start"&gt;% de todas las peticiones (HTTP ≥ 400) · mediana de tres ejecuciones&lt;/text&gt;
&lt;line x1="46" y1="236.0" x2="700" y2="236.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="40.0" y="240.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;0%&lt;/text&gt;
&lt;line x1="46" y1="190.0" x2="700" y2="190.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="40.0" y="194.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;10%&lt;/text&gt;
&lt;line x1="46" y1="144.0" x2="700" y2="144.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="40.0" y="148.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;20%&lt;/text&gt;
&lt;line x1="46" y1="98.0" x2="700" y2="98.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="40.0" y="102.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;30%&lt;/text&gt;
&lt;line x1="46" y1="52.0" x2="700" y2="52.0" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="40.0" y="56.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;40%&lt;/text&gt;
&lt;line x1="90.0" y1="236" x2="108.0" y2="236" stroke="var(--c-g4)" stroke-width="2"/&gt;
&lt;path d="M115.0,236.0 v-0.6 a0.6,0.6 0 0 1 0.6,-0.6 h22.8 a0.6,0.6 0 0 1 0.6,0.6 v0.6 z" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 800 VUs: 0,09%&lt;/title&gt;&lt;/path&gt;
&lt;line x1="146.0" y1="236" x2="164.0" y2="236" stroke="var(--c-g3)" stroke-width="2"/&gt;
&lt;line x1="174.0" y1="236" x2="192.0" y2="236" stroke="var(--c-pt)" stroke-width="2"/&gt;
&lt;line x1="202.0" y1="236" x2="220.0" y2="236" stroke="var(--c-pl)" stroke-width="2"/&gt;
&lt;text x="155.0" y="254.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;800 VUs&lt;/text&gt;
&lt;line x1="308.0" y1="236" x2="326.0" y2="236" stroke="var(--c-g4)" stroke-width="2"/&gt;
&lt;path d="M333.0,236.0 v-93.9 a4,4 0 0 1 4,-4 h16.0 a4,4 0 0 1 4,4 v93.9 z" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 1000 VUs: 21,3%&lt;/title&gt;&lt;/path&gt;
&lt;line x1="364.0" y1="236" x2="382.0" y2="236" stroke="var(--c-g3)" stroke-width="2"/&gt;
&lt;line x1="392.0" y1="236" x2="410.0" y2="236" stroke="var(--c-pt)" stroke-width="2"/&gt;
&lt;line x1="420.0" y1="236" x2="438.0" y2="236" stroke="var(--c-pl)" stroke-width="2"/&gt;
&lt;text x="373.0" y="254.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1000 VUs&lt;/text&gt;
&lt;path d="M523.0,236.0 v-66.0 a4,4 0 0 1 4,-4 h16.0 a4,4 0 0 1 4,4 v66.0 z" fill="var(--c-g4)"&gt;&lt;title&gt;GameAP 4.x — 1200 VUs: 15,2%&lt;/title&gt;&lt;/path&gt;
&lt;path d="M551.0,236.0 v-155.4 a4,4 0 0 1 4,-4 h16.0 a4,4 0 0 1 4,4 v155.4 z" fill="var(--c-pp)"&gt;&lt;title&gt;PufferPanel — 1200 VUs: 34,6%&lt;/title&gt;&lt;/path&gt;
&lt;line x1="582.0" y1="236" x2="600.0" y2="236" stroke="var(--c-g3)" stroke-width="2"/&gt;
&lt;line x1="610.0" y1="236" x2="628.0" y2="236" stroke="var(--c-pt)" stroke-width="2"/&gt;
&lt;line x1="638.0" y1="236" x2="656.0" y2="236" stroke="var(--c-pl)" stroke-width="2"/&gt;
&lt;text x="591.0" y="254.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;1200 VUs&lt;/text&gt;
&lt;text x="99.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="127.0" y="228.8" fill="var(--fg)" font-size="11" text-anchor="middle" font-weight="600"&gt;0,09&lt;/text&gt;
&lt;text x="155.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="183.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="211.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="317.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;&amp;lt; 0,01&lt;/text&gt;
&lt;text x="345.0" y="132.1" fill="var(--fg)" font-size="11" text-anchor="middle" font-weight="600"&gt;21,3&lt;/text&gt;
&lt;text x="373.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="401.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="429.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="535.0" y="160.0" fill="var(--fg)" font-size="11" text-anchor="middle" font-weight="600"&gt;15,2&lt;/text&gt;
&lt;text x="563.0" y="70.6" fill="var(--fg)" font-size="11" text-anchor="middle" font-weight="600"&gt;34,6&lt;/text&gt;
&lt;text x="591.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="619.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;text x="647.0" y="230.0" fill="var(--muted)" font-size="10" text-anchor="middle"&gt;0&lt;/text&gt;
&lt;rect x="46.0" y="271" width="9" height="9" rx="2" fill="var(--c-g4)"/&gt;
&lt;text x="60.0" y="280.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;GameAP 4.x&lt;/text&gt;
&lt;rect x="166.0" y="271" width="9" height="9" rx="2" fill="var(--c-pp)"/&gt;
&lt;text x="180.0" y="280.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;PufferPanel&lt;/text&gt;
&lt;rect x="294.0" y="271" width="9" height="9" rx="2" fill="var(--c-g3)"/&gt;
&lt;text x="308.0" y="280.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;GameAP 3.x&lt;/text&gt;
&lt;rect x="414.0" y="271" width="9" height="9" rx="2" fill="var(--c-pt)"/&gt;
&lt;text x="428.0" y="280.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;Pterodactyl&lt;/text&gt;
&lt;rect x="542.0" y="271" width="9" height="9" rx="2" fill="var(--c-pl)"/&gt;
&lt;text x="556.0" y="280.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;Pelican&lt;/text&gt;
&lt;/svg&gt;
&lt;p&gt;Peticiones fallidas (HTTP ≥ 400), % de todas las peticiones del perfil:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Perfil&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;load (hasta 100 VUs)&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (hasta 800 VUs)&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0,09&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;&amp;lt; 0,01&lt;/td&gt;
					&lt;td&gt;21,3&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;15,2&lt;/td&gt;
					&lt;td&gt;34,6&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;0&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Los puntos de ruptura, según estos datos. PufferPanel es el primero en devolver errores: aislados (0,08–0,12 %) ya a 800 VUs, 21 % a 1000, 35 % a 1200 (hasta 40 % en la petición de lista). GameAP 4 supera 1000 VUs casi limpio (mediana: cero errores; 0,011 % en una de las tres ejecuciones) y devuelve 14,3–15,5 % de errores a 1200 VUs. Los paneles PHP no devuelven errores en absoluto, en ningún perfil.&lt;/p&gt;
&lt;p&gt;Pero «0 % de errores» aquí no significa «el panel funciona». En stress-1200, el p95 de GameAP 3 es 2,1 s, el de Pterodactyl es 14,1 s, el de Pelican es 17,5 s. Formalmente cada petición termina recibiendo un 200 (k6 espera hasta 60 s); en la práctica, un panel que responde en 10–17 segundos es inutilizable. Son dos modos de degradación distintos, no «PHP aguanta y Go no»:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Los paneles Go — fail fast&lt;/strong&gt;: una parte de las peticiones termina rápidamente en error, el resto se sirve con latencia aceptable (la mediana de GameAP 4 a 1200 VUs es 25 ms, la de PufferPanel es 97 ms). El cliente sabe de inmediato que el servidor está sufriendo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Los paneles PHP — una cola&lt;/strong&gt;: PHP-FPM encola las peticiones, no hay errores, pero el tiempo de respuesta crece sin límite. El cliente espera sin saber si llegará una respuesta.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Qué modo es el «correcto» es una cuestión de requisitos, no de benchmark. La consecuencia práctica es única: la sobrecarga de un panel PHP no aparecerá en la monitorización de errores, solo en la latencia.&lt;/p&gt;
&lt;p&gt;RPS alcanzados en los perfiles de estrés:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Perfil&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (hasta 800 VUs)&lt;/td&gt;
					&lt;td&gt;259&lt;/td&gt;
					&lt;td&gt;257&lt;/td&gt;
					&lt;td&gt;210&lt;/td&gt;
					&lt;td&gt;82&lt;/td&gt;
					&lt;td&gt;68&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;716&lt;/td&gt;
					&lt;td&gt;615&lt;/td&gt;
					&lt;td&gt;341&lt;/td&gt;
					&lt;td&gt;90&lt;/td&gt;
					&lt;td&gt;73&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;764&lt;/td&gt;
					&lt;td&gt;699&lt;/td&gt;
					&lt;td&gt;349&lt;/td&gt;
					&lt;td&gt;90&lt;/td&gt;
					&lt;td&gt;74&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Léase esta tabla con atención: debido a las pausas de think-time y a la rampa gradual de VUs, el perfil de estrés físicamente no puede entregar más de ~260 peticiones/s (el número medio temporal de VUs activos es ≈ 268, con ~3,1 s de pausas por iteración de tres peticiones). GameAP 4 y PufferPanel alcanzan el techo del perfil, no el suyo propio: 259 y 257. GameAP 3 logra 210. Pterodactyl y Pelican, en cambio, entregan aproximadamente su max-throughput en cada perfil de estrés (82–90 y 68–74 peticiones/s frente a techos de 93 y 76): el panel sirve lo que puede, el resto se acumula en la cola.&lt;/p&gt;
&lt;h3 id="recursos-y-cuellos-de-botella"&gt;Recursos y cuellos de botella &lt;a class="hanchor" href="#recursos-y-cuellos-de-botella" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CPU pico de la VM del panel, %:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Perfil&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;smoke (1 VU)&lt;/td&gt;
					&lt;td&gt;0,4&lt;/td&gt;
					&lt;td&gt;0,6&lt;/td&gt;
					&lt;td&gt;3,2&lt;/td&gt;
					&lt;td&gt;2,3&lt;/td&gt;
					&lt;td&gt;2,9&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;baseline (10 VUs)&lt;/td&gt;
					&lt;td&gt;0,7&lt;/td&gt;
					&lt;td&gt;0,9&lt;/td&gt;
					&lt;td&gt;4,8&lt;/td&gt;
					&lt;td&gt;8,9&lt;/td&gt;
					&lt;td&gt;10,9&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;load (hasta 100 VUs)&lt;/td&gt;
					&lt;td&gt;2,4&lt;/td&gt;
					&lt;td&gt;3,8&lt;/td&gt;
					&lt;td&gt;25,7&lt;/td&gt;
					&lt;td&gt;81,2&lt;/td&gt;
					&lt;td&gt;96,8&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (hasta 800 VUs)&lt;/td&gt;
					&lt;td&gt;20,4&lt;/td&gt;
					&lt;td&gt;65,3&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1000&lt;/td&gt;
					&lt;td&gt;40,0&lt;/td&gt;
					&lt;td&gt;95,6&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;87,7&lt;/td&gt;
					&lt;td&gt;96,1&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;max-throughput&lt;/td&gt;
					&lt;td&gt;96,1&lt;/td&gt;
					&lt;td&gt;95,3&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
					&lt;td&gt;100&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;La fila clave es load: la carga «de trabajo» de 100 usuarios concurrentes, que todos los paneles superan con cero errores, cuesta a GameAP 4 y PufferPanel un 2–4 % de CPU, a GameAP 3 un 26 %, a Pterodactyl un 81 % y a Pelican un 97 %. Pterodactyl y Pelican ya funcionan al borde de la saturación en este perfil: no les queda margen para picos.&lt;/p&gt;
&lt;p&gt;A dónde va la CPU bajo sobrecarga: procesos en el perfil de estrés (800 VUs), pico en la ventana, media de las ejecuciones 2–3:&lt;/p&gt;
&lt;svg class="bench-chart" viewBox="0 0 720 330" role="img" aria-label="CPU por proceso en el perfil de estrés (800 VUs). En Pterodactyl y Pelican, MySQL ocupa aproximadamente un núcleo completo" style="max-width:100%;height:auto;font-family:var(--mono);font-size:12px"&gt;
&lt;text x="8.0" y="16.0" fill="var(--fg)" font-size="13" text-anchor="start" font-weight="600"&gt;CPU por proceso en el perfil de estrés (800 VUs)&lt;/text&gt;
&lt;text x="712.0" y="16.0" fill="var(--muted)" font-size="11" text-anchor="end"&gt;cuanto menor, mejor&lt;/text&gt;
&lt;text x="8.0" y="32.0" fill="var(--muted)" font-size="11" text-anchor="start"&gt;pico en la ventana del perfil · 400% = 4 vCPU · media de las ejecuciones 2–3&lt;/text&gt;
&lt;line x1="118.0" y1="48" x2="118.0" y2="258" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="118.0" y="272.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;0%&lt;/text&gt;
&lt;line x1="248.5" y1="48" x2="248.5" y2="258" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="248.5" y="272.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;100%&lt;/text&gt;
&lt;line x1="379.0" y1="48" x2="379.0" y2="258" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="379.0" y="272.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;200%&lt;/text&gt;
&lt;line x1="509.5" y1="48" x2="509.5" y2="258" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="509.5" y="272.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;300%&lt;/text&gt;
&lt;line x1="640.0" y1="48" x2="640.0" y2="258" stroke="var(--border)" stroke-width="1"/&gt;
&lt;text x="640.0" y="272.0" fill="var(--muted)" font-size="11" text-anchor="middle"&gt;400%&lt;/text&gt;
&lt;text x="110.0" y="73.5" fill="var(--fg)" font-size="12" text-anchor="end"&gt;GameAP 4.x&lt;/text&gt;
&lt;path d="M118.0,56.5 h38.0 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-38.0 z" fill="var(--c-app)"&gt;&lt;title&gt;GameAP 4.x — gameap: 31,5% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="165.0" y="68.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;31,5&lt;/text&gt;
&lt;path d="M118.0,74.5 h1.9 a1.8744545454545474,1.8744545454545474 0 0 1 1.8744545454545474,1.8744545454545474 v11.3 a1.8744545454545474,1.8744545454545474 0 0 1 -1.8744545454545474,1.8744545454545474 h-1.9 z" fill="var(--c-db)"&gt;&lt;title&gt;GameAP 4.x — postgresql: 2,9% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="127.7" y="86.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;2,9&lt;/text&gt;
&lt;text x="110.0" y="115.5" fill="var(--fg)" font-size="12" text-anchor="end"&gt;PufferPanel&lt;/text&gt;
&lt;path d="M118.0,98.5 h92.4 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-92.4 z" fill="var(--c-app)"&gt;&lt;title&gt;PufferPanel — pufferpanel: 73,1% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="219.4" y="110.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;73,1&lt;/text&gt;
&lt;path d="M118.0,116.5 h9.4 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-9.4 z" fill="var(--c-db)"&gt;&lt;title&gt;PufferPanel — postgresql: 9,5% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="136.4" y="128.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;9,5&lt;/text&gt;
&lt;text x="110.0" y="157.5" fill="var(--fg)" font-size="12" text-anchor="end"&gt;GameAP 3.x&lt;/text&gt;
&lt;path d="M118.0,140.5 h412.1 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-412.1 z" fill="var(--c-app)"&gt;&lt;title&gt;GameAP 3.x — php-fpm: 318% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="539.1" y="152.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;318&lt;/text&gt;
&lt;path d="M118.0,158.5 h38.0 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-38.0 z" fill="var(--c-db)"&gt;&lt;title&gt;GameAP 3.x — mysqld: 31,4% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="165.0" y="170.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;31,4&lt;/text&gt;
&lt;text x="110.0" y="199.5" fill="var(--fg)" font-size="12" text-anchor="end"&gt;Pterodactyl&lt;/text&gt;
&lt;path d="M118.0,182.5 h313.7 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-313.7 z" fill="var(--c-app)"&gt;&lt;title&gt;Pterodactyl — php-fpm: 243% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="440.7" y="194.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;243&lt;/text&gt;
&lt;path d="M118.0,200.5 h138.8 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-138.8 z" fill="var(--c-db)"&gt;&lt;title&gt;Pterodactyl — mysqld: 109% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="265.8" y="212.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;109&lt;/text&gt;
&lt;text x="110.0" y="241.5" fill="var(--fg)" font-size="12" text-anchor="end"&gt;Pelican&lt;/text&gt;
&lt;path d="M118.0,224.5 h336.2 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-336.2 z" fill="var(--c-app)"&gt;&lt;title&gt;Pelican — php-fpm: 260% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="463.2" y="236.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;260&lt;/text&gt;
&lt;path d="M118.0,242.5 h120.0 a3,3 0 0 1 3,3 v9.0 a3,3 0 0 1 -3,3 h-120.0 z" fill="var(--c-db)"&gt;&lt;title&gt;Pelican — mysqld: 94,3% CPU&lt;/title&gt;&lt;/path&gt;
&lt;text x="247.0" y="254.5" fill="var(--fg)" font-size="11" text-anchor="start"&gt;94,3&lt;/text&gt;
&lt;rect x="118" y="285" width="9" height="9" rx="2" fill="var(--c-app)"/&gt;
&lt;text x="132.0" y="294.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;aplicación (Go / php-fpm)&lt;/text&gt;
&lt;rect x="358" y="285" width="9" height="9" rx="2" fill="var(--c-db)"/&gt;
&lt;text x="372.0" y="294.0" fill="var(--fg)" font-size="11" text-anchor="start"&gt;base de datos (PostgreSQL / MySQL)&lt;/text&gt;
&lt;/svg&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Proceso&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Aplicación (Go / php-fpm)&lt;/td&gt;
					&lt;td&gt;31,5&lt;/td&gt;
					&lt;td&gt;73,1&lt;/td&gt;
					&lt;td&gt;318&lt;/td&gt;
					&lt;td&gt;243&lt;/td&gt;
					&lt;td&gt;260&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Base de datos (PostgreSQL / MySQL)&lt;/td&gt;
					&lt;td&gt;2,9&lt;/td&gt;
					&lt;td&gt;9,5&lt;/td&gt;
					&lt;td&gt;31,4&lt;/td&gt;
					&lt;td&gt;109&lt;/td&gt;
					&lt;td&gt;94,3&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;100 % = un núcleo, 4 vCPU en total. Nginx se mantuvo ≤ 3,2 % en cada ejecución; Redis, donde process_exporter lo vio, ≤ 5,1 % (la cobertura de monitorización del proceso de Redis resultó incompleta: en algunas ejecuciones process_exporter no lo rastreó; no cambia el panorama, pero se deja fuera de la tabla).&lt;/p&gt;
&lt;p&gt;La fila más interesante es la de la base de datos. En Pterodactyl y Pelican, MySQL consume 94–109 % de CPU — casi un núcleo completo — mientras sirve 68–82 peticiones por segundo. En GameAP 3, el mismo MySQL 8.0 con los mismos ajustes está ocupado un 31 %, a 210 peticiones por segundo. Por petición, Pterodactyl y Pelican queman aproximadamente nueve veces más tiempo de CPU de base de datos que GameAP 3. Al parecer, el cuello de botella de estos paneles no es PHP como tal, sino su carga de trabajo de base de datos. La causa no se perfiló y sigue siendo una cuestión abierta; un pase de &lt;code&gt;EXPLAIN&lt;/code&gt; sobre las consultas de los endpoints de lista se sugiere por sí mismo.&lt;/p&gt;
&lt;p&gt;RAM pico de la VM, MB:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Perfil&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;smoke (1 VU)&lt;/td&gt;
					&lt;td&gt;449&lt;/td&gt;
					&lt;td&gt;462&lt;/td&gt;
					&lt;td&gt;1 049&lt;/td&gt;
					&lt;td&gt;1 096&lt;/td&gt;
					&lt;td&gt;1 177&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress (hasta 800 VUs)&lt;/td&gt;
					&lt;td&gt;480&lt;/td&gt;
					&lt;td&gt;539&lt;/td&gt;
					&lt;td&gt;1 288&lt;/td&gt;
					&lt;td&gt;1 463&lt;/td&gt;
					&lt;td&gt;1 606&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;stress-1200&lt;/td&gt;
					&lt;td&gt;701&lt;/td&gt;
					&lt;td&gt;744&lt;/td&gt;
					&lt;td&gt;1 354&lt;/td&gt;
					&lt;td&gt;1 469&lt;/td&gt;
					&lt;td&gt;1 610&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;max-throughput&lt;/td&gt;
					&lt;td&gt;641&lt;/td&gt;
					&lt;td&gt;672&lt;/td&gt;
					&lt;td&gt;1 342&lt;/td&gt;
					&lt;td&gt;1 445&lt;/td&gt;
					&lt;td&gt;1 556&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;RSS de procesos en stress-800 (media de las ejecuciones 2–3): la aplicación — 65 MB para GameAP 4 y 107 MB para PufferPanel frente a 2,7–4,2 GB de RSS total entre cincuenta workers de php-fpm; la base de datos — 107 MB de PostgreSQL en GameAP 4 frente a ~613–641 MB de MySQL. Dos salvedades: el RSS de los workers de php-fpm cuenta la memoria compartida muchas veces (el consumo real de la VM está en la tabla anterior), y el RSS de PostgreSQL de PufferPanel varió notablemente entre ejecuciones (721 MB en la primera, ~440 MB en la segunda y la tercera).&lt;/p&gt;
&lt;p&gt;Y una anomalía que honestamente sigue sin explicación: PufferPanel es el único panel que escribe activamente en disco bajo carga — picos de 140–150 IOPS de escritura frente a 4 de GameAP 4 y 21–24 del resto. La causa (¿logs? ¿auditoría? ¿particularidades del uso de PostgreSQL?) no se investigó.&lt;/p&gt;
&lt;h3 id="lo-que-muestran-los-endpoints-individuales"&gt;Lo que muestran los endpoints individuales &lt;a class="hanchor" href="#lo-que-muestran-los-endpoints-individuales" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Latencia mediana por endpoint en el perfil load, ms:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Endpoint&lt;/th&gt;
					&lt;th&gt;GameAP 4.x&lt;/th&gt;
					&lt;th&gt;PufferPanel&lt;/th&gt;
					&lt;th&gt;GameAP 3.x&lt;/th&gt;
					&lt;th&gt;Pterodactyl&lt;/th&gt;
					&lt;th&gt;Pelican&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;list_servers&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;0,87&lt;/td&gt;
					&lt;td&gt;1,41&lt;/td&gt;
					&lt;td&gt;11,1&lt;/td&gt;
					&lt;td&gt;87,3&lt;/td&gt;
					&lt;td&gt;141&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;server_details&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;0,58&lt;/td&gt;
					&lt;td&gt;0,74&lt;/td&gt;
					&lt;td&gt;8,34&lt;/td&gt;
					&lt;td&gt;12,7&lt;/td&gt;
					&lt;td&gt;25,6&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;server_status&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;0,38&lt;/td&gt;
					&lt;td&gt;1,49&lt;/td&gt;
					&lt;td&gt;8,34&lt;/td&gt;
					&lt;td&gt;11,5&lt;/td&gt;
					&lt;td&gt;20,9&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;La lista de servidores es la petición más pesada en todos los paneles, pero en Pterodactyl y Pelican la brecha es dramática: la lista es 7–8 veces más lenta que la petición de detalles (87 y 141 ms frente a 13 y 26 ms). Es la petición de lista la que se degrada primero bajo carga.&lt;/p&gt;
&lt;p&gt;Un recordatorio de la salvedad de la metodología: el tamaño de la respuesta de lista difiere entre paneles (102 registros para GameAP, 50 para Pterodactyl/Pelican, 20 para PufferPanel), así que comparar la fila de &lt;code&gt;list_servers&lt;/code&gt; &lt;strong&gt;entre&lt;/strong&gt; paneles no es justo. Lo que sí puede leerse de esta tabla es la proporción entre endpoints dentro de un mismo panel, y el hecho de que GameAP 4 devuelve su lista completa de 102 registros (~13 KB) más rápido que cualquier panel PHP devuelve su primera página.&lt;/p&gt;
&lt;h3 id="las-tres-comparaciones-directas"&gt;Las tres comparaciones directas &lt;a class="hanchor" href="#las-tres-comparaciones-directas" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;GameAP 3 → GameAP 4.&lt;/strong&gt; Para esto fue la reescritura: la mediana de baseline es 9,24 frente a 0,82 ms (11×), la mediana de load es 8,78 frente a 0,59 ms (15×), el techo de rendimiento es 394 frente a 1126 peticiones/s (2,9×), la memoria de toda la VM es 1,0–1,4 GB frente a 0,45–0,70 GB, la CPU en el perfil load es 26 % frente a 2,4 %.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GameAP 4 vs PufferPanel.&lt;/strong&gt; Ambos son Go + PostgreSQL, y ambos son un orden de magnitud más rápidos que el grupo PHP. Entre los dos: GameAP 4 es 1,9–2,3× más rápido por mediana en baseline/load y 1,6× por el techo de rendimiento (1126 frente a 696), se rompe más tarde bajo sobrecarga (15 % de errores a 1200 VUs frente al 21 % de PufferPanel ya a 1000 y los primeros errores aislados a 800) y apenas toca el disco. Téngase en cuenta, sin embargo, que en este escenario PufferPanel devuelve un orden de magnitud menos datos por petición de lista (20 registros, ~1,6 KB frente a 102 registros, ~13 KB): la brecha se midió sobre un trabajo más ligero para PufferPanel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pterodactyl vs Pelican.&lt;/strong&gt; El fork resultó más lento que el original en todos los perfiles: en load la mediana es 54,5 frente a 20,7 ms, p95 — 473 frente a 154 ms, el techo — 76 frente a 93 peticiones/s, y la saturación de CPU llega antes (97 % ya en load frente a 81 %). Por procesos, el php-fpm de Pelican está más ocupado (260 frente a 243 %) y su MySQL menos (94 frente a 109 %). Las causas de las diferencias no se investigaron; en el momento de la prueba Pelican era un fork joven (1.0.x), y su perfil de rendimiento aún puede cambiar.&lt;/p&gt;
&lt;h2 id="limitaciones"&gt;Limitaciones &lt;a class="hanchor" href="#limitaciones" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una lista de lo que limita las conclusiones de esta prueba.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bases de datos distintas entre los grupos.&lt;/strong&gt; Los paneles Go funcionaron sobre PostgreSQL, los paneles PHP sobre MySQL 8.0 (en ambos casos, las configuraciones recomendadas por los desarrolladores de los paneles). Cualquier comparación «Go vs PHP» aquí significa en realidad «Go+PostgreSQL vs PHP+MySQL»: esta prueba no separa la contribución de la base de datos de la del lenguaje y la arquitectura. Dentro de cada grupo las bases de datos son idénticas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tamaños de respuesta de &lt;code&gt;list_servers&lt;/code&gt; distintos.&lt;/strong&gt; Cada base de datos contiene 100 servidores, pero GameAP devuelve la lista completa (102 registros), Pterodactyl/Pelican la primera página de 50, PufferPanel la primera página de 20; los tamaños de respuesta van de 1,6 a 16 KB. La prueba no normalizó la paginación a un tamaño de página común. Las comparaciones directas de latencia de lista entre paneles están, por tanto, sesgadas; a favor de quién depende del par (el sesgo aligera el trabajo de PufferPanel y lo hace más pesado para GameAP).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GameAP 3 no tiene endpoint de estado&lt;/strong&gt;: su &lt;code&gt;server_status&lt;/code&gt; es una repetición de la petición de detalles, así que un tercio del escenario en GameAP 3 no es equivalente al de los demás paneles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Solo lectura, solo API.&lt;/strong&gt; Sin escrituras, sin WebSocket, sin UI; los servidores ficticios nunca se instalaron ni se iniciaron, y los daemons de las VM vecinas estuvieron prácticamente ociosos. Es una prueba de la capa HTTP/API de los paneles, no de los paneles en su conjunto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El modelo de bucle cerrado de k6&lt;/strong&gt; (&lt;a href="#el-modelo-de-carga-de-bucle-cerrado"&gt;véase la metodología&lt;/a&gt;): los paneles degradados recibieron automáticamente menos carga, así que sus cifras de estrés son una estimación optimista. El tiempo de espera de petición de k6 es de 60 s.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Calentamiento.&lt;/strong&gt; Una ejecución de calentamiento smoke (30 s) antes de la serie de perfiles, y un reinicio de servicios antes de cada perfil. En los paneles que no están limitados por CPU (GameAP 3/4, PufferPanel), la latencia media en baseline es mayor que en el perfil load, más pesado (0,85 → 0,66 ms; 1,49 → 1,27; 10,9 → 9,7): parte de la ventana de baseline aparentemente sigue siendo calentamiento. No cambia las conclusiones, pero las cifras absolutas de baseline están ligeramente infladas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asimetría de reinicio.&lt;/strong&gt; Antes de cada perfil, a los paneles PHP se les reinició php-fpm, nginx y MySQL (buffer pool y cachés vaciados; en GameAP 3 también Redis), a PufferPanel se le reinició la aplicación (PostgreSQL siguió en marcha), y a GameAP 4 solo se le reinició nginx (la aplicación y PostgreSQL no). Los paneles PHP empezaron cada perfil «más fríos». Las fases de rampa de los perfiles compensan esto parcialmente, pero la asimetría no se eliminó por completo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versiones registradas a nivel mayor&lt;/strong&gt; (GameAP 4.x / 3.x, Pterodactyl 1.11.x, Pelican 1.0.x, PufferPanel 3.x, abril de 2026). GameAP 4 es funcionalmente más joven y más simple que el resto: hacer menos trabajo por petición puede ser parte de su ventaja; eso no se cuantificó.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Límites modificados.&lt;/strong&gt; En Pterodactyl y Pelican, los límites de tasa se elevaron a 10 000 peticiones/min; una instalación estándar habría cortado la carga antes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitorización.&lt;/strong&gt; CPU y RAM son métricas de toda la VM con paso de 15 s (los picos cortos pueden suavizarse); las métricas de procesos tienen paso de 30 s, y la cobertura del proceso de Redis entre ejecuciones es incompleta; las estadísticas de smoke de PufferPanel incluyen la petición OAuth de &lt;code&gt;setup()&lt;/code&gt;. Una configuración de hardware, una disposición de VM, 100 servidores en la base de datos: con otro hardware y otros volúmenes de datos, las cifras absolutas diferirán.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusiones"&gt;Conclusiones &lt;a class="hanchor" href="#conclusiones" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Volvamos a las tres preguntas de los objetivos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Cuánto difieren los paneles con carga normal?&lt;/strong&gt; En el perfil load (hasta 100 usuarios concurrentes), los cinco paneles funcionan con cero errores, y la latencia difiere en un orden de magnitud o más: 0,6–1,4 ms de mediana para los paneles Go, ~9 ms para GameAP 3, 21–55 ms para Pterodactyl/Pelican. Dicho sin rodeos: para una persona en un navegador, incluso 55 ms es rápido. Si usted opera un panel, una docena de servidores y ninguna automatización, cualquiera de los contendientes parecerá instantáneo. La diferencia se vuelve práctica con integraciones y automatización, con operaciones masivas, en hardware barato, y en el precio de esa velocidad: los mismos 100 VUs cuestan a Pelican un 97 % de CPU y a GameAP 4, un 2,4 %.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Dónde está el punto de ruptura y cómo se rompe un panel?&lt;/strong&gt; Por techo de rendimiento: 1126 (GameAP 4) → 696 (PufferPanel) → 394 (GameAP 3) → 93 (Pterodactyl) → 76 (Pelican) peticiones/s. Bajo sobrecarga hay dos modos: los paneles Go fallan rápido con errores (PufferPanel desde 800–1000 VUs, GameAP 4 desde 1200), los paneles PHP no devuelven errores pero responden en segundos o decenas de segundos. La conclusión práctica para la monitorización: la sobrecarga de un panel PHP solo es visible en la latencia; no habrá alertas de errores.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Cuál es el cuello de botella?&lt;/strong&gt; Para GameAP 4, la propia CPU de la aplicación (la base de datos apenas está cargada). Para PufferPanel, la aplicación más escrituras de disco notables cuya causa no se investigó. Para GameAP 3, php-fpm (318 % de CPU con MySQL al 31 %). Para Pterodactyl y Pelican el panorama es distinto: junto a php-fpm, MySQL consume casi un núcleo completo; por petición, eso es aproximadamente nueve veces más tiempo de CPU de base de datos del que GameAP 3 gasta en el mismo MySQL. Este es el resultado más interesante de la prueba, y merece una investigación aparte con perfilado de consultas.&lt;/p&gt;
&lt;p&gt;Esta prueba mide un solo corte: la velocidad de lectura de la capa HTTP/API. No dice nada sobre funcionalidades, usabilidad, seguridad o ecosistema, y la elección de un panel también depende de eso. GameAP 4 ganó este benchmark, pero también es el contendiente más joven; Pterodactyl perdió en las cifras, y sin embargo sigue siendo el panel más extendido y con el mayor ecosistema. Qué hacer con estos hechos es decisión suya.&lt;/p&gt;
&lt;h3 id="qué-sigue"&gt;Qué sigue &lt;a class="hanchor" href="#qu%c3%a9-sigue" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fase 2 — gestión de servidores de juegos&lt;/strong&gt;: arranque/parada masivo de N servidores, estado estacionario con servidores en ejecución, consolas WebSocket en paralelo, creación de servidores vía la API. Para ello se está preparando un fake-game-server: un binario que imita a un servidor de juegos real.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Escalado por volumen de datos&lt;/strong&gt;: ejecuciones con 1 / 100 / 1000 servidores en la base de datos, para medir cómo depende la latencia de los endpoints de lista del tamaño de la base de datos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cuestiones abiertas&lt;/strong&gt; de esta fase: las escrituras de disco de PufferPanel, el perfilado de las consultas MySQL de Pterodactyl/Pelican, la diferencia entre Pelican y Pterodactyl.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Los scripts, las configuraciones y los datos sin procesar de todas las ejecuciones están en el repositorio &lt;a href="https://github.com/et-nik/game-panels-benchmark" target="_blank" rel="noopener"&gt;game-panels-benchmark&lt;/a&gt;. Si encuentra un fallo en la metodología o en la interpretación, abra un issue: se volverá a comprobar y se publicará una corrección.&lt;/p&gt;</content:encoded></item><item><title>El blog de GameAP está de vuelta</title><link>https://blog.gameap.com/es/news/blog-relaunch/</link><pubDate>Sun, 02 Aug 2026 12:00:00 +0300</pubDate><guid>https://blog.gameap.com/es/news/blog-relaunch/</guid><description>El blog de GameAP ha sido relanzado: noticias del proyecto, anuncios de lanzamientos y guías sobre servidores de juegos.</description><content:encoded>&lt;p&gt;El blog de GameAP está de vuelta. La versión anterior llevaba años sin actualizarse y había envejecido mal, así que, en lugar de resucitarla, reconstruimos el blog desde cero: con una nueva plataforma y nuevas direcciones.&lt;/p&gt;
&lt;h2 id="nuevas-direcciones"&gt;Nuevas direcciones &lt;a class="hanchor" href="#nuevas-direcciones" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Antes el blog vivía en un solo dominio, &lt;code&gt;gameap.blog&lt;/code&gt;, donde los idiomas se distinguían por un prefijo en la ruta: &lt;code&gt;gameap.blog/en/&lt;/code&gt; y &lt;code&gt;gameap.blog/ru/&lt;/code&gt;. Ahora cada idioma tiene su propio dominio:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.gameap.com/"&gt;blog.gameap.com&lt;/a&gt; — versión en inglés;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.gameap.ru/" target="_blank" rel="noopener"&gt;blog.gameap.ru&lt;/a&gt; — versión en ruso.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Así el blog queda en línea con el resto del proyecto: los recursos en inglés están en &lt;code&gt;gameap.com&lt;/code&gt; (&lt;a href="https://gameap.com/" target="_blank" rel="noopener"&gt;sitio web&lt;/a&gt;, &lt;a href="https://docs.gameap.com/" target="_blank" rel="noopener"&gt;documentación&lt;/a&gt;), y los recursos en ruso están en &lt;code&gt;gameap.ru&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Los enlaces al antiguo dominio &lt;code&gt;gameap.blog&lt;/code&gt; siguen funcionando: le llevan a las nuevas direcciones.&lt;/p&gt;
&lt;p&gt;Cada artículo se publica en ambos idiomas. El selector de idioma en la cabecera del sitio le lleva al mismo artículo en el otro dominio, no a la página de inicio.&lt;/p&gt;
&lt;h2 id="qué-puede-esperar"&gt;Qué puede esperar &lt;a class="hanchor" href="#qu%c3%a9-puede-esperar" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Noticias del proyecto&lt;/strong&gt; — anuncios de lanzamientos de GameAP, cambios en el panel y en el daemon, novedades del ecosistema de plugins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guías&lt;/strong&gt; — artículos prácticos sobre el despliegue de GameAP y la administración de servidores de juegos: desde la instalación del panel hasta el ajuste fino.&lt;/li&gt;
&lt;li&gt;Comparativas, mediciones de rendimiento y análisis en profundidad: lo que no cabe en la documentación.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="enlaces-útiles"&gt;Enlaces útiles &lt;a class="hanchor" href="#enlaces-%c3%batiles" aria-label="Enlace a este encabezado"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gameap.com/" target="_blank" rel="noopener"&gt;Sitio web de GameAP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.gameap.com/" target="_blank" rel="noopener"&gt;Documentación&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gameap" target="_blank" rel="noopener"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El primer gran artículo ya está en marcha: pruebas de carga de los paneles de control de servidores de juegos más populares.&lt;/p&gt;</content:encoded></item><item><title>Bienvenido</title><link>https://blog.gameap.com/es/news/welcome/</link><pubDate>Fri, 24 Apr 2020 00:00:00 +0300</pubDate><guid>https://blog.gameap.com/es/news/welcome/</guid><description>Bienvenido al blog de GameAP. Aquí publicamos información sobre las versiones de GameAP, guías, información sobre módulos y mucho más.</description><content:encoded>&lt;p&gt;Bienvenido al blog de GameAP. Aquí se publicará información sobre las versiones de GameAP, guías, información sobre módulos, etc.&lt;/p&gt;</content:encoded></item></channel></rss>