Por qué ChatGPT escribe scripts de FiveM rotos (y cómo solucionarlo)

8 min de lectura

  • chatgpt fivem
  • ia fivem lua
  • skills

ChatGPT escribe scripts de FiveM rotos porque aprendió FiveM de posts de foros y repositorios de GitHub que abarcan diez años de APIs cambiantes, sin forma de saber qué versión usas tú. El resultado es código que mezcla llamadas de ESX y QBCore, usa natives que no existen, pone consultas a la base de datos en el cliente y confía en cada valor que envía un jugador. La solución no es un prompt mejor. Es darle al modelo la documentación real de tu stack en un formato que lee antes de escribir: una skill.

Los seis patrones de fallo en Lua de FiveM escrito por IA

Después de revisar muchos recursos generados por IA, los mismos seis errores aparecen en casi todos. Si aprendes a detectarlos, puedes revisar un script en minutos.

1. Natives inventados

El modelo ha visto miles de nombres de natives, así que puede generar otros nuevos que suenan plausibles. GetVehicleOwner(vehicle), SetPlayerJob(source, 'police'), IsPedInAnyOwnedVehicle. Ninguno existe. Los natives reales siguen la nomenclatura de docs.fivem.net/natives, y un native que no está en esa página no es un native.

La pista: un native que hace exactamente la cosa de alto nivel que pediste. Los natives reales son de bajo nivel. No hay un native para “dueño de un vehículo” porque la propiedad es un concepto del framework guardado en tu base de datos, no en el motor del juego.

2. APIs de QBCore, Qbox y ESX mezcladas

Pide un script para QBCore y a menudo te aparecerá ESX.GetPlayerFromId a mitad de camino, o QBCore.Functions.GetPlayer junto a exports.qbx_core:GetPlayer. El modelo no sabe que son tres frameworks distintos con tres objetos jugador distintos. Sabe que todos son “código de roleplay de FiveM” y los mezcla.

Antes y después, para un evento de servidor que paga a un jugador:

-- Salida de la IA: llamada ESX dentro de un recurso QBCore
RegisterNetEvent('myjob:pay', function(amount)
    local xPlayer = ESX.GetPlayerFromId(source)
    xPlayer.addMoney(amount)
end)
-- Corregido para QBCore, con la recompensa decidida en el servidor
local QBCore = exports['qb-core']:GetCoreObject()

RegisterNetEvent('myjob:pay', function()
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end
    local reward = Config.Reward -- nunca confíes en una cantidad enviada por el cliente
    Player.Functions.AddMoney('cash', reward, 'myjob-payout')
end)

Fíjate en la segunda corrección de ese ejemplo: el original aceptaba amount desde el cliente. Ese es el patrón de fallo cinco, y los dos suelen venir juntos.

3. Código de cliente donde debería ir código de servidor (y al revés)

FiveM ejecuta dos entornos Lua. El cliente corre en el PC del jugador y puede dibujar interfaz, reproducir animaciones y leer el ped local. El servidor guarda la base de datos, el dinero y la verdad. Los modelos de IA confunden esto constantemente:

  • MySQL.query en client.lua. Imposible; oxmysql es un recurso solo de servidor. Además filtraría tu cadena de conexión si llegara a funcionar.
  • TriggerClientEvent llamado desde un script de cliente. Esa es una función de servidor.
  • GetPlayerName(source) en el cliente, donde source no existe.
  • DrawText o lib.notify en el servidor, donde no hay pantalla en la que dibujar.

La pista: un único main.lua que lo hace todo. Los recursos reales separan client/ y server/, y el fxmanifest.lua dice qué archivo es cada cosa.

4. Campos de fxmanifest ausentes o incorrectos

Un manifiesto que el modelo escribe de memoria suele tener esta pinta:

resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937'
client_script 'client.lua'
server_script 'server.lua'

Ese es el formato de 2018. Todavía carga, pero desactiva silenciosamente Lua 5.4 y las características más nuevas del manifiesto. La forma actual es:

fx_version 'cerulean'
game 'gta5'
lua54 'yes'

shared_script '@ox_lib/init.lua'
client_script 'client.lua'
server_scripts {
    '@oxmysql/lib/MySQL.lua',
    'server.lua'
}

Las omisiones más comunes son lua54 'yes' (el script entonces falla con <const> o la división entera), el shared script @ox_lib/init.lua (cada llamada lib. se convierte en “attempt to index a nil value (global ‘lib’)”) y @oxmysql/lib/MySQL.lua (mismo error, esta vez con MySQL).

5. Eventos de servidor que confían en el cliente

Este es el que hace que roben servidores. Un modelo escribe:

RegisterNetEvent('shop:buy', function(item, price)
    local Player = QBCore.Functions.GetPlayer(source)
    Player.Functions.RemoveMoney('cash', price)
    Player.Functions.AddItem(item, 1)
end)

Cualquier jugador con un menú de cheats puede disparar shop:buy con ('weapon_pistol', 0). El handler del evento debe buscar el ítem en una configuración del lado del servidor, tomar el precio de ahí, comprobar que el jugador puede pagarlo y rechazar cualquier otra cosa. La skill fivem-security tiene la lista completa, pero la versión corta es: el cliente pide, el servidor decide.

RegisterNetEvent('shop:buy', function(itemName)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    local item = Config.Items[itemName]
    if not Player or not item then return end
    if Player.PlayerData.money.cash < item.price then return end
    if Player.Functions.RemoveMoney('cash', item.price, 'shop-purchase') then
        Player.Functions.AddItem(itemName, 1)
    end
end)

6. Llamadas a base de datos obsoletas

Los tutoriales antiguos usaban mysql-async y ghmattimysql. Los servidores modernos corren oxmysql. El modelo escribe alegremente MySQL.Async.fetchAll('SELECT * FROM users WHERE identifier = @id', {['@id'] = id}, function(result) ... end). oxmysql mantuvo una capa de compatibilidad para esos nombres durante un tiempo, pero la API actual y documentada es:

local rows = MySQL.query.await('SELECT * FROM users WHERE identifier = ?', { identifier })
local id = MySQL.insert.await('INSERT INTO vehicles (owner, plate) VALUES (?, ?)', { owner, plate })

Depender de los nombres antiguos significa que tu script se romperá el día que desaparezca la capa de compatibilidad, y además las pirámides de callbacks son más difíciles de revisar. La skill oxmysql documenta las variantes await y las llamadas MySQL.prepare y MySQL.transaction que el modelo rara vez usa bien.

Por qué insistir en el prompt no lo soluciona

Puedes poner “usa QBCore, no ESX” en cada prompt y el modelo seguirá resbalando, porque el problema es lo que aprendió, no lo que le pediste. No tiene una memoria fiable de qué firma de QBCore.Functions.* es la actual, qué funciones lib. existen o qué devuelve MySQL.query.await. Las correcciones en el prompt compiten con miles de ejemplos desactualizados en los datos de entrenamiento.

Lo que funciona es poner la referencia correcta delante del modelo en el momento en que escribe. Eso es una skill.

La solución: darle al modelo documentación real con skills

Una skill es una carpeta con un archivo SKILL.md en la raíz y archivos de referencia a su lado. SKILL.md describe qué cubre la skill y cuándo usarla. Los archivos de referencia contienen la API real: firmas de funciones, nombres de eventos, fragmentos de manifiesto, errores comunes y sus soluciones.

Agentes como Claude Code y Cursor leen las descripciones de SKILL.md al inicio de una sesión, y cuando tu petición coincide con una, cargan la skill completa en contexto antes de escribir código. Así, cuando pides una tienda para QBCore, el modelo lee Player.Functions.AddItem, Player.PlayerData.money y el patrón de validación en el servidor desde la referencia, no desde su memoria.

Instalar las nuestras lleva un minuto:

  1. Abre la página de skills de FiveM y descarga las de tu stack. Para la mayoría de servidores son fivem-basics, oxlib, oxmysql y esx-framework o qbcore-framework.
  2. Descomprime cada una en la carpeta de skills que usa tu herramienta (.claude/skills/ para Claude Code, .cursor/rules/ para Cursor, .github/instructions/ para Copilot). Las notas de instalación de cada página de skill tienen las rutas actuales.
  3. Inicia una sesión nueva y pregunta “¿qué skills de FiveM tienes cargadas?” para confirmar.

A partir de ahí, el modelo escribe MySQL.query.await porque es lo que dice la referencia, y pone el manejo del dinero en el servidor porque la skill de seguridad se lo indicó.

Lista de revisión para recursos de FiveM escritos por IA

Incluso con las skills cargadas, revisa cada recurso antes de hacerle ensure. Diez comprobaciones, en orden:

  1. Todos los natives existen en docs.fivem.net.
  2. Solo se referencia un framework, y es el tuyo.
  3. No hay llamadas a base de datos, dinero o inventario en client.lua.
  4. fxmanifest.lua tiene fx_version 'cerulean', game 'gta5' y lua54 'yes' cuando hace falta.
  5. @ox_lib/init.lua y @oxmysql/lib/MySQL.lua están declarados si el código los usa.
  6. Cada handler de evento de servidor valida source y sus argumentos.
  7. Los precios, recompensas y nombres de ítems salen de la configuración del servidor, nunca del evento.
  8. Las llamadas a base de datos usan MySQL.*.await o callbacks con placeholders ?, nunca concatenación de cadenas.
  9. Los bucles con Citizen.Wait(0) solo corren mientras hace falta (un while true do Wait(0) que dibuja un marker por todo el mapa es un bug de frame-time esperando a ocurrir).
  10. Ningún TriggerClientEvent(-1, ...) para cosas que solo debería ver un jugador.

Diez minutos con esta lista detectan la mayor parte de lo que un menú de cheats encontraría por ti.

Preguntas frecuentes

¿Puede ChatGPT escribir un script de FiveM que funcione?

Sí, para recursos standalone pequeños con instrucciones claras, y con mucha más fiabilidad cuando tiene skills o documentación en contexto. Le cuesta cualquier cosa que dependa de una versión concreta de un framework o de un recurso de pago que nunca ha visto.

¿Esto también pasa con Claude, Copilot y Gemini?

Sí. Todos los modelos de lenguaje grandes comparten el mismo problema de datos de entrenamiento: ejemplos de FiveM desactualizados en muchas variantes incompatibles. Los modelos difieren en lo bien que siguen la documentación que les das, y por eso las skills ayudan con todos ellos.

¿Una skill es lo mismo que una regla de Cursor o un system prompt?

Casi. Una regla de Cursor o un CLAUDE.md es una instrucción corta que siempre está activa. Una skill se carga solo cuando es relevante y puede llevar muchos archivos de referencia, así que contiene una referencia de API completa sin inflar cada prompt. Nuestras skills se distribuyen como SKILL.md más referencias y se pueden usar en cualquiera de los dos mecanismos.

Añade estas skills a Cursor, VS Code o Claude Code para que la IA conozca las APIs reales que se mencionan en este artículo.