Atributos de usuario personalizados
Los atributos de usuario son propiedades que describen a tus usuarios. Úsalos para personalizar mensajes, crear segmentos y analizar tu base de usuarios.
¿Qué son los atributos de usuario?
Los atributos son pares clave-valor vinculados a perfiles de usuario:
{
"userId": "user_123",
"attributes": {
"firstName": "John",
"lastName": "Doe",
"plan": "premium",
"signupDate": "2024-01-15",
"totalPurchases": 5,
"lastLoginDate": "2024-01-20",
"preferences": {
"newsletter": true,
"notifications": "email"
}
}
}
Configurar atributos
Mediante SDK (lado cliente)
import JoryioSDK from '@joryio/web-sdk';
const joryio = new JoryioSDK({ sdkKey: 'jry_sdk_web_...' });
// Identifica a un usuario
joryio.identify('user_123');
// Configura atributos por separado
joryio.setAttributes({
firstName: 'John',
lastName: 'Doe',
plan: 'premium',
signupDate: '2024-01-15'
});
// Actualiza los atributos más adelante
joryio.setAttributes({
plan: 'enterprise',
lastUpgrade: new Date().toISOString()
});
Mediante API (lado servidor)
// Crea o actualiza un usuario con atributos
fetch('https://api-eu1.joryio.com/api/users/track', {
method: 'POST',
headers: {
'Authorization': 'Bearer jry_live_your_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
attributes: {
externalId: 'user_123',
attributes: {
firstName: 'John',
plan: 'premium',
totalOrders: 10,
lifetimeValue: 1250.00
}
}
})
});
Atributos estándar frente a personalizados
Atributos estándar
Envía siempre los números de teléfono en formato internacional completo (E.164): +, el código de país y luego el número - por ejemplo +972501234567. Un número sin + se lee con el país predeterminado para números de teléfono del espacio de trabajo; si no hay ninguno, o los dígitos se pueden leer de dos formas, se guarda sin verificar y no se usa para mensajes. Cómo se tratan los números de teléfono
Reservados por Joryio (se almacenan en el nivel raíz):
userId: identificador único de usuario.email: dirección de email.phone: número de teléfono.externalId: ID de sistema externo.createdAt: marca de tiempo de creación del usuario.updatedAt: marca de tiempo de la última actualización.
Atributos personalizados
Cualquier otra propiedad que definas (se almacena en el objeto attributes):
firstName,lastName,nameplan,subscription Tiercompany,industry,jobTitletotalPurchases,lifetimeValuepreferences,settings- Y cualquier otra cosa que necesites.
Tipos de datos
Los atributos admiten varios tipos de datos:
Cadena
{
firstName: "John",
plan: "premium",
country: "US"
}
Número
{
age: 30,
totalPurchases: 15,
lifetimeValue: 1250.50
}
Booleano
{
emailVerified: true,
newsletter: false,
isPremium: true
}
Fecha/marca de tiempo
{
signupDate: "2024-01-15",
lastLoginDate: "2024-01-20T10:30:00Z",
trialEndsAt: "2024-02-15T23:59:59Z"
}
Para las fechas, usa el formato ISO 8601: YYYY-MM-DDTHH:mm:ssZ.
Matriz
{
tags: ["vip", "early-adopter"],
interests: ["technology", "sports", "music"],
purchasedProducts: ["product_1", "product_2"]
}
Objeto/anidado
{
preferences: {
theme: "dark",
language: "en",
notifications: {
email: true,
sms: false,
push: true
}
},
address: {
street: "123 Main St",
city: "San Francisco",
state: "CA",
zip: "94102"
}
}
Cómo se decide un tipo
Joryio nunca adivina por el NOMBRE de un campo. El tipo se resuelve en este orden:
- Declarado - alguien definió el tipo en la página Atributos (o en la página Eventos, para propiedades de evento). Esto siempre prevalece.
- Campo de comercio estándar -
price,total,revenue,quantity,tax,shipping,discount,subtotalyvaluese tratan como números por defecto, envíes12.34o"12.34". Los identificadores comoorder_idyskuquedan fuera a propósito: suelen parecer numéricos, y tiparlos como números es la razón por la que un pedido007deja de coincidir. - Inferido de tus datos - el tipo más común en una muestra, por evento.
Product Added.stepyOnboarding Step Tapped.stepse tipan por separado.
Cuando los valores no coinciden
Si más de un ~20% de los valores son de otro tipo, el atributo se muestra como Mixto con los recuentos (por ejemplo string 994 - number 448). Joryio no elige un lado: compara como texto - lo que nunca da una respuesta incorrecta, solo aproximada - y te pide que definas el tipo.
Definir el tipo corrige la comparación también para los valores ya almacenados: un campo declarado como número compara "12.34" y 12.34 como el mismo número, de modo que price > 100 deja de comparar texto (donde "90" > "100" es verdadero).
Declarar un tipo cambia cómo se COMPARAN los valores. No reescribe lo almacenado: tus datos históricos conservan exactamente los valores que enviaste.
Qué cuesta definir un tipo
Definir un tipo es una decisión sobre cómo tratar un desacuerdo, no una forma de terminarlo. Los valores que no se pueden leer como el tipo que elegiste dejan de coincidir con los filtros, y quedan fuera de ambos lados de un rango.
Supón que step tiene los números 1, 2, 3 en la mayoría de los eventos y
las palabras "invite" y "photo" en unos pocos, y lo defines como Número:
| filtro | un contacto cuyo step es "invite" |
|---|---|
step > 1 | no coincide |
step <= 1 | no coincide |
step = 1 | no coincide |
step no es 1 | coincide |
step existe | coincide |
Así que dos segmentos pensados para dividir tu audiencia en dos - step > 1 y
step <= 1 - dejan a esos contactos en ninguno. Una prueba dividida construida
así los pierde en silencio.
Los números escritos como texto no son problema: "12.34" se lee como 12.34 y
coincide con normalidad. Solo se ven afectados los valores sin lectura posible
como el tipo elegido.
La lista de atributos muestra cuántos valores se ven afectados junto al tipo, de modo que el desacuerdo sigue visible después de decidir. La corrección está en el origen - envía el valor en una forma consistente - porque nada aquí reescribe datos que ya enviaste.
Los campos estándar se tipan por ti
Algunos campos tienen un significado que definimos nosotros, no tú, y su tipo es fijo:
- Atributos de plataforma que escribe el sistema:
lastEventTime(una fecha, aunque se almacene como texto),total_purchases,ml_churn_score,is_purchase_overdue. - Propiedades de comercio estándar:
price,total,revenue,quantity,tax,shipping,discount,subtotal,value.
No necesitas definirlos y no puedes cambiarlos. Es deliberado: si el mismo campo estándar pudiera significar un número en un espacio de trabajo y texto en otro, nada construido encima - informes, recomendaciones, ingresos - podría confiar en él.
Si alguno de esos nombres significa otra cosa en tus datos, envíalo con un
nombre distinto. Un price que en realidad es una etiqueta debería ser
price_label. Todo lo que quede fuera de estas listas lo defines tú.
Ejemplos de atributos habituales
E-commerce
joryio.setAttributes({
// Información de la cuenta
accountType: "premium",
memberSince: "2024-01-15",
// Historial de compras
totalOrders: 15,
lastOrderDate: "2024-01-20",
lifetimeValue: 2500.00,
avgOrderValue: 166.67,
// Preferencias
favoriteCategory: "electronics",
preferredShipping: "express",
// Interacción
cartAbandoned: false,
wishlistItems: 5,
reviewsWritten: 3
});
SaaS
joryio.setAttributes({
// Suscripción
plan: "pro",
billingCycle: "monthly",
subscriptionStatus: "active",
trialEndsAt: "2024-02-15T23:59:59Z",
mrr: 99,
// Uso
loginCount: 45,
lastLoginDate: "2024-01-20",
featuresUsed: ["export", "api", "integrations"],
apiCallsThisMonth: 10500,
storageUsedGB: 15.5,
// Equipo
teamSize: 8,
role: "admin",
companyName: "Acme Corp"
});
Medios y contenido
joryio.setAttributes({
// Suscripción
subscriptionTier: "premium",
contentAccessLevel: "unlimited",
// Interacción
articlesRead: 125,
videosWatched: 45,
podcastsListened: 30,
favoriteTopics: ["technology", "business"],
// Comportamiento
avgSessionDuration: 25.5,
lastVisit: "2024-01-20T14:30:00Z",
deviceType: "mobile"
});
Actualizar atributos
Combinar frente a reemplazar
Combinar (predeterminado): actualiza los campos especificados.
// Atributos iniciales
{
firstName: "John",
plan: "free",
country: "US"
}
// Actualiza atributos (se combinan con los existentes)
joryio.setAttributes({
plan: "premium"
});
// Resultado (plan actualizado, se conservan los demás)
{
firstName: "John",
plan: "premium", // Updated
country: "US" // Preserved
}
Al llamar a setAttributes(), los atributos nuevos se combinan con los existentes. Los atributos existentes que no se mencionan en la llamada se conservan.
Incrementar/reducir
Para los contadores, incrementa el valor en vez de obtener el valor actual:
// Incorrecto: puede producirse una condición de carrera
const current = await getUserAttribute('loginCount');
joryio.setAttributes({ loginCount: current + 1 });
// Correcto: incremento atómico
joryio.incrementAttribute('loginCount', 1);
// Reducir
joryio.incrementAttribute('creditsRemaining', -10);
Atributos de matriz
Puedes almacenar y manipular matrices como valores de atributos:
// Configura un atributo de matriz
joryio.setAttributes({
tags: ['vip', 'early-adopter'],
interests: ['technology', 'sports']
});
// Add to array (only adds if value doesn't already exist)
joryio.addToArray('tags', 'premium');
// Resultado: ['vip', 'early-adopter', 'premium']
// Add duplicate (no-op, prevents duplicates)
joryio.addToArray('tags', 'vip');
// Resultado: ['vip', 'early-adopter', 'premium'] (sin cambios)
// Remove from array
joryio.removeFromArray('tags', 'early-adopter');
// Resultado: ['vip', 'premium']
Detalles de los métodos:
| Método | Descripción |
|---|---|
addToArray(key, value) | Añade el valor a la matriz si aún no existe (evita duplicados). Crea una matriz nueva si el atributo no existe. |
removeFromArray(key, value) | Elimina todas las instancias del valor de la matriz. Advierte si el atributo no es una matriz. |
Los atributos de matriz son ideales para:
- Etiquetas de usuario:
['vip', 'trial', 'beta-tester']. - Intereses:
['sports', 'technology', 'fashion']. - Productos comprados:
['prod_123', 'prod_456']. - Feature flags:
['feature-a', 'feature-b']. - Roles:
['admin', 'editor'].
Usar atributos para segmentar
Crea segmentos basados en atributos:
Filtro de atributo sencillo
plan equals "premium"
Comparación numérica
lifetimeValue >= 1000
totalPurchases > 5
age between 25 and 45
Filtros basados en fechas
signupDate is within last 30 days
trialEndsAt is within next 7 days
lastLoginDate is more than 14 days ago
Coincidencia de cadenas
email contains "@company.com"
country equals "US"
firstName exists
plan is not "free"
Filtros de matriz
tags contains "vip"
interests contains any of ["technology", "business"]
Personalizar mensajes
Usa atributos en el contenido de las campañas:
Personalización de email
Hola, {{firstName}}:
Tu plan {{plan}} incluye estas ventajas:
...
{{#if trialEndsAt}}
Tu prueba termina el {{trialEndsAt}}. ¡Mejora ahora!
{{/if}}
Contenido dinámico
{{#if plan == "free"}}
<p>¡Mejora a Premium para disfrutar de más funciones!</p>
{{else}}
<p>¡Gracias por ser miembro del plan {{plan}}!</p>
{{/if}}
Bloques condicionales
{{#if totalPurchases > 10}}
<div class="vip-offer">
Como cliente valioso, aquí tienes una oferta exclusiva...
</div>
{{/if}}
Prácticas recomendadas para atributos
1. Usa nombres descriptivos
Correcto:
{
subscriptionTier: "premium",
lifetimeValueUSD: 1250.00,
emailVerified: true
}
Incorrecto:
{
sub: "p",
ltv: 1250,
verified: 1
}
2. Mantén la coherencia
Usa la misma convención de nombres:
Correcto (camelCase):
{
firstName: "John",
lastName: "Doe",
signupDate: "2024-01-15"
}
Incorrecto (mezclado):
{
first_name: "John",
LastName: "Doe",
"signup-date": "2024-01-15"
}
3. Usa tipos adecuados
Correcto:
{
age: 30, // Número
isPremium: true, // Booleano
signupDate: "2024-01-15", // Cadena de fecha ISO
tags: ["vip", "beta"] // Matriz
}
Incorrecto:
{
age: "30", // Cadena en vez de número
isPremium: "true", // Cadena en vez de booleano
signupDate: 1705276800, // Marca de tiempo en vez de ISO
tags: "vip,beta" // Cadena en vez de matriz
}
4. No almacenes datos sensibles
No almacenes nunca:
- Contraseñas ni hashes de contraseñas.
- Números completos de tarjetas de crédito.
- Números de seguridad social.
- Datos bancarios.
- Información de salud.
Es seguro almacenar:
- Los últimos cuatro dígitos de la tarjeta.
- El tipo de método de pago ("visa", "mastercard").
- Tokens cifrados o con hash.
- Información pública del perfil.
5. Mantén cortos los nombres de atributos
Correcto: plan, ltv, mrr
Incorrecto: currentSubscriptionPlanTierLevel
Nombres de atributos reservados
Evita usar estos nombres reservados:
userId,user_id,idemailphonecreatedAt,created_atupdatedAt,updated_at$app_id,$platform(con el prefijo $)
Actualizaciones masivas
Actualiza atributos de varios usuarios:
// API: Bulk update - each field of POST /users/track accepts an array (attributes: max 1000)
POST /users/track
{
"attributes": [
{
"externalId": "user_1",
"attributes": { "plan": "premium" }
},
{
"externalId": "user_2",
"attributes": { "plan": "enterprise" }
}
]
}
Límites de atributos
| Límite | Valor |
|---|---|
| Máximo de atributos por usuario | 200 |
| Longitud máxima del nombre de atributo | 100 caracteres |
| Longitud máxima de valor de cadena | 10.000 caracteres |
| Longitud máxima de matriz | 100 elementos |
| Profundidad máxima de anidamiento | 5 niveles |
Solución de problemas
Los atributos no aparecen
- Comprueba la ortografía del nombre del atributo.
- Verifica que el usuario se haya identificado.
- Activa el modo de depuración para ver las llamadas de API.
- Comprueba si hay errores de API en la respuesta.
El atributo no se actualiza
- Asegúrate de usar la combinación, no el reemplazo.
- Comprueba que el tipo de datos coincida.
- Verifica que el nombre del atributo sea correcto.
- Comprueba los límites de velocidad.
La segmentación no funciona
- Verifica que el atributo exista para los usuarios.
- Comprueba el tipo de datos del atributo.
- Prueba la lógica de filtro con usuarios conocidos.
- Asegúrate de que los valores de atributo coincidan exactamente con el filtro.