Regla simple
Una regla responde a una sola pregunta del negocio. Si necesita explicarse en dos párrafos, está mal escrita. Si requiere conocer la implementación interna del motor, está mal escrita. Lectura ejecutiva en menos de 30 segundos.
Estándar oficial para escribir reglas que convierten KPIs en tensiones, acciones, responsables, evidencia e impacto Score. Sin código hardcodeado. Auditable, versionable, testeable, segura.
FARO-CFG-001 define cómo se escriben las reglas que convierten datos en tensiones ejecutivas. Las reglas viven como configuración YAML, no como código de aplicación. El motor evaluador las lee, las valida, las ejecuta y registra cada disparo con trazabilidad completa.
El loop conceptual que FARO opera con estas reglas es siempre el mismo:
--- pipeline conceptual --- KPIs → señales relevantes (delta, percentil, valor absoluto) → condiciones (operadores comparados contra umbral) → reglas (cruce de condiciones bajo all/any/none) → tensiones (diagnóstico ejecutivo accionable) → acciones recomendadas (códigos ACT-*) → responsables (rol asignado + aprobador) → evidencia requerida (códigos EVD-*) → impacto sobre FARO Score (negativo, acotado)
Decisión arquitectónica. FARO Connect no detecta tensiones con if/else en código de aplicación. Cada tensión se dispara porque una regla YAML versionada y testeada cruzó condiciones declarativas sobre KPIs canónicos. Si el código tuviera la lógica adentro, ninguna dirección podría auditarla; tampoco un nuevo desarrollador podría leerla; tampoco un cliente podría ajustarla por industria sin un release. El YAML es la única forma honesta de hacer esto.
Comparación rápida entre las dos arquitecturas posibles:
| Dimensión | Reglas hardcodeadas | Reglas DSL YAML (FARO) |
|---|---|---|
| Quién las edita | Solo dev con commit | Producto + dirección (revisable en repo) |
| Versionado | git diff sobre código fuente | version: N + change_reason dentro del YAML |
| Auditable por dirección | No (es código) | Sí (es configuración legible) |
| Test declarativo | Requiere framework de test | Bloque tests: dentro del propio YAML |
| Ajuste por industria | Forks de código o feature flags | Subcarpeta industry/*/ |
| Ajuste por cliente | Branches o ramas separadas | company_id en metadata + overrides |
| Trazabilidad de disparo | Logs si el dev los puso | rule_evaluations con regla, versión, payload, severidad |
| Reversibilidad | Rollback de deploy | status: inactive sin tocar binario |
Este documento define las 10 secciones obligatorias del YAML, los 13 operadores soportados, la matriz de severidad, las políticas de datos faltantes y las 11 reglas iniciales que componen el núcleo del MVP (TNS-001 a TNS-011). Las 19 reglas restantes (TNS-012 a TNS-030) viven en archivos secundarios y se activan por industria; quedan referenciadas en catalogo-tensiones-mvp.html.
El contexto operativo asumido en todos los ejemplos es Empresa Demo Cuyo S.A., la empresa demo del pack FARO Connect: rubro distribución comercial multisucursal, datos cargados en el schema faro_demo, evaluación semanal de reglas comerciales, financieras, stock, compras y ejecución.
Antes de escribir un solo YAML, el equipo acuerda estos cuatro atributos. Una regla que no cumple los cuatro no entra al catálogo MVP, aunque funcione técnicamente.
Una regla responde a una sola pregunta del negocio. Si necesita explicarse en dos párrafos, está mal escrita. Si requiere conocer la implementación interna del motor, está mal escrita. Lectura ejecutiva en menos de 30 segundos.
Cada regla conserva rule_code, tension_code, version, author, created_at, updated_at, status, change_reason. Ningún disparo histórico queda sin trazabilidad de qué regla lo generó.
Una regla no puede inventar datos, no puede consultar empresas cruzadas, no puede modificar datos RAW, no puede cerrar acciones automáticamente, no puede cambiar Score sin registro, no puede llamar a IA para decidir si dispara, no puede ocultar baja confianza del dato.
Cambiar la lógica nunca pisa la versión anterior: se crea version: N+1 con change_reason explícito. Las reglas archivadas se mantienen disponibles para explicar tensiones históricas. Borrar reglas activas con histórico está prohibido.
Cada regla incluye bloque tests: con mínimo dos casos: dispara y no dispara. Reglas críticas suman caso de severidad crítica y caso de baja confianza de datos. Sin tests, la regla queda en status: draft.
El motor registra en faro.rule_evaluations cada ejecución: regla, versión, snapshot de KPIs evaluados, condiciones pasadas, severidad calculada, tensión generada (si la hubo), confianza promedio del dato, timestamp.
Una regla apunta siempre a KPIs canónicos (catálogo en construcción · ver sección 11), acciones canónicas (ACT-*), responsables por rol y evidencia (EVD-*). No hay strings sueltos; todo resuelve contra catálogos.
Toda regla declara minimum_confidence_score y política ante datos faltantes. La regla del MVP por defecto: una alerta menos antes que una falsa certeza. missing_data_policy: do_not_trigger.
La regla no es una caja negra. Cuando FARO dispara TNS-001, la dirección debe poder leer: ventas netas subieron 18%, margen bruto cayó 24%, descuento promedio subió 98%, confianza del dato 86%, severidad calculada crítica, acción sugerida revisar política de descuentos, responsable comercial, evidencia requerida matriz de descuentos aprobada. Eso es FARO. Lo otro es un semáforo con traje.
Antes de leer una regla YAML, fijar el vocabulario. El motor distingue estos seis niveles y los serializa en tablas separadas; mezclarlos es la causa número uno de productos de BI que parecen funcionar pero no se pueden gobernar.
| Elemento | Qué es | Tabla destino | Ejemplo |
|---|---|---|---|
| KPI | Medición del negocio en un período | faro.kpi_snapshots | Margen bruto = 21% (semana 22) |
| Señal | Cambio o condición relevante sobre un KPI | Derivada en evaluación | Margen cayó más de 10% vs período anterior |
| Regla | Cruce declarativo de señales bajo all/any/none | faro.rule_definitions | Ventas suben + margen baja + descuento sube |
| Tensión | Diagnóstico ejecutivo accionable | faro.tensions | Crecimiento no rentable (TNS-001) |
| Acción | Qué debe hacerse para resolver | faro.actions | Revisar política de descuentos (ACT-COM-001) |
| Evidencia | Cómo se prueba el cierre operativo | faro.evidence | Cambio de política aprobado (EVD-007) |
El YAML que define la regla conoce todos los niveles: declara qué KPIs necesita (sección data_requirements.required_kpis), qué condiciones evalúa (conditions), qué tensión genera (tension_code), qué acciones sugiere (output.recommended_actions) y qué evidencia exige (output.evidence_required). Por eso una regla bien escrita es autosuficiente para auditoría: no hay nada más que leer.
Las reglas viven bajo config/rules/mvp/. Una regla por archivo. Una carpeta por área funcional. Una subcarpeta opcional por industria. Una subcarpeta shared/ para thresholds, severity matrix y archivos transversales.
config/ └── rules/ └── mvp/ ├── commercial/ # reglas comerciales │ ├── TNS-001_crecimiento_no_rentable.yaml │ ├── TNS-002_descuento_fuera_politica.yaml │ └── TNS-003_vendedor_erosiona_margen.yaml ├── finance/ # reglas financieras y cobranza │ ├── TNS-004_venta_sin_caja.yaml │ └── TNS-005_mora_critica_cliente.yaml ├── stock/ # reglas de inventario │ ├── TNS-006_stock_critico_alta_rotacion.yaml │ └── TNS-007_stock_inmovilizado.yaml ├── purchasing/ # reglas de compras │ └── TNS-008_reposicion_reactiva.yaml ├── execution/ # reglas de ejecución de acciones │ ├── TNS-009_acciones_vencidas.yaml │ └── TNS-010_acciones_sin_evidencia.yaml ├── data_quality/ # reglas de calidad de dato │ └── TNS-011_baja_confianza_dato.yaml ├── industry/ # overrides por industria │ ├── retail/ │ ├── construction_supplies/ │ ├── services/ │ └── manufacturing/ └── shared/ # archivos transversales ├── thresholds.yaml # umbrales reutilizables ├── severity_matrix.yaml # pesos por severidad ├── score_impact.yaml # mapping a FARO Score ├── evidence_types.yaml # tipos de evidencia válidos └── role_mapping.yaml # roles canónicos del MVP
Convención de nombres: TNS-NNN_slug_descriptivo.yaml. El código TNS al inicio facilita encontrar el archivo por código; el slug humano facilita encontrarlo por concepto. La extensión es siempre .yaml (no .yml) para consistencia con el parser.
Toda regla del MVP cumple este contrato. El parser FARO-ENG-002 valida estas 10 secciones contra JSON Schema (sección 10) antes de cargar la regla en faro.rule_definitions.
| Sección | Propósito | Obligatorio | Validación |
|---|---|---|---|
| metadata | Identificación de regla | Sí | rule_code, tension_code, version, status, name, description |
| scope | Dónde aplica la regla | Sí | company_types, modules, frequency, evaluation_window, comparison_window |
| data_requirements | KPIs y políticas de dato | Sí | required_kpis, minimum_confidence_score, missing_data_policy, stale_data_policy |
| conditions | Lógica de disparo | Sí | all / any / none con tuplas kpi.metric.operator.value |
| severity | Severidad por defecto y escalamientos | Sí | default en {low, medium, high, critical} + bloques escalation.when |
| output | Qué emite la regla cuando dispara | Sí | create_tension, title, diagnosis_template, recommended_actions, assign_to_role, evidence_required, default_sla_days |
| score_impact | Impacto sobre FARO Score | Sí | base negativo + max negativo (cap absoluto por tensión) |
| governance | Versionado y auditoría | Opcional | author, created_at, updated_at, change_reason |
| demo_relevance | Si la regla dispara en demo de Empresa Demo Cuyo S.A. | Opcional | Boolean + nota explicativa |
| tests | Casos declarativos de validación | Sí | Array de {name, input, expect} con mínimo 2 casos (dispara + no dispara) |
Plantilla de referencia que muestra todas las secciones en orden, con comentarios explicativos. Las reglas reales (sección 8) siguen exactamente esta estructura.
# ============================================================ # SECCIÓN 1 · metadata # ============================================================ rule_code: RULE-TNS-XXX tension_code: TNS-XXX version: 1 status: active # draft | active | inactive | archived name: Nombre legible de la regla description: Qué detecta la regla, en una línea. # ============================================================ # SECCIÓN 2 · scope # ============================================================ scope: company_types: [commercial, retail, distribution] modules: [sales, margin] frequency: weekly # daily | weekly | monthly evaluation_window: current_period comparison_window: previous_period # ============================================================ # SECCIÓN 3 · data_requirements # ============================================================ data_requirements: required_kpis: [KPI-SAL-001, KPI-SAL-002] minimum_confidence_score: 75 missing_data_policy: do_not_trigger stale_data_policy: warn # ============================================================ # SECCIÓN 4 · conditions # ============================================================ conditions: all: # todas deben cumplirse - kpi: KPI-SAL-001 metric: delta_pct operator: ">=" value: 0.10 # ============================================================ # SECCIÓN 5 · severity # ============================================================ severity: default: high escalation: - when: all: - kpi: KPI-SAL-002 metric: value operator: "<=" value: 0.22 set: critical # ============================================================ # SECCIÓN 6 · output # ============================================================ output: create_tension: true title: Título ejecutivo de la tensión diagnosis_template: > Descripción ejecutiva del diagnóstico, lista para mostrar en bandeja. recommended_actions: [ACT-COM-001, ACT-COM-002] assign_to_role: commercial_manager approver_role: general_manager evidence_required: [EVD-007, EVD-012] default_sla_days: 7 # ============================================================ # SECCIÓN 7 · score_impact (anidado dentro de output) # ============================================================ score_impact: base: -8 max: -12 # ============================================================ # SECCIÓN 8 · governance (opcional) # ============================================================ governance: author: tomas.pombo created_at: "2026-05-31" updated_at: "2026-05-31" change_reason: Creación inicial MVP. # ============================================================ # SECCIÓN 9 · demo_relevance (opcional) # ============================================================ demo_relevance: empresa_demo_cuyo_sa: true note: Dispara en demo semana 22 con severidad crítica. # ============================================================ # SECCIÓN 10 · tests (obligatoria) # ============================================================ tests: - name: caso_que_dispara input: KPI-SAL-001: {delta_pct: 0.18, confidence_score: 90} KPI-SAL-002: {value: 0.21, delta_pct: -0.24, confidence_score: 88} expect: triggered: true severity: critical - name: caso_que_no_dispara input: KPI-SAL-001: {delta_pct: 0.02, confidence_score: 90} KPI-SAL-002: {value: 0.28, delta_pct: 0.01, confidence_score: 88} expect: triggered: false
El DSL FARO acota los operadores deliberadamente. Más operadores = más caminos de falsos positivos. El MVP cubre todos los casos de las 30 tensiones con este set acotado.
conditions)| Combinador | Significado | Uso típico |
|---|---|---|
| all | Conjunción | Todas las condiciones del array deben cumplirse. Es el combinador por defecto del MVP. |
| any | Disyunción | Al menos una condición del array debe cumplirse. Usar con cuidado: tiende a generar falsos positivos. |
| none | Negación de disyunción | Ninguna condición del array debe cumplirse. Útil para excepciones declarativas. |
kpi.metric)| Operador | Uso | Ejemplo declarativo |
|---|---|---|
| > | Mayor que | kpi: KPI-FIN-003 · metric: value · operator: ">" · value: 30 |
| >= | Mayor o igual | kpi: KPI-SAL-001 · metric: delta_pct · operator: ">=" · value: 0.10 |
| < | Menor que | kpi: KPI-STK-001 · metric: value · operator: "<" · value: 7 |
| <= | Menor o igual | kpi: KPI-SAL-002 · metric: delta_pct · operator: "<=" · value: -0.10 |
| == | Igual | kpi: KPI-ACT-004 · metric: assigned · operator: "==" · value: false |
| != | Distinto | kpi: KPI-DQ-002 · metric: source_status · operator: "!=" · value: "ok" |
| between | Entre dos valores | kpi: KPI-FIN-003 · metric: value · operator: "between" · value: [30, 45] |
| in | Dentro de lista | kpi: KPI-CUS-001 · metric: segment · operator: "in" · value: ["A", "B"] |
| not_in | Fuera de lista | kpi: KPI-SAL-009 · metric: branch · operator: "not_in" · value: ["pilot"] |
| exists | Existe (no null) | kpi: KPI-ACT-001 · metric: due_date · operator: "exists" |
| missing | Falta (null) | kpi: KPI-ACT-004 · metric: owner · operator: "missing" |
| changed_by_pct | Cambió cierto porcentaje vs ventana previa | kpi: KPI-SAL-001 · metric: value · operator: "changed_by_pct" · value: 0.10 |
| older_than_days | Antigüedad mayor a X días | kpi: KPI-DQ-002 · metric: last_sync · operator: "older_than_days" · value: 2 |
kpi.metric.operator.valueToda condición del DSL FARO se escribe como una tupla de cuatro campos: el KPI (código canónico contra catálogo de KPIs), la métrica (qué dimensión del snapshot leer: value, delta_pct, percentile, confidence_score, etc.), el operador (uno de los 13 anteriores) y el valor de comparación (número, string, lista o array según operador). Esta notación reemplaza la sintaxis informal de la biblioteca extendida v2 (la de 300 tensiones) y es el único formato aceptado por el parser FARO-ENG-002.
Políticas ante datos faltantes. Cada regla declara qué hacer si los KPIs requeridos no están disponibles. El MVP usa por defecto missing_data_policy: do_not_trigger + stale_data_policy: warn. Las 5 políticas soportadas son: do_not_trigger (no dispara), trigger_with_warning (dispara pero marca baja confianza), create_data_quality_tension (genera tensión TNS-028 en lugar de la original), use_last_available (usa último dato disponible) y manual_review (encola para revisión humana). Para dirección, una alerta menos antes que una falsa certeza.
La severidad de una tensión disparada se calcula así: primero severity.default, luego se evalúan los bloques severity.escalation[].when en orden; el último que matchea define la severidad efectiva. Si ninguno matchea, queda la default.
| Severidad | Significado | Priority base | SLA default | Score impact base |
|---|---|---|---|---|
| low | Observación · no requiere acción inmediata | 25 | 14 días | −1 |
| medium | Requiere seguimiento operativo | 50 | 10 días | −3 |
| high | Requiere acción | 75 | 5 días | −6 |
| critical | Requiere acción prioritaria + posible escalamiento a dirección | 90 | 2 días | −10 |
Cuando una regla no declara severity.escalation, el motor puede inferir severidad efectiva combinando el impacto del KPI (delta o valor absoluto) con la urgencia operativa (frecuencia, ventana, criticidad del rubro). La matriz es referencial; cada regla puede sobreescribir explícitamente.
| Impacto · Urgencia → | Urgencia baja | Urgencia media | Urgencia alta |
|---|---|---|---|
| Bajo | low | medium | medium |
| Medio | medium | medium | high |
| Alto | high | high | critical |
| Crítico | critical | critical | critical |
La regla TNS-001 (Crecimiento no rentable) declara severity default high y un bloque de escalamiento a critical cuando el margen cae por debajo de 22% y el descuento supera 10%. Así se escribe:
severity: default: high escalation: - when: all: - kpi: KPI-SAL-002 # margen bruto metric: value operator: "<=" value: 0.22 # margen <= 22% - kpi: KPI-SAL-003 # descuento promedio metric: value operator: ">=" value: 0.10 # descuento >= 10% set: critical # sube severidad a critical
Cap policy. Aunque varias tensiones disparen en el mismo período, el impacto agregado sobre FARO Score está capeado: max_negative_score_impact_per_period: -25 y max_single_tension_impact: -15. Evita que Score caiga 80 puntos en una semana por una racha de reglas correlacionadas. Vive en shared/score_impact.yaml.
Estas son las once reglas que componen el núcleo activo del MVP. Cubren los cinco bloques: comercial (3), finanzas (2), stock (2), compras (1), ejecución (2) y calidad de dato (1). Las 19 restantes (TNS-012 a TNS-030) viven en archivos secundarios y se referencian en catalogo-tensiones-mvp.html.
rule_code: RULE-TNS-001 tension_code: TNS-001 version: 1 status: active name: Crecimiento no rentable description: Detecta aumento de ventas acompañado de caída de margen y aumento de descuentos. scope: company_types: [commercial, retail, distribution, construction_supplies] modules: [sales, margin, discounts] frequency: weekly evaluation_window: current_period comparison_window: previous_period data_requirements: required_kpis: [KPI-SAL-001, KPI-SAL-002, KPI-SAL-003] minimum_confidence_score: 75 missing_data_policy: do_not_trigger stale_data_policy: warn conditions: all: - kpi: KPI-SAL-001 # ventas netas metric: delta_pct operator: ">=" value: 0.10 # crecen >= 10% - kpi: KPI-SAL-002 # margen bruto metric: delta_pct operator: "<=" value: -0.10 # cae >= 10% - kpi: KPI-SAL-003 # descuento promedio metric: delta_pct operator: ">=" value: 0.30 # sube >= 30% severity: default: high escalation: - when: all: - kpi: KPI-SAL-002 metric: value operator: "<=" value: 0.22 - kpi: KPI-SAL-003 metric: value operator: ">=" value: 0.10 set: critical output: create_tension: true title: Crecimiento no rentable diagnosis_template: > Las ventas crecieron, pero el margen cayó y los descuentos aumentaron. La empresa está vendiendo más, pero capturando menos rentabilidad. recommended_actions: [ACT-COM-001, ACT-COM-002, ACT-COM-003] assign_to_role: commercial_manager approver_role: general_manager evidence_required: [EVD-007, EVD-012] default_sla_days: 7 score_impact: base: -8 max: -12 tests: - name: dispara_con_crecimiento_no_rentable input: KPI-SAL-001: {delta_pct: 0.18, confidence_score: 90} KPI-SAL-002: {value: 0.21, delta_pct: -0.24, confidence_score: 88} KPI-SAL-003: {value: 0.12, delta_pct: 0.98, confidence_score: 86} expect: triggered: true severity: critical - name: no_dispara_si_margen_no_cae input: KPI-SAL-001: {delta_pct: 0.18, confidence_score: 90} KPI-SAL-002: {value: 0.29, delta_pct: 0.02, confidence_score: 88} KPI-SAL-003: {value: 0.12, delta_pct: 0.98, confidence_score: 86} expect: triggered: false
Nota. Es la regla insignia del MVP. Dispara en demo de Empresa Demo Cuyo S.A. semana 22 con severidad crítica. Cruza tres KPIs comerciales clásicos cuyo deterioro individual pasa desapercibido en cualquier dashboard tradicional.
rule_code: RULE-TNS-002 tension_code: TNS-002 version: 1 status: active name: Descuento fuera de política description: Detecta descuentos superiores al umbral permitido por política comercial. scope: company_types: [commercial, retail, distribution, construction_supplies] modules: [sales, discounts] frequency: weekly evaluation_window: current_period comparison_window: policy_threshold data_requirements: required_kpis: [KPI-SAL-003] minimum_confidence_score: 75 missing_data_policy: do_not_trigger stale_data_policy: warn conditions: all: - kpi: KPI-SAL-003 metric: value operator: ">=" value: 0.10 # descuento >= 10% severity: default: high escalation: - when: all: - kpi: KPI-SAL-003 metric: value operator: ">=" value: 0.15 # >= 15% escala a crítica set: critical output: create_tension: true title: Descuento fuera de política diagnosis_template: > El descuento promedio superó el umbral permitido. Esto puede erosionar margen aunque la venta parezca positiva. recommended_actions: [ACT-COM-001, ACT-COM-003] assign_to_role: commercial_manager approver_role: general_manager evidence_required: [EVD-007, EVD-012] default_sla_days: 5 score_impact: base: -5 max: -9 tests: - name: dispara_descuento_alto input: KPI-SAL-003: {value: 0.12, confidence_score: 86} expect: triggered: true severity: high - name: no_dispara_descuento_dentro_politica input: KPI-SAL-003: {value: 0.07, confidence_score: 86} expect: triggered: false
Nota. Frecuentemente convive con TNS-001 cuando hay erosión generalizada de modelo comercial. El parser debería emitir warning si ambas disparan en el mismo período para evitar duplicar score impact.
rule_code: RULE-TNS-003 tension_code: TNS-003 version: 1 status: active name: Vendedor erosiona margen description: Detecta vendedores con ventas altas, descuento elevado y margen bajo. scope: company_types: [commercial, retail, distribution, construction_supplies] modules: [sales, margin, discounts, employees] frequency: weekly evaluation_window: current_period dimension: employee # evalúa por vendedor data_requirements: required_kpis: [KPI-SAL-004, KPI-SAL-005, KPI-SAL-006] minimum_confidence_score: 75 missing_data_policy: do_not_trigger stale_data_policy: warn conditions: all: - kpi: KPI-SAL-004 # ventas por vendedor metric: percentile operator: ">=" value: 0.70 # percentil 70+ (top 30%) - kpi: KPI-SAL-005 # margen por vendedor metric: value operator: "<=" value: 0.22 # margen <= 22% - kpi: KPI-SAL-006 # descuento por vendedor metric: value operator: ">=" value: 0.12 # descuento >= 12% severity: default: high escalation: - when: all: - kpi: KPI-SAL-005 metric: value operator: "<=" value: 0.18 set: critical output: create_tension: true title: Vendedor erosiona margen diagnosis_template: > Un vendedor está generando volumen comercial con descuentos altos y margen inferior al objetivo. recommended_actions: [ACT-COM-004, ACT-COM-002, ACT-COM-003] assign_to_role: commercial_manager approver_role: general_manager evidence_required: [EVD-010, EVD-012] default_sla_days: 7 score_impact: base: -6 max: -10 tests: - name: dispara_vendedor_alto_volumen_bajo_margen input: KPI-SAL-004: {percentile: 0.85, confidence_score: 88} KPI-SAL-005: {value: 0.18, confidence_score: 86} KPI-SAL-006: {value: 0.15, confidence_score: 87} expect: triggered: true severity: critical
Nota. Esta regla requiere KPIs dimensionados por empleado (sufijo _by_employee en el snapshot). El parser FARO-ENG-002 valida que los KPIs declarados soporten la dimensión employee en el catálogo (pendiente FARO-SQL-007).
rule_code: RULE-TNS-004 tension_code: TNS-004 version: 1 status: active name: Venta sin conversión a caja description: Detecta ventas relevantes acompañadas de deterioro de cobranza. scope: company_types: [commercial, retail, distribution, services, construction_supplies] modules: [sales, receivables, finance] frequency: weekly evaluation_window: current_period comparison_window: previous_period data_requirements: required_kpis: [KPI-SAL-001, KPI-FIN-001, KPI-FIN-002] minimum_confidence_score: 75 missing_data_policy: do_not_trigger stale_data_policy: warn conditions: all: - kpi: KPI-SAL-001 # ventas netas metric: delta_pct operator: ">=" value: 0.05 - kpi: KPI-FIN-001 # días promedio de cobro metric: delta_pct operator: ">=" value: 0.15 - kpi: KPI-FIN-002 # monto vencido total metric: delta_pct operator: ">=" value: 0.20 severity: default: high escalation: - when: all: - kpi: KPI-FIN-002 metric: delta_pct operator: ">=" value: 0.75 set: critical output: create_tension: true title: Venta sin conversión a caja diagnosis_template: > La empresa vende, pero la cobranza se deteriora. El crecimiento comercial no se está convirtiendo en caja. recommended_actions: [ACT-FIN-001, ACT-FIN-002, ACT-FIN-003] assign_to_role: finance_manager approver_role: general_manager evidence_required: [EVD-011, EVD-006, EVD-012] default_sla_days: 5 score_impact: base: -7 max: -12 tests: - name: dispara_venta_sin_caja input: KPI-SAL-001: {delta_pct: 0.18, confidence_score: 89} KPI-FIN-001: {delta_pct: 0.34, confidence_score: 82} KPI-FIN-002: {delta_pct: 1.11, confidence_score: 82} expect: triggered: true severity: critical
Nota. Esta tensión es la clásica que tira para abajo Score de salud de caja. En Empresa Demo Cuyo S.A. dispara junto con TNS-001: el crecimiento comercial vendido como buena noticia es en realidad descuento + mora.
rule_code: RULE-TNS-005 tension_code: TNS-005 version: 1 status: active name: Mora crítica por cliente description: Detecta clientes con monto vencido relevante y atraso superior al umbral. scope: company_types: [commercial, distribution, services, construction_supplies] modules: [receivables, customers, finance] frequency: weekly evaluation_window: current_period dimension: customer # evalúa por cliente data_requirements: required_kpis: [KPI-FIN-003, KPI-FIN-004] minimum_confidence_score: 75 missing_data_policy: do_not_trigger stale_data_policy: warn conditions: all: - kpi: KPI-FIN-003 # días de atraso por cliente metric: value operator: ">=" value: 30 - kpi: KPI-FIN-004 # monto vencido por cliente metric: value operator: ">=" value: 1000000 severity: default: high escalation: - when: all: - kpi: KPI-FIN-003 metric: value operator: ">=" value: 45 - kpi: KPI-FIN-004 metric: value operator: ">=" value: 3000000 set: critical output: create_tension: true title: Mora crítica por cliente diagnosis_template: > Un cliente acumula deuda vencida relevante y supera el umbral de atraso. recommended_actions: [ACT-FIN-001, ACT-FIN-002] assign_to_role: finance_manager approver_role: general_manager evidence_required: [EVD-011, EVD-006] default_sla_days: 3 score_impact: base: -6 max: -10 tests: - name: dispara_cliente_mora_critica input: KPI-FIN-003: {value: 47, confidence_score: 86} KPI-FIN-004: {value: 3800000, confidence_score: 86} expect: triggered: true severity: critical
Nota. Genera una tensión por cliente moroso, no una agregada. El motor agrupa por customer_id en faro.tensions.dimension_value. Demo Cuyo S.A. dispara tres tensiones TNS-005 simultáneas en demo semana 22.
rule_code: RULE-TNS-006 tension_code: TNS-006 version: 1 status: active name: Stock crítico en productos de alta rotación description: Detecta productos de alta demanda con cobertura insuficiente. scope: company_types: [commercial, retail, distribution, construction_supplies] modules: [stock, sales] frequency: weekly evaluation_window: current_period dimension: product data_requirements: required_kpis: [KPI-STK-001, KPI-STK-003] minimum_confidence_score: 70 missing_data_policy: trigger_with_warning stale_data_policy: warn conditions: all: - kpi: KPI-STK-001 # días de cobertura metric: value operator: "<=" value: 7 - kpi: KPI-STK-003 # rotación del producto metric: percentile operator: ">=" value: 0.70 severity: default: high escalation: - when: all: - kpi: KPI-STK-001 metric: value operator: "<=" value: 3 set: critical output: create_tension: true title: Stock crítico en productos de alta rotación diagnosis_template: > Hay productos de venta relevante con cobertura de stock insuficiente. Existe riesgo de perder ventas o incumplir entregas. recommended_actions: [ACT-STK-001, ACT-STK-002] assign_to_role: stock_manager approver_role: general_manager evidence_required: [EVD-005, EVD-002] default_sla_days: 3 score_impact: base: -6 max: -10 tests: - name: dispara_stock_critico_alta_rotacion input: KPI-STK-001: {value: 4, confidence_score: 78} KPI-STK-003: {percentile: 0.82, confidence_score: 84} expect: triggered: true severity: high
Nota. Usa missing_data_policy: trigger_with_warning en lugar de do_not_trigger porque en stock el dato faltante suele ser síntoma del problema (sistema sin inventario al día). Mejor avisar con confianza baja que silencio.
rule_code: RULE-TNS-007 tension_code: TNS-007 version: 1 status: active name: Stock inmovilizado description: Detecta inventario con alto valor y baja o nula rotación. scope: company_types: [commercial, retail, distribution, construction_supplies] modules: [stock, sales] frequency: weekly evaluation_window: current_period dimension: product data_requirements: required_kpis: [KPI-STK-002, KPI-STK-004] minimum_confidence_score: 70 missing_data_policy: trigger_with_warning stale_data_policy: warn conditions: all: - kpi: KPI-STK-002 # valor inmovilizado metric: value operator: ">=" value: 5000000 - kpi: KPI-STK-004 # días sin rotación metric: value operator: ">=" value: 60 severity: default: medium escalation: - when: all: - kpi: KPI-STK-002 metric: value operator: ">=" value: 10000000 set: high output: create_tension: true title: Stock inmovilizado diagnosis_template: > Hay capital atrapado en inventario con baja rotación. Esto afecta liquidez y capacidad de compra. recommended_actions: [ACT-STK-003, ACT-COM-001] assign_to_role: stock_manager approver_role: general_manager evidence_required: [EVD-010, EVD-012] default_sla_days: 10 score_impact: base: -4 max: -8 tests: - name: dispara_stock_inmovilizado input: KPI-STK-002: {value: 12250000, confidence_score: 75} KPI-STK-004: {value: 90, confidence_score: 72} expect: triggered: true severity: high
Nota. Severidad por defecto medium (no high) porque inmovilización es problema crónico, no urgencia. SLA largo (10 días). Sube a high cuando el valor inmovilizado supera 10M.
rule_code: RULE-TNS-008 tension_code: TNS-008 version: 1 status: active name: Reposición reactiva description: Detecta compras urgentes recurrentes sobre productos críticos. scope: company_types: [commercial, retail, distribution, construction_supplies, manufacturing] modules: [stock, purchasing] frequency: weekly evaluation_window: current_period data_requirements: required_kpis: [KPI-STK-001, KPI-PUR-001] minimum_confidence_score: 70 missing_data_policy: do_not_trigger stale_data_policy: warn conditions: all: - kpi: KPI-STK-001 # cobertura crítica metric: value operator: ">=" value: 3 - kpi: KPI-PUR-001 # órdenes urgentes en período metric: value operator: ">=" value: 2 severity: default: high escalation: - when: all: - kpi: KPI-PUR-001 metric: value operator: ">=" value: 5 set: critical output: create_tension: true title: Reposición reactiva diagnosis_template: > La empresa está comprando tarde o por urgencia, en lugar de anticipar reposición. recommended_actions: [ACT-STK-001, ACT-PUR-001] assign_to_role: purchasing_manager approver_role: general_manager evidence_required: [EVD-005, EVD-010] default_sla_days: 5 score_impact: base: -5 max: -9 tests: - name: dispara_reposicion_reactiva input: KPI-STK-001: {value: 4, confidence_score: 78} KPI-PUR-001: {value: 3, confidence_score: 80} expect: triggered: true severity: high
Nota. El KPI KPI-PUR-001 requiere que el ERP marque qué órdenes de compra son urgentes vs planeadas. Si la integración no lo provee, esta regla queda en estado inactive hasta que el cliente lo habilite.
rule_code: RULE-TNS-009 tension_code: TNS-009 version: 1 status: active name: Acciones vencidas description: Detecta acciones abiertas con fecha vencida. scope: company_types: [all] modules: [actions, workflow] frequency: daily evaluation_window: current_date data_requirements: required_kpis: [KPI-ACT-001] minimum_confidence_score: 80 missing_data_policy: do_not_trigger stale_data_policy: warn conditions: all: - kpi: KPI-ACT-001 # acciones vencidas no cerradas metric: value operator: ">=" value: 1 severity: default: medium escalation: - when: all: - kpi: KPI-ACT-001 metric: value operator: ">=" value: 3 set: high - when: all: - kpi: KPI-ACT-001 metric: value operator: ">=" value: 7 set: critical output: create_tension: true title: Acciones vencidas diagnosis_template: > Existen acciones vencidas que no fueron cerradas ni reprogramadas. La ejecución perdió ritmo. recommended_actions: [ACT-OPS-001, ACT-DIR-001] assign_to_role: general_manager approver_role: director evidence_required: [EVD-004, EVD-012] default_sla_days: 2 score_impact: base: -4 max: -10 tests: - name: dispara_acciones_vencidas input: KPI-ACT-001: {value: 3, confidence_score: 90} expect: triggered: true severity: high
Nota. Tres niveles de escalamiento progresivo (medium → high → critical) según volumen de acciones vencidas. Frecuencia diaria (las demás son weekly), porque ejecución requiere lectura más fina.
rule_code: RULE-TNS-010 tension_code: TNS-010 version: 1 status: active name: Acciones sin evidencia description: Detecta acciones marcadas como cerradas o avanzadas sin evidencia suficiente. scope: company_types: [all] modules: [actions, evidence, workflow] frequency: daily evaluation_window: current_date data_requirements: required_kpis: [KPI-ACT-002] minimum_confidence_score: 80 missing_data_policy: do_not_trigger stale_data_policy: warn conditions: all: - kpi: KPI-ACT-002 # acciones cerradas sin evidencia metric: value operator: ">=" value: 1 severity: default: high escalation: - when: all: - kpi: KPI-ACT-002 metric: value operator: ">=" value: 5 set: critical output: create_tension: true title: Acciones sin evidencia diagnosis_template: > Hay acciones sin evidencia suficiente. El sistema no puede considerar resuelto lo que no está probado. recommended_actions: [ACT-OPS-002, ACT-DIR-001] assign_to_role: general_manager approver_role: director evidence_required: [EVD-004, EVD-012] default_sla_days: 2 score_impact: base: -5 max: -10 tests: - name: dispara_accion_sin_evidencia input: KPI-ACT-002: {value: 2, confidence_score: 90} expect: triggered: true severity: high
Nota. Asignada a general_manager con aprobador director. Es la única tensión del MVP que escala directo a nivel director si reincide, porque el problema no es operativo sino de gobierno.
rule_code: RULE-TNS-011 tension_code: TNS-011 version: 1 status: active name: Baja confianza del dato en KPI crítico description: Detecta cuando un KPI crítico opera con confianza por debajo del umbral. scope: company_types: [all] modules: [data_quality, all] frequency: daily evaluation_window: current_date data_requirements: required_kpis: [KPI-DQ-003] # confianza promedio KPIs críticos minimum_confidence_score: 0 # meta: la tensión justamente trata esto missing_data_policy: trigger_with_warning stale_data_policy: warn conditions: all: - kpi: KPI-DQ-003 metric: value operator: "<=" value: 70 # confianza <= 70% severity: default: high escalation: - when: all: - kpi: KPI-DQ-003 metric: value operator: "<=" value: 60 set: critical output: create_tension: true title: Baja confianza del dato en KPI crítico diagnosis_template: > Hay KPIs críticos operando con confianza de dato inferior al umbral mínimo aceptable. Las decisiones ejecutivas sobre estos KPIs pueden estar mal informadas. recommended_actions: [ACT-DQ-001] assign_to_role: data_owner approver_role: general_manager evidence_required: [EVD-002, EVD-004] default_sla_days: 3 score_impact: base: -5 max: -10 tests: - name: dispara_baja_confianza input: KPI-DQ-003: {value: 62, confidence_score: 95} expect: triggered: true severity: high - name: no_dispara_confianza_aceptable input: KPI-DQ-003: {value: 85, confidence_score: 95} expect: triggered: false
Nota. Es la única regla del MVP cuyo minimum_confidence_score está en 0: meta-regla que justamente trata el problema de baja confianza, así que no puede auto-bloquearse. Asignada a data_owner (admin de empresa o servicio de integración).
Cada área del negocio tiene su carpeta. Una regla vive en exactamente una carpeta funcional (la primaria). Si una regla aplica a varias áreas, sigue siendo un solo archivo; las áreas extras se declaran dentro de scope.modules.
| Carpeta | Propósito | Reglas MVP | Frecuencia típica |
|---|---|---|---|
commercial/ | Reglas comerciales (ventas, margen, descuentos, vendedores) | TNS-001, TNS-002, TNS-003 | weekly |
finance/ | Reglas financieras y cobranza (caja, mora, crédito, facturación) | TNS-004, TNS-005 | weekly |
stock/ | Reglas de inventario (cobertura, rotación, inmovilización) | TNS-006, TNS-007 | weekly |
purchasing/ | Reglas de compras (proveedores, reposición, costos) | TNS-008 | weekly |
execution/ | Reglas de ejecución (acciones, evidencia, workflow) | TNS-009, TNS-010 | daily |
data_quality/ | Reglas de calidad de dato (confianza, frescura, integridad) | TNS-011 | daily |
industry/ | Overrides por industria (retail, construction_supplies, services, manufacturing) | Overrides opcionales | según override |
shared/ | Archivos transversales (thresholds, severity_matrix, score_impact, role_mapping, evidence_types) | Configuración común | n/a |
shared/thresholds.yaml · umbrales reutilizablesEn vez de hardcodear umbrales en cada regla, se definen una vez en shared/thresholds.yaml y se referencian con ${thresholds.X}. Permite ajustar política comercial global sin tocar 30 reglas.
thresholds: sales_growth_relevant_pct: 0.10 sales_drop_relevant_pct: -0.12 margin_drop_warning_pct: -0.08 margin_drop_critical_pct: -0.15 gross_margin_min_warning: 0.24 gross_margin_min_critical: 0.22 discount_warning_pct: 0.08 discount_critical_pct: 0.12 collection_days_warning: 35 collection_days_critical: 45 overdue_amount_warning: 1000000 overdue_amount_critical: 3000000 stock_coverage_warning_days: 10 stock_coverage_critical_days: 5 stock_immobilized_warning_days: 60 stock_immobilized_critical_days: 90 data_confidence_minimum: 75 data_confidence_critical: 60 action_overdue_warning_count: 1 action_overdue_critical_count: 5
shared/role_mapping.yaml · roles canónicosLos roles a los que se asignan tensiones (assign_to_role) se resuelven contra este archivo. Cada rol declara role_codes (roles concretos del schema RBAC) y fallback_role (a quién escala si nadie tiene el rol primario en la empresa).
roles: commercial_manager: role_codes: [commercial_user, area_manager] fallback_role: general_manager finance_manager: role_codes: [finance_user, area_manager] fallback_role: general_manager stock_manager: role_codes: [stock_user, area_manager] fallback_role: general_manager purchasing_manager: role_codes: [area_manager] fallback_role: general_manager general_manager: role_codes: [general_manager] fallback_role: director director: role_codes: [director] fallback_role: company_admin data_owner: role_codes: [company_admin, integration_service] fallback_role: general_manager
Antes de cargar una regla en faro.rule_definitions, el parser FARO-ENG-002 ejecuta cinco validaciones bloqueantes. Una sola falla rechaza la regla con error claro y código de error tipado.
El YAML se valida contra el JSON Schema oficial de FARO Rule Schema MVP. Verifica tipos, requeridos, enums, patterns de códigos (RULE-TNS-[0-9]{3}, TNS-[0-9]{3}), longitudes mínimas y estructura anidada de output, data_requirements, severity.
required: ["rule_code", "tension_code", "version", "status", "name", "description", "scope", "data_requirements", "conditions", "severity", "output", "tests"] rule_code: { type: "string", pattern: "^RULE-TNS-[0-9]{3}$" } tension_code: { type: "string", pattern: "^TNS-[0-9]{3}$" } status: { enum: ["draft", "active", "inactive", "archived"] }
El campo tension_code debe existir en faro.tension_definitions con status = 'active'. Si la regla apunta a un código no canónico, se rechaza con UNKNOWN_TENSION_CODE. Reglas activas que apuntan a tensiones archivadas se bloquean.
SELECT 1 FROM faro.tension_definitions WHERE tension_code = :tension_code AND status = 'active';
Cada elemento de output.recommended_actions debe existir en faro.action_definitions (catálogo canónico de acciones · pendiente FARO-SQL-005). El array no puede estar vacío. Acciones archivadas son rechazadas.
SELECT action_code FROM faro.action_definitions WHERE action_code = ANY(:recommended_actions) AND status = 'active';
Cada elemento de output.evidence_required debe existir en faro.evidence_definitions (catálogo canónico de evidencias · pendiente FARO-SQL-006). El array no puede estar vacío: una regla sin evidencia es una regla sin cierre operativo.
SELECT evidence_code FROM faro.evidence_definitions WHERE evidence_code = ANY(:evidence_required) AND status = 'active';
Cada elemento de data_requirements.required_kpis debe existir en faro.kpi_definitions (catálogo canónico de KPIs · pendiente FARO-SQL-007 a generar). Cada kpi dentro de conditions.all/any/none también. Esta validación es la que cierra la cadena regla → KPI → snapshot.
-- Bloquea reglas que apuntan a KPIs no canónicos. -- Pendiente: faro.kpi_definitions debe existir primero (FARO-SQL-007). SELECT kpi_code FROM faro.kpi_definitions WHERE kpi_code = ANY(:required_kpis) AND status = 'active';
minimum_confidence_score < 60. Si está debajo, dirección puede recibir tensiones poco fiables.triggered: true y otro triggered: false.default: low pero score_impact.base < -5 (escalas inconsistentes).default_sla_days > 7 y severity.default = critical (crítico con SLA de dos semanas no tiene sentido).version > 1 y governance.change_reason está vacío.El DSL referencia KPIs por código (KPI-SAL-001, KPI-FIN-002, etc.) pero el catálogo canónico de KPIs MVP todavía no existe. Hasta que exista, la validación 05 (sección 10) queda en estado soft: emite warning pero no bloquea. Esto debe cerrarse antes del piloto.
Qué falta. El catálogo formal faro.kpi_definitions con todos los códigos KPI-* usados por las 30 reglas. Debe incluir: kpi_code, name, area_code, module_code, unit, dimension (empresa/sucursal/cliente/producto/empleado), aggregation (sum/avg/percentile), source_module, refresh_frequency, confidence_baseline, status.
Por qué importa. Sin este catálogo, una regla puede declarar required_kpis: [KPI-INVENTADO-999] y el parser no lo detecta. El motor evaluador después intenta cargar snapshots y falla en runtime, no en parsing. Convierte un error de configuración en error de producción.
Bloqueo temporal. Mientras FARO-SQL-007 no exista, las validaciones FK contra KPIs son warning en vez de error. La carga de reglas continúa, pero queda registrado en faro.rule_load_warnings. Cuando FARO-SQL-007 se publique, las warnings deberán resolverse y la validación pasa a bloqueante.
KPIs ya referenciados en MVP. Las 11 reglas iniciales usan los siguientes códigos: KPI-SAL-001..006 (ventas, margen, descuentos, ventas/margen/descuento por vendedor), KPI-FIN-001..004 (días de cobro, monto vencido total, días atraso por cliente, monto vencido por cliente), KPI-STK-001..004 (cobertura, valor inmovilizado, rotación, días sin rotación), KPI-PUR-001 (órdenes urgentes), KPI-ACT-001..002 (acciones vencidas, acciones sin evidencia), KPI-DQ-003 (confianza promedio de KPIs críticos). El catálogo debe incluir mínimo estos 16 + los necesarios para TNS-012 a TNS-030.
Acción recomendada. Generar FARO-SQL-007 antes del cierre del MVP. Patrón sugerido: igual que FARO-SQL-004 (catalogo-tensiones-mvp.html), con DDL V027__create_kpi_definitions.sql + seed V028__seed_kpi_definitions_mvp.sql. Después agregar pieza catalogo-kpis-mvp.html al pack NDA con la misma estructura visual que este documento.
El YAML no es un producto en sí mismo. Es la capa que conecta el catálogo de tensiones, el motor evaluador, los catálogos de acciones y evidencia, y la bandeja UI. Estos son los puntos de cruce.
30 tensiones canónicas TNS-001..TNS-030. El campo tension_code de cada YAML debe existir aquí con status = active.
Catálogo canónico MVP de acciones (paralelo al de tensiones pero para ACT-*). Pendiente de construcción.
Catálogo canónico MVP de evidencias (paralelo al de tensiones pero para EVD-*). Pendiente de construcción.
Catálogo canónico MVP de KPIs (sección 11). Necesario para validar data_requirements.required_kpis. Pendiente.
Spec técnica del parser que lee estos YAMLs, valida contra schema y carga en faro.rule_definitions. Pendiente.
Spec del motor que ejecuta las reglas: carga snapshots, valida confianza, evalúa condiciones, calcula severidad, registra evaluación. Pendiente.
300 tensiones extendidas v2. Provee equivalentes conceptuales para las 30 del MVP. El DSL del MVP NO usa la notación vieja de la biblioteca; usa kpi.metric.operator.value de CFG-001.
DDL completo del sistema FARO Connect. Incluye faro.rule_definitions donde se guarda cada YAML convertido a JSON y faro.rule_evaluations donde se registran las ejecuciones.
Este documento es la base de FARO-ENG-002 (parser YAML) y FARO-ENG-003 (motor evaluador). Cerrar el catálogo canónico de KPIs (FARO-SQL-007) habilita la cadena completa de validación. Pasá al hub para ver el resto del pack o seguí con el catálogo de tensiones.
→ Volver al hub modelos NDA