Categoría: SoA portable Estado: Propio sano Tamaño esperado: ~6-8k LOC (núcleo IA + reglas calcificadas semánticas)
Producir, para cada documento entrante, una decisión contable completa y ERP-agnostic que cualquier integrador (Holded, Sage, Odoo) pueda traducir a operaciones nativas. La decisión es el StandaloneIntent + InternalAccountingPlan + ContextualDecision + ValidationResult.
Es el activo de largo plazo del producto. Sobrevive a cualquier cambio de ERP de abajo.
tax_key, subdomain, payloads.account_lines paralelo al ledger. Si V3 conserva alguna proyección, vive en I1 como sombra del SoR ajeno, no en M1.El motor sigue las cinco fases del target V2 documentado (que están bien diseñadas):
| Sub-fase | Tipo | Output |
|---|---|---|
| Facts | Lectura DB + LLM compiler | AccountingInputPackage (frozen) |
| Decisions (3 LLMs) | LLM con guards + repair | StandaloneIntent |
| Internal Plan | Determinista (función pura) | InternalAccountingPlan |
| Contextual (kernel + planner + delta_builder) | Determinista | ContextualDecision |
| Validation | Determinista, filtrada a ~5-7 invariantes | ValidationResult |
Cambios respecto al target V2 documentado:
facts/extracted/, facts/memory/, facts/erp_snapshot/. La sub-fase reúne las tres, pero los modelos son distintos y se versionan por separado.DeterministicAccountingConfig ahora son audit por cliente (T3 detecta gaps).El Motor entrega al integrador una tupla inmutable:
{
standalone_intent: StandaloneIntent,
internal_plan: InternalAccountingPlan,
contextual_decision: ContextualDecision,
validation_result: ValidationResult (status=PASSED),
motor_metadata: {
rule_versions: dict[str, str],
llm_versions: dict[str, str],
input_hash: str,
decision_audit_log_id: UUID,
}
}
El motor_metadata es el contrato de trazabilidad con T3 y T4. Sin él, la calcificación no puede saber qué versión de qué regla produjo qué decisión.
T3 puede observar y proponer reglas semánticas al Motor. Tipos:
| Tipo | Descripción | Ubicación |
|---|---|---|
account_rule |
"Para este shape de documento + proveedor → cuenta X" | sustituye llamada a LLM Account en Fase 2 |
cost_center_rule |
"Para este shape de línea + cliente → distribución Y" | sustituye llamada a LLM CostCenter |
fiscal_rule |
"Para este tipo + receptor → deducibilidad Z + retención W" | sustituye llamada a LLM Fiscal |
contextual_rule |
"Para este patrón de relación documento-previo → naturaleza N" | sustituye decisión del kernel contextual |
Las reglas semánticas viven en M1.RuleStore. No mezclan con reglas de I1 (que son traducción a Holded). Una regla semántica dice "para este input fiscal → este intent"; una regla de I1 dice "para este intent + estado Holded → esta operación API".
El Motor nunca importa nada de holded_integration/. Ni a nivel de tipo, ni a nivel de constante. Esta regla se verifica con un lint custom: cualquier import de holded_* desde motor/ falla CI.
Si el Motor necesita facts del ERP (e.g. period locks de Holded para decidir si redatar), los recibe via facts/erp_snapshot/, que es input del Motor, no dependencia. El erp_snapshot lo produce I1 antes del Motor.
| Riesgo | Mitigación |
|---|---|
| Las reglas calcificadas semánticas crecen sin disciplina y el Motor se vuelve un rule engine opaco | Métricas obligatorias: % docs servidos por regla vs LLM, alarma si una regla cubre >40% de docs (señal de regla demasiado amplia) |
DeterministicAccountingConfig tiene tantos defaults que un cliente nuevo arranca con criterios silenciosamente equivocados |
T3 monitoriza signals aviso-cuenta-generica por cliente. Si supera umbral, alerta a Intelia ops para auditar config |
| LLM Compiler de guía de cliente produce CompiledGuide inconsistente con el chart of accounts | Validator post-compile que cruza códigos del CompiledGuide contra el chart actual. Inconsistencia → bloquea promote, señaliza humano |
decision_guards.py regresa al patrón actual (1142 LOC mezclando decisión + lectura SoR) |
Disciplina de código: guards consumen facts normalizados. Lecturas SoR viven en facts/erp_snapshot/, no en guards. Lint custom |
| El Motor no se entera de que el contable cambia el criterio (criterio drift) | T4 propaga signal CRITERION_CHANGED al Motor. Intelia ops decide si invalidar reglas calcificadas afectadas |
Patología 1: el Motor se vuelve un proxy del ERP. Si por presión de implementación se mete "si Holded está locked, redatar" dentro del Motor en lugar de en facts/erp_snapshot/, el Motor empieza a saber de Holded. En 6 meses tendremos facts.holded_period_lock_status referenciado en el kernel contextual y el motor ya no es portable. Vigilancia: lint + code review.
Patología 2: regla calcificada con razonamiento desplazado. Si una regla calcificada account_rule es del tipo "proveedor Glovo → cuenta 62980000", está bien. Si es "proveedor Glovo → cuenta 62980000, EXCEPTO si Holded ya tiene mappings para él", está mal: la regla está consultando estado del SoR. La regla debería ser puramente función de los facts, no del estado del ERP. Vigilancia: schema estricto de reglas calcificadas.
Patología 3: bootstrap cruzado introduce sesgo masivo. Cliente nuevo en sector hostelería arranca con reglas heredadas de "cliente mediano hostelería". Si "cliente mediano hostelería" es una empresa muy específica con criterios poco transferibles, el cliente nuevo va a recibir muchas correcciones del contable en primeras semanas. Vigilancia: T3 marca reglas bootstrapped como bootstrap_origin=X; si correction_rate>30% en primeros 30 docs, invalida en bloque y vuelve a 100% agente LLM hasta calcificar de novo.
¿Multi-tax-entity per team afecta al Motor? Probablemente el Motor opera por tax_entity_id aislado. Pero el bootstrap cruzado puede ser a nivel team. Decidir si la granularidad de calcificación es tax_entity o team.
Shape del input_hash para lookup en M1.RuleStore. ¿Qué subset del AccountingInputPackage lo determina? Si incluye provider_history completo, casi nunca hay hit. Si solo provider + document_type + amount range, las reglas calcifican sobre patrones demasiado amplios y dan intents incorrectos.
Versioning de guides cliente. Cuando el cliente edita su guía contable, ¿las reglas calcificadas se invalidan automáticamente? ¿O quedan vivas hasta que el LLM las contradiga?
¿LLM Cost Center merece existir si el cliente tiene reglas declarativas exhaustivas? Si todas las líneas matchean reglas del CompiledGuide con confidence=1, ¿skipear el LLM? Decisión de design para Fase 2.
¿Cómo se versiona previous_accounting_state que consume el kernel contextual? Si V2 emitió 3 versiones de un documento (post-edición, post-bug-fix), ¿el kernel compara contra última o contra "estable"?
| Métrica | Target |
|---|---|
%_docs_served_by_motor_rule_vs_llm |
Crece con el tiempo. >50% a los 6 meses por team |
motor_latency_p95 |
<2s (sub-fases deterministas) + <8s (Fase 2 con LLMs paralelos) |
decision_audit_completeness |
100%. Cada decisión tiene rule_versions + input_hash en log |
validation_failed_rate |
<1%. Si sube, hay bug en algún sub-componente del motor |
criterion_drift_alerts_per_month_per_client |
<2. Si sube, las reglas calcificadas no representan el criterio del cliente |
i1-integrator-holded.md — qué hace I1 con el output del Motor.t3-calcification.md — cómo T3 propone reglas al Motor.t4-reverse-sync.md — cómo T4 propaga señal de criterion_changed al Motor.