
SQL & Identifiers Migration: steam/license → citize&#…
Caso de uso: Caso práctico: Está migrando de ESX a QBCore o QBOX (qbx_core) y necesita una migración limpia y auditable de los identificadores y saldos de los jugadores. Esta guía le ofrece SQL listo para producción, un plan reversible y los pasos de validación.
Lecturas relacionadas:
- Patrones de adaptador: ESX↔QBCore↔QBOX (Exportaciones, eventos y modelos de reproductor) — https://fivemx.com/adapter-patterns/
- Conversión de scripts de FiveM: ESX, QBCore, QBOX (Guía del framework) — https://fivemx.com/converting-fivem-scripts/ (Pilar)
¿Qué cambios hay entre ESX y QBCore/QBOX?
| Tema | ESX (común) | QBCore / QBOX (común) |
|---|---|---|
| Clave de jugador principal | identificador (p.ej, licencia:xxx o legado vapor:xxx) | ID de ciudadano (token generado por el servidor) |
| Identificadores Alt | identificador de usuarios, a veces una separada identificadores mesa | columnas como licencia, vapor, fivem almacenado junto ID de ciudadano |
| Modelo monetario | Cuentas separadas (efectivo/banco/dinero negro) a través de usuarios.cuentas (JSON) o cuentas de usuario filas | Soltero dinero JSON en jugadores (p.ej, { "efectivo": 0, "banco": 5000 }); carteras adicionales opcionales |
| Vehículos | vehículos_propiedad.propietario se refiere a ESX identificador | player_vehicles.citizenid (o licencia en algunas horquillas) |
QBOX generalmente sigue la estructura de la base de datos de QB. Trate QBOX como "esquema QB + adiciones qbx". Siempre compare su esquema en vivo.
Reglas de oro (no te las saltes)
- Congelar escrituras durante la migración (detener el servidor del juego + cualquier bot externo que toque la base de datos).
- Copia de seguridad completa y un volcado de estructuras de tablasGuarde ambos con marcas de tiempo.
- Trabajar en una transacción por mesa si es posible, manteniendo los escalones idempotentes.
- Crear un paso de peatones (
identificador antiguo→ID de ciudadano) puedes reutilizarlo o revertirlo.
Objetivo al que apuntas (línea base QB/QBOX)
Un típico jugadores tabla (las columnas varían según la bifurcación):
-- Inspeccione su esquema actual y ajústelo. DESCRIBA a los jugadores; -- Espere columnas como: ID de ciudadano, licencia, nombre, dinero, información del personaje, trabajo, pandilla, metadatos.
- ID de ciudadano:clave principal utilizada en QB/QBOX.
- licencia/steam:conservar para análisis forense y revinculación.
- dinero (JSON): p.ej
{"efectivo":123,"banco":456}Algunos servidores agregancripto,sucio, etc.
Paso 0: Instantánea y puesta en escena
# MySQL/MariaDB backup mysqldump -u root -p --routines --triggers yourdb > yourdb_$(date +%F_%H%M).sql # Optional: structure‑only snapshot mysqldump -u root -p --no-data yourdb > yourdb_schema_$(date +%F_%H%M).sql
Crea una copia de prueba. Ejecútala primero.
Paso 1 — Construir el paso de peatones mesa
Mapearemos cada ESX identificador a un nuevo ID de ciudadanoSi ya tienes una jugadores tabla con los ID de ciudadanos, invertirá el mapeo (ver Jugadores QB existentes nota a continuación).
-- 1) Crear cruce de peatones CREAR TABLA SI NO EXISTE identificador_cruce de peatones ( identificador_antiguo VARCHAR(60) CLAVE PRINCIPAL, id_de_ciudadano VARCHAR(20) NO NULO, licencia VARCHAR(60) NULO, vapor VARCHAR(60) NULO, creado_en MARCA DE TIEMPO PREDETERMINADA MARCA_DE_TIEMPO ACTUAL ) MOTOR=InnoDB JUEGO DE CARACTERES PREDETERMINADO=utf8mb4; -- 2) Semilla de usuarios de ESX (ajuste los nombres de tabla/columna a su versión de ESX) -- EsX común tiene `users.identifier` que contiene license:xxx o steam:xxx INSERT IGNORE INTO identifier_crosswalk (old_identifier, license, steam, citizenid) SELECT u.identifier AS old_identifier, CASE WHEN u.identifier LIKE 'license:%' THEN u.identifier ELSE NULL END AS license, CASE WHEN u.identifier LIKE 'steam:%' THEN u.identifier ELSE NULL END AS steam, UPPER(SUBSTRING(REPLACE(UUID(),'-',''),1,10)) AS citizenid FROM users u; -- 3) Si tiene una tabla de `identificadores` independiente, combine los valores más conocidos -- Ejemplo (opcional): preferir licencia cuando esté disponible UPDATE identifier_crosswalk x JOIN ( SELECT i1.identifier AS old_identifier, MAX(CASE WHEN i1.type='license' THEN i1.value END) AS license, MAX(CASE WHEN i1.type='steam' THEN i1.value END) AS steam FROM identifiers i1 GROUP BY i1.identifier ) i ON i.old_identifier = x.old_identifier SET x.license = COALESCE(i.license, x.license), x.steam = COALESCE(i.steam, x.steam); -- 4) Unicidad e índices ALTER TABLE identifier_crosswalk ADD UNIQUE KEY ux_cid (citizenid), ADD KEY ix_license (license), ADD KEY ix_steam (steam);
¿Jugadores QB existentes? Si ya tienes
jugadoresfilas, crear el paso de peatones seleccionando sulicencia/vapory existenteID de ciudadanoEn lugar de generar nuevos. Tu cruce de caminos nunca debe asignar un nuevo ID de ciudadano a un QB existente.
Paso 2: Normalizar/preparar el objetivo jugadores filas
Crea cualquier faltante jugadores filas basadas en ESX usuarios.
-- Asegúrese de que `players` exista e inspeccione primero sus columnas. -- Insertaremos shells solo para los ciudadanos faltantes. INSERT INTO players (citizenid, license, name, money, charinfo, metadata) SELECT x.citizenid, COALESCE(NULLIF(x.license,''), NULLIF(x.steam,'')) AS license_like, COALESCE(u.firstname, '') || ' ' || COALESCE(u.apellido, '') COMO name_like, '{"cash":0,"bank":0}' COMO money, JSON_OBJECT( 'nombre', COALESCE(u.nombre,''), 'apellido', COALESCE(u.apellido,''), 'fecha de nacimiento', COALESCE(u.fechadenacimiento,''), 'género', COALESCE(u.sexo,'') ) COMO charinfo, JSON_OBJECT('esx_identifier', u.identifier) COMO metadatos DE usuarios u UNIRSE identificador_cruce de caminos x EN x.old_identifier = u.identifier IZQUIERDA UNIRSE jugadores p EN p.citizenid = x.citizenid DONDE p.citizenid ES NULO;
Nota: Utilice la cadena concat de su variante SQL (
CONCATen MySQL) y las funciones JSON correspondientemente. Para MySQL 5.7, reemplaceOBJETO JSONcon construcción manual de cuerdas si es necesario.
Variante segura para MySQL:
INSERTAR EN jugadores (idciudadano, licencia, nombre, dinero, charinfo, metadatos) SELECCIONAR x.idciudadano, COALESCE(NULLIF(x.licencia,''), NULLIF(x.vapor,'')) COMO similar a licencia, RECORTAR(CONCAT(COALESCE(u.nombre,''), ' ', COALESCE(u.apellido,''))) COMO similar a nombre, '{"efectivo":0,"banco":0}' COMO dinero, CONCAT('{', '"nombre":"', REEMPLAZAR(COALESCE(u.nombre,''),'"','\"'), '",', '"apellido":"', REEMPLAZAR(COALESCE(u.apellido,''),'"','\"'), '",', '"fechanacimiento":"', REEMPLAZAR(COALESCE(u.fechanacimiento,''),'"','\"'),'",', '"género":"', REEMPLAZAR(COALESCE(u.sexo,''),'"','\"'), '"', '}') COMO charinfo, CONCAT('{', '"esx_identifier":"', REEMPLAZAR(u.identificador,'"','\"'), '"', '}') COMO metadatos DESDE usuarios u UNIRSE identificador_cruce x EN x.antiguo_identificador = u.identificador IZQUIERDA UNIRSE jugadores p EN p.ciudadanoid = x.ciudadanoid DONDE p.ciudadanoid ES NULO;
Paso 3 — Migrar Cuentas → Dinero
Hay dos patrones ESX comunes:
A) ESX almacena los saldos en su interior usuarios.cuentas JSON
-- Ejemplo: users.accounts = '{"bank":5000, "money":750, "black_money":200}' -- 1) Extraer de JSON de ESX de forma segura -- Crear una vista/tabla temporal con números analizados CREATE TEMPORARY TABLE esx_balances AS SELECT u.identifier, COALESCE(JSON_EXTRACT(u.accounts, '$.money'), 0) AS esx_cash, COALESCE(JSON_EXTRACT(u.accounts, '$.bank'), 0) AS esx_bank, COALESCE(JSON_EXTRACT(u.accounts, '$.black_money'), 0) AS esx_black FROM users u; -- 2) Fusionar en JSON de dinero QB/QBOX -- Decide cómo manejar black_money (ver Opciones a continuación) ACTUALIZAR jugadores p UNIRSE a identifier_crosswalk x ON x.citizenid = p.citizenid UNIRSE a esx_balances b ON b.identifier = x.old_identifier ESTABLECER p.money = JSON_OBJECT( 'cash', CAST(b.esx_cash AS UNSIGNED), 'bank', CAST(b.esx_bank AS UNSIGNED) );
Si MySQL no tiene operaciones JSON nativas (o versión anterior): crear cadenas JSON usando CONCAT.
B) ESX almacena los saldos en cuentas de usuario filas
-- Ejemplo: user_accounts(identificador, cuenta, dinero) CREAR TABLA TEMPORAL esx_balances COMO SELECCIONAR ua.identificador, SUMA(CASO CUANDO ua.cuenta='dinero' ENTONCES ua.dinero SI NO 0 FIN) COMO esx_efectivo, SUMA(CASO CUANDO ua.cuenta='banco' ENTONCES ua.dinero SI NO 0 FIN) COMO esx_banco, SUMA(CASO CUANDO ua.cuenta='negro_dinero' ENTONCES ua.dinero SI NO 0 FIN) COMO esx_negro DE user_accounts ua AGRUPAR POR ua.identificador; ACTUALIZAR jugadores p UNIRSE a identificador_crosswalk x ENCENDIDO x.citizenid = p.citizenid UNIRSE a esx_balances b ENCENDIDO b.identificador = x.old_identifier ESTABLECER p.dinero = JSON_OBJECT( 'efectivo', CAST(b.esx_cash COMO SIN FIRMAR), 'banco', CAST(b.esx_bank COMO SIN FIRMAR) );
Manejo dinero negro (elige uno)
- Opción 1 (recomendada): Cree una clave de billetera dedicada en QB money JSON, por ejemplo
"sucio". - Opción 2: Convertir a artículos (por ejemplo, facturas marcadas) y acreditar el inventario en su lugar (requiere migración de artículos; fuera del alcance aquí).
- Opción 3: Póngalo a cero (fuertemente desaconsejado a menos que haya anunciado un borrado).
Implementación de la opción 1:
-- Agregar billetera sucia en JSON (servidores que admiten billeteras adicionales) ACTUALIZAR jugadores p UNIRSE a identifier_crosswalk x ON x.citizenid = p.citizenid UNIRSE a esx_balances b ON b.identifier = x.old_identifier SET p.money = JSON_MERGE_PATCH(p.money, JSON_OBJECT('dirty', CAST(b.esx_black AS UNSIGNED)));
Asegúrese de que su marco de trabajo/recursos realmente respeten la billetera adicional. De lo contrario, prefiera la opción 2.
Paso 4: Vuelva a crear las claves de las tablas externas que hacen referencia a ESX identificador
Tablas típicas para arreglar:
vehículos_propiedad.propietario→ mapa aID de ciudadano(QB:player_vehicles.citizenid)- Cualquier tabla personalizada que contenga
identificadorcolumnas (casas, facturación, pandillas, negocios)
Vehículos (ESX → QB)
-- Si conserva ESX `owned_vehicles`, vuelva a codificar owner → citizenid para compatibilidad futura ALTER TABLE owned_vehicles ADD COLUMN citizenid VARCHAR(20) NULL; UPDATE owned_vehicles v JOIN identifier_crosswalk x ON x.old_identifier = v.owner SET v.citizenid = x.citizenid WHERE v.citizenid IS NULL; CREATE INDEX ix_ov_cid ON owned_vehicles (citizenid);
**Vehículos en QB** (campos mínimos; ajuste a su esquema):
INSERTAR IGNORAR EN player_vehicles (citizenid, placa, vehículo, estado, garaje) SELECCIONAR x.citizenid, v.placa, v.vehicle, 0 COMO estado, 'A' COMO garaje DESDE owned_vehicles v UNIR identificador_cruce de peatones x EN x.old_identifier = v.owner;
Validar nombres de campos JSON (
vehículocontramods/accesorios) y la lista de columnas contra su esquema QB/QBOX real.
Paso 5: Restricciones, índices y comprobaciones de integridad
-- Ensure primary/unique keys ALTER TABLE players ADD UNIQUE KEY ux_players_citizenid (citizenid); -- Optional: keep a quick lookup by license/steam ALTER TABLE players ADD KEY ix_players_license (license); -- Spot orphaned crosswalks (no players row) SELECT x.* FROM identifier_crosswalk x LEFT JOIN players p ON p.citizenid = x.citizenid WHERE p.citizenid IS NULL; -- Spot players with zeroed wallets (sanity) SELECT citizenid, money FROM players WHERE JSON_EXTRACT(money, '$.cash') IS NULL OR JSON_EXTRACT(money, '$.bank') IS NULL; -- Detect duplicates (same human with multiple identifiers) SELECT old_identifier, COUNT(*) FROM identifier_crosswalk GROUP BY old_identifier HAVING COUNT(*) > 1;
Paso 6 — Conjunto de validación
- Número de filas:
COUNT(usuarios)≈CONTAR(jugadores)(dentro de los deltas esperados). - Totales de saldo: Suma del efectivo/banco ESX ≈ Suma de las billeteras QB después de la migración.
- Ejemplo de auditoría: Elige 10 jugadores por nombre; verifica
ID de ciudadano, balanzas, vehículos. - Prueba de inicio de sesión: Poner el servidor en modo staging, iniciar sesión con algunos jugadores conocidos, verificar las IU.
Ejemplos de comprobación de totales:
-- Totales de ESX SELECCIONAR SUMA(COALESCE(JSON_EXTRACT(cuentas,'$.dinero'),0)) COMO esx_cash_total, SUM(COALESCE(JSON_EXTRACT(cuentas,'$.banco'),0)) COMO esx_bank_total DE usuarios; -- Totales de QB SELECCIONAR SUMA(COALESCE(JSON_EXTRACT(dinero,'$.dinero'),0)) COMO qb_cash_total, SUM(COALESCE(JSON_EXTRACT(dinero,'$.banco'),0)) COMO qb_bank_total DE jugadores;
Paso 7: Compatibilidad en tiempo de ejecución (adaptadores)
Incluso después de la migración, algunos scripts heredados aún pueden hacer referencia a ESX identificadorMantener el paso de peatones y use un ayudante para resolver " (o inverso) en tiempo de ejecución.
Ayudante de Lua (servidor):
--- lookup_citizenid.lua función local getCitizenIdByIdentifier(identificador) resultado local = MySQL.query.await('SELECT citizenid FROM identifier_crosswalk WHERE old_identifier = ? LIMIT 1', { identificador }) si resultado y resultado[1] entonces devuelve resultado[1].citizenid fin devuelve nulo fin devuelve { getCitizenIdByIdentifier = getCitizenIdByIdentifier }
Use esto en los controladores de eventos heredados hasta que todos los scripts sean nativos de QB/QBOX. Consulte el artículo sobre patrones de adaptador para obtener información completa sobre las correcciones de interfaz.
- Patrones adaptadores: https://fivemx.com/adapter-patterns/
- Guía de conversión completa (Pillar): https://fivemx.com/converting-fivem-scripts/
Estrategia de reversión
- Mantener
identificador_cruce de peatonesy un copia de seguridad previa a la migración. - Si algo sale mal, descarte el nuevo.
jugadoresfilas creadas en esta ventana y restaurar la copia de seguridad. - Vuelva a ejecutar la migración después de corregir los casos extremos de los datos.
Etiqueta sencilla para marcar tu ventana:
-- Etiquetar nuevas filas ACTUALIZAR jugadores ESTABLECER metadatos = JSON_MERGE_PATCH(COALESCE(metadata,'{}'), JSON_OBJECT('migration_tag','esx_to_qb_2025_08_16')) DONDE citizenid EN (SELECCIONAR citizenid DE identifier_crosswalk);
Casos extremos y consejos
- Varios caracteres por humano: Si su ESX usó uno
identificadorpor cuenta (sin multi-char), pero planeas tener multi-char en QB, considera generar ciudadanos adicionales más adelante a través de flujos en el juego, no aquí. - Colisiones de nombres: Dos usuarios de ESX con el mismo nombre y apellido están bien; ID de ciudadano es la clave
- Desaparecido “ valores: Prefiera cualquier identificador estable que tenga (
vapor,licencia2,fivem). Rellenarjugadores.licenciacon lo mejor disponible. - MySQL antiguo sin JSON: Utilice cadenas JSON de texto simple y analícelas en el código de la aplicación; planifique actualizar.
- Política de dinero negro: Comunique su decisión. Si se convierte a elementos, realice una migración de elementos independiente y transparente.
Lista de verificación de corte (producción)
Preguntas frecuentes
P: ¿Puedo seguir usando ESX? “ ¿en cualquier lugar?
A: Sí, pero trátalo como legadoUtilice la pasarela para resolver cuando sea necesario y actualice los scripts a citizenid lo antes posible.
P: ¿QBOX requiere SQL diferente?
R: No para identificadores ni dinero; QBOX sigue de cerca el esquema de QB. Valide los nombres de las columnas antes de ejecutar.
P: ¿Qué pasa con los inventarios, los trabajos y las pandillas?
R: Fuera del alcance de este artículo. Se deben abordar después de que los identificadores y el dinero se estabilicen. Consulte la guía de Pilares para obtener una cobertura completa.
Próximos pasos
- Implementar calzas de tiempo de ejecución desde Patrones adaptadores: https://fivemx.com/adapter-patterns/
- Termine su migración completa utilizando el Guía del marco (Pilar): https://fivemx.com/converting-fivem-scripts/
- Documente sus desviaciones locales (billeteras personalizadas, columnas adicionales) dentro de su repositorio.
Apéndice — Envoltorios idempotentes
Envuelva las ACTUALIZACIONES/INSERCIONES críticas con protectores para poder ejecutarlas nuevamente de manera segura.
-- Ejemplo de guardia: solo actualiza los jugadores con dinero intacto UPDATE jugadores p JOIN identifier_crosswalk x ON x.citizenid = p.citizenid JOIN esx_balances b ON b.identifier = x.old_identifier SET p.money = JSON_OBJECT('cash', CAST(b.esx_cash AS UNSIGNED), 'bank', CAST(b.esx_bank AS UNSIGNED)) WHERE JSON_EXTRACT(p.money, '$.cash') = 0 AND JSON_EXTRACT(p.money, '$.bank') = 0;
Conserve el paso de peatones para siempre. Es su Rosetta Stone para registros y guiones antiguos.






