Agency-Phone Apps Personalizadas
Ahora con AgencyOS27: una actualización gratuita para todos los que ya tienen el teléfono.

🧩 Crea una app propia para Agency-Phone
Hay dos formas de poner una app en el teléfono. Elige la que te encaje:
| Método | Ideal para | Qué tocas |
|---|---|---|
| A. SDK (resource externo) | Desarrolladores y scripts que añaden una app con su propio icono | Solo tu propio resource. Ningún cambio en agency-phone. |
| B. Drop-in (dentro de agency-phone) | Dueños de servidor que añaden una pantalla nativa en la interfaz del teléfono | config.lua + apps/ (+ html/js para una pantalla) |
Si distribuyes una app a otros servidores, usa el método A. No necesita código fuente nuestro y sigue funcionando tras las actualizaciones.
A SDK: registra una app desde tu propio resource
agency-phone ofrece exports con los que cualquier resource pone en tiempo de ejecución una app con su propio icono en la pantalla de inicio del teléfono. La interfaz de tu app vive en tu propio resource; el teléfono muestra el icono y te pasa el control cuando el jugador lo toca. Sin cambios en agency-phone ni build desbloqueado.
Ejemplo mínimo
-- client side of YOUR resource
CreateThread(function()
Wait(1500) -- let agency-phone boot
exports['agency-phone']:RegisterApp({
id = 'myshop',
label = 'My Shop',
icon = 'fa-solid fa-cart-shopping', -- FontAwesome 6 class
-- image = 'https://cfx-nui-myresource/icon.png', -- or your own image instead
color = '#ff9500',
colorDark = '#e08600',
})
end)
-- fired when the player taps your icon on the phone
AddEventHandler('agency-phone:client:openApp', function(appId)
if appId ~= 'myshop' then return end
exports['agency-phone']:ClosePhone()
-- open your own NUI here
end)
Configuración de RegisterApp
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
id | string | sí | Id único, en minúsculas y sin espacios. |
label | string | sí | Nombre bajo el icono. |
icon | string | no | Clase de FontAwesome 6. Se ignora si hay image. |
image | string | no | Imagen de icono propia, por ejemplo https://cfx-nui-<resource>/icon.png. |
color / colorDark | string | no | Degradado detrás del icono (hex). |
slot | number | no | Posición en la pantalla de inicio. Si falta, se coloca sola. |
storeDesc | string | no | Descripción corta en la página de la Store de tu app. Si falta, se usa el label. |
Exports del SDK (cliente)
| Export | Descripción |
|---|---|
RegisterApp(config) | Añade o actualiza tu app. Devuelve true, o false, err. |
UnregisterApp(id) | Quita tu app. También ocurre automáticamente cuando se detiene tu resource. |
GetRegisteredApps() | Tabla con todas las apps externas registradas. |
ClosePhone() | Cierra el teléfono, por ejemplo antes de abrir tu propia NUI. |
El evento al tocar
Cuando el jugador toca tu icono, agency-phone lanza estos dos eventos en el cliente. Usa el que prefieras:
| Evento | Argumentos |
|---|---|
agency-phone:client:openApp | appId (filtra por tu id) |
agency-phone:client:openApp:<yourId> | ninguno (ya va solo a tu app) |
Añade dependency 'agency-phone' a tu fxmanifest.lua para que el orden de arranque sea correcto.
B Drop-in, una pantalla nativa dentro del teléfono
Para una pantalla que dibuja el propio teléfono (no tu resource). Esto modifica archivos de agency-phone.
| Paso | Archivo | ¿Editable en el build escrow? |
|---|---|---|
1. Entrada de la app en AgencyConfig.Apps | config.lua | ✓ sí |
2. Lógica con RegisterNUICallback | apps/<id>_client.lua (+ _server.lua) | ✓ sí |
3. Pantalla <div id="app-<id>"> | html/index.html | ✗ cifrado |
4. case '<id>': en openApp() + cargador | html/js/phone.js | ✗ cifrado |
Los pasos 3 y 4 están en archivos cifrados por el escrow, así que una pantalla nativa necesita un build con código fuente o desbloqueado. Si solo tienes la versión escrow, usa el método A (tu pantalla vive en tu propio resource). Archivos de referencia dentro del resource: apps/testapp_client.lua y apps/dispatch_client.lua.
📋 Otros exports del teléfono
Cliente
| Export | Devuelve | Descripción |
|---|---|---|
IsPhoneOpen() | boolean | El teléfono está abierto. |
IsPhonePoweredOn() | boolean | El dispositivo está encendido. |
HasPhone() | boolean | El jugador tiene un objeto de teléfono. |
HasCarrierContract() | boolean | eSIM o contrato activo. |
GetPhoneData() | table | Instantánea del estado del teléfono. |
PhoneNotification(title, text, icon, color, timeout, acceptIcon, denyIcon) | void | Notificación en el teléfono. |
StartCall(contact) | void | Llama a { name, number }. |
StartAgencyPayCheckout(opts) | void | Cobro AgencyPay (ver abajo). |
Servidor
| Export | Descripción |
|---|---|
SendMail(identifier, mailData) | Entrega un correo a un jugador (conectado o no). |
AddChirp(data) | Publica un mensaje en el feed social Pulse. |
VerifyAgencyPayCharge(source, requestId, minAmount) | Confirma en el servidor un cobro AgencyPay pagado antes de entregar la mercancía (ver abajo). |
⇄ Puente NUI ↔ Lua (método B)
| Dirección | Lado JS | Lado Lua |
|---|---|---|
| JS → Lua | fetchNUI('callbackName', data) | RegisterNUICallback('callbackName', fn) |
| Lua → JS | escucha action en window.onmessage | SendNUIMessage({ action = '...' }) |
💳 Cobro AgencyPay
Cobra al wallet del jugador desde cualquier resource.
exports['agency-phone']:StartAgencyPayCheckout({
merchant = 'My Shop', title = 'Agency Pay', subtitle = 'Checkout',
label = 'Premium Coffee', amount = 24.50,
icon = 'fa-solid fa-mug-hot', accent = '#ff9f0a',
clientEvent = 'myshop:client:agencyPayResult',
serverEvent = 'myshop:server:agencyPayResult',
metadata = { orderId = 'order_2048' },
})
RegisterNetEvent('myshop:client:agencyPayResult', function(result)
if result.status == 'paid' then
-- deliver the goods
end
end)
El resultado incluye status (paid / cancelled / failed), amount, cardId, balance y tus metadata.
Un paid que llega desde el cliente a tu resource no demuestra nada por sí solo. Pasa tu propio id a StartAgencyPayCheckout y llama a exports['agency-phone']:VerifyAgencyPayCharge(source, id, amount) en el servidor antes de entregar nada. Solo devuelve true y el importe si este jugador pagó ese cobro en los últimos 60 segundos, y consume el pago para que no se pueda reclamar dos veces.
🐛 Solución de problemas
| Síntoma | Solución |
|---|---|
| El icono no aparece (SDK) | Actualiza agency-phone al último build de Keymaster, pon Wait(1500) antes de RegisterApp y añade dependency 'agency-phone' a tu manifest. |
| Tocar no hace nada (SDK) | Tu handler de agency-phone:client:openApp tiene que coincidir exactamente con tu id (distingue mayúsculas y minúsculas). |
| La imagen propia no se ve | Usa una URL NUI completa https://cfx-nui-<your-resource>/path/icon.png y añade el archivo a files { } en tu manifest. |
| El icono no aparece (drop-in) | enabled = true e installed = true en la entrada de la config. |
¿Dudas o necesitas ayuda? Únete al Discord de Agency Scripts.