Motor de reglas
El motor de reglas es el corazon de Presage. Evalua condiciones declarativas contra el UserContext actual y devuelve la accion apropiada.
Como funcionan las reglas
Sección titulada «Como funcionan las reglas»Cada regla apunta a un adaptation point especifico (identificado por adaptationId). Cuando un componente resuelve ese adaptation point, el motor:
- Filtra las reglas por
adaptationId - Ordena por
priority(la mas alta primero) - Evalua las condiciones de cada regla contra el
UserContext - Devuelve la accion de la primera regla que coincida
- Devuelve
nullsi ninguna regla coincide (el componente usa su variante por defecto)
const rule: AdaptationRule = { id: 'enterprise-advanced-dashboard', adaptationId: 'dashboard', priority: 10, conditions: { all: [ { field: 'traits.plan', operator: 'eq', value: 'enterprise' }, { field: 'signals.sessionCount', operator: 'gte', value: 10 }, ], }, action: { type: 'show', variantId: 'advanced' },}Los 14 operadores
Sección titulada «Los 14 operadores»Igualdad
Sección titulada «Igualdad»| Operador | Descripcion | Ejemplo |
|---|---|---|
eq | Igualdad estricta (===) | { field: 'traits.role', operator: 'eq', value: 'admin' } |
neq | Desigualdad estricta (!==) | { field: 'traits.plan', operator: 'neq', value: 'free' } |
Comparacion numerica
Sección titulada «Comparacion numerica»Todos los operadores numericos requieren que tanto el valor del campo como el valor de comparacion sean numeros.
| Operador | Descripcion | Ejemplo |
|---|---|---|
gt | Mayor que | { field: 'signals.sessionCount', operator: 'gt', value: 5 } |
gte | Mayor o igual que | { field: 'signals.totalEvents', operator: 'gte', value: 100 } |
lt | Menor que | { field: 'signals.daysSinceSignup', operator: 'lt', value: 7 } |
lte | Menor o igual que | { field: 'traits.companySize', operator: 'lte', value: 50 } |
Pertenencia a conjuntos
Sección titulada «Pertenencia a conjuntos»| Operador | Descripcion | Ejemplo |
|---|---|---|
in | El valor esta en el array proporcionado | { field: 'traits.plan', operator: 'in', value: ['pro', 'enterprise'] } |
notIn | El valor no esta en el array proporcionado | { field: 'traits.role', operator: 'notIn', value: ['viewer', 'guest'] } |
Cadenas y arrays
Sección titulada «Cadenas y arrays»| Operador | Descripcion | Ejemplo |
|---|---|---|
contains | La cadena incluye la subcadena, o el array incluye el elemento | { field: 'traits.company', operator: 'contains', value: 'Corp' } |
notContains | Inverso de contains | { field: 'traits.locale', operator: 'notContains', value: 'zh' } |
Existencia
Sección titulada «Existencia»| Operador | Descripcion | Ejemplo |
|---|---|---|
exists | El valor no es undefined ni null | { field: 'traits.company', operator: 'exists', value: true } |
notExists | El valor es undefined o null | { field: 'traits.signupDate', operator: 'notExists', value: true } |
| Operador | Descripcion | Ejemplo |
|---|---|---|
between | El numero esta dentro de [min, max] (inclusivo) | { field: 'signals.sessionCount', operator: 'between', value: [5, 20] } |
Coincidencia de patrones
Sección titulada «Coincidencia de patrones»| Operador | Descripcion | Ejemplo |
|---|---|---|
matches | La cadena coincide con una expresion regular | { field: 'traits.company', operator: 'matches', value: '^Acme.*' } |
Logica booleana
Sección titulada «Logica booleana»all — AND
Sección titulada «all — AND»Todas las condiciones del array deben coincidir:
conditions: { all: [ { field: 'traits.role', operator: 'eq', value: 'admin' }, { field: 'traits.plan', operator: 'eq', value: 'enterprise' }, { field: 'signals.sessionCount', operator: 'gte', value: 5 }, ],}any — OR
Sección titulada «any — OR»Al menos una condicion debe coincidir:
conditions: { any: [ { field: 'maturity', operator: 'eq', value: 'new' }, { field: 'maturity', operator: 'eq', value: 'onboarding' }, ],}not — Negacion
Sección titulada «not — Negacion»Invierte el resultado de una condicion o grupo:
conditions: { not: { field: 'traits.plan', operator: 'eq', value: 'free' },}Condiciones anidadas
Sección titulada «Condiciones anidadas»Los grupos pueden anidarse para expresar logica compleja:
// (admin AND enterprise) OR (power user with 50+ sessions)conditions: { any: [ { all: [ { field: 'traits.role', operator: 'eq', value: 'admin' }, { field: 'traits.plan', operator: 'eq', value: 'enterprise' }, ], }, { all: [ { field: 'maturity', operator: 'eq', value: 'power' }, { field: 'signals.sessionCount', operator: 'gte', value: 50 }, ], }, ],}Ordenamiento por prioridad
Sección titulada «Ordenamiento por prioridad»Las reglas se evaluan de mayor prioridad a menor. La primera coincidencia gana.
const rules = [ { id: 'vip-override', adaptationId: 'dashboard', priority: 100, // Evaluated first conditions: { all: [{ field: 'traits.role', operator: 'eq', value: 'vip' }] }, action: { type: 'show', variantId: 'vip-dashboard' }, }, { id: 'enterprise-dashboard', adaptationId: 'dashboard', priority: 50, // Evaluated second conditions: { all: [{ field: 'traits.plan', operator: 'eq', value: 'enterprise' }] }, action: { type: 'show', variantId: 'advanced' }, }, { id: 'default-dashboard', adaptationId: 'dashboard', priority: 1, // Catch-all conditions: {}, // Empty group matches everything action: { type: 'show', variantId: 'standard' }, },]Rutas de campos
Sección titulada «Rutas de campos»Los campos usan notacion de punto para acceder a valores anidados en el UserContext:
| Ruta | Se resuelve a |
|---|---|
maturity | context.maturity (una cadena) |
traits.role | context.traits.role |
traits.companySize | context.traits.companySize |
signals.sessionCount | context.signals.sessionCount |
signals.featureUsage.export | context.signals.featureUsage.export |
signals.clickMap.nav-settings | context.signals.clickMap['nav-settings'] |
Tipos de accion
Sección titulada «Tipos de accion»Selecciona una variante especifica para renderizar:
action: { type: 'show', variantId: 'guided-tour' }Oculta el adaptation point completamente:
action: { type: 'hide' }reorder
Sección titulada «reorder»Reordena elementos (usado con AdaptiveNav):
action: { type: 'reorder', order: ['analytics', 'settings', 'home'] }Pasa props arbitrarios al componente coincidente:
action: { type: 'modify', props: { showBetaBadge: true, maxItems: 10 } }Mejores practicas
Sección titulada «Mejores practicas»- Nombre las reglas de forma descriptiva —
new-user-guided-toures mas claro querule-1 - Use espacios entre prioridades — Deje espacio entre prioridades (10, 20, 30) para poder insertar reglas despues sin renumerar
- Mantenga las condiciones poco profundas — Los arboles booleanos profundamente anidados son dificiles de depurar; prefiera estructuras mas planas
- Pruebe las reglas de forma aislada — La clase
RulesEnginepuede instanciarse directamente para pruebas unitarias - Prefiera
insobre multipleseq—{ operator: 'in', value: ['pro', 'enterprise'] }es mas limpio que dos condicionesany