t4-reverse-sync

T4 — Reverse Sync

Categoría: Conector con interpretación SoA pequeña (LLM extrae intención de comments) Estado: Propio sano (componente nuevo crítico en V3) Tamaño esperado: ~2-3k LOC


1. Responsabilidad

Cerrar el loop de aprendizaje del SoA: detectar cualquier cambio en Holded posterior a la ejecución del plan de Intelia y propagarlo como señal estructurada al resto del sistema (T3, M1, dashboard).

Sin T4, el SoA es ciego al criterio del contable. Es el sustrato de toda la calcificación.

2. NO es su responsabilidad

  • No decide nada. Propaga señal estructurada; otros componentes (T3, M1, Intelia ops) deciden qué hacer.
  • No escribe a Holded. Solo lee. T4 es read-only sobre el SoR ajeno.
  • No procesa el reverse-sync en runtime de la decisión de Intelia. Trabaja asíncrono.
  • No interpreta semantics libremente. Cuando lee un comment del contable, el LLM extrae intent estructurado (positive | negative | neutral | criterion_change); no toma decisión propia.

3. Qué monitoriza

Para cada documento que Intelia ha ejecutado (registrado en executed_plan_history con su plan_id y external_id en Holded), T4 vigila:

Campo en Holded Cambio que detecta Signal que emite
approved Pasa de false a true APPROVED_BY_HUMAN
accounts (líneas) Cambio de cuenta en una línea EDITED_ACCOUNT_LINE
tax_keys Cambio en taxes EDITED_TAX
tags (excluyendo intelia-*) El contable añadió tag TAGGED_BY_HUMAN
comments (excluyendo [Intelia]) El contable comentó COMMENTED_BY_HUMAN
accountingDate Cambió la fecha contable DATE_OVERRIDDEN
notes Cambió la nota NOTE_EDITED
Estado del payment Pasa de pending a paid o se reconcilió manualmente PAYMENT_STATE_CHANGED
Documento eliminado Detectado por 404 en readback DELETED_BY_HUMAN

T4 NO monitoriza cambios que hizo Intelia misma (filtrados por timestamp + actor cuando posible, o por hash del estado pre/post-ejecución de Intelia).

4. Mecanismo de polling

Holded no tiene webhooks fiables. T4 funciona por polling.

Cadencia

  • Polling caliente (cada 15 min): documentos ejecutados en últimos 30 días por team activo. Es donde más probable que el contable trabaje.
  • Polling tibio (cada 6h): documentos de últimos 90 días.
  • Polling frío (cada 7 días): documentos antiguos (>90 días). Para detectar correcciones tardías o cierres anuales.

Adaptable por team: clientes con contables muy activos → cadencia más alta.

Hash de estado por documento

Para detectar cambios sin enviar el documento entero por cada poll, T4 mantiene:

HoldedDocumentMonitor {
    document_id: str (external),
    intelia_plan_id: UUID,
    monitored_fields: ["account_lines", "approved", "tax_keys", ...],
    last_hash: str,           # hash del subset monitoreado en el último poll
    last_polled_at: datetime,
    poll_tier: "hot" | "warm" | "cold",
    edits_detected: list[EditSignal]
}

En cada poll: lee Holded → calcula hash → si distinto → diff detallado + emite signals.

Optimización

  • Batch polling: una sola llamada Holded por team con múltiples document_ids cuando la API lo permite.
  • Cache local del estado: el last_hash evita rehacer trabajo en polls sin cambios.
  • Rate limit-aware: T4 respeta los límites de Holded (~100 calls/min por API key). Si team grande, polling se distribuye en el tiempo.

5. Procesamiento de comments

Los comments libres del contable son la señal más rica pero la más ruidosa.

Pipeline de extracción

  1. Poll detecta comment nuevo (no escrito por Intelia).

  2. LLM corto (modelo barato, e.g. gpt-5-nano) procesa el comment con prompt:

    Eres un clasificador. El contable ha comentado lo siguiente en un documento contable
    procesado por Intelia. Extrae la intención.
    
    Contexto: [resumen del plan + cuenta usada + cliente]
    Comentario: "[texto del contable]"
    
    Devuelve JSON: {
      intent: "positive_confirmation" | "negative_disagreement" | "criterion_change" | "question" | "noise",
      subject: "account" | "tax" | "imputation" | "date" | "general" | null,
      confidence: 0.0-1.0,
      extracted_correction: { ... } | null  // solo si negative_disagreement con cambio sugerido
    }
  3. Si confidence < 0.7 → no se propaga signal (ambiguo). Queda en low_confidence_comments para revisión humana opcional.

  4. Si criterion_change → signal estructurado a Intelia ops dashboard con el comment original.

  5. Si negative_disagreement con extracted_correction → signal estructurado a T3 (evidencia negativa de la regla aplicada).

Anti-loop

T4 NO procesa comments que parezcan auto-respuestas o que ya fueron procesados. Hash + dedupe por comment_id.

6. Signals que emite

T4 emite signals al bus de signals (TX) tipados:

ReverseSyncSignal {
    signal_type: "APPROVED_BY_HUMAN" | "EDITED_ACCOUNT_LINE" | ...,
    document_id: str,
    intelia_plan_id: UUID,
    detected_at: datetime,
    delta: {
        field: str,
        old_value: any,
        new_value: any,
    },
    contable_id: str?,         // si Holded lo expone
    audience: ["t3_calcification", "m1_motor", "intelia_ops"],
    actionable: bool,
}

Los consumidores deciden qué hacer con los signals:

Consumidor Acción
T3 Calcification Suma evidencia (positiva si confirmación, negativa si edición) para reglas que aplicaron
M1 Motor Recalibra ProviderHistoryFacts (la próxima vez que el documento vuelva por este proveedor, M1 sabe que el último plan fue editado)
Intelia ops dashboard Surface las correcciones recientes; permite filtrar por cliente, por regla aplicada, por tipo de edición

7. Ciclo de respuesta opcional al contable

Cuando T4 detecta una edición ambigua, puede dejar un comment al contable en Holded preguntando la razón:

[Intelia] Detectamos que cambiaste la cuenta 62980000 por 62890000 en este documento.

¿Razón del cambio? Responde con uno de:
- #criterio_cambiado (queremos actualizar la regla)
- #extraccion_incorrecta (el OCR leyó algo mal)
- #bug_intelia (el agente se equivocó)
- O ignora este mensaje si fue una excepción puntual.

Esto convierte un signal ambiguo en uno estructurado.

Política: solo se hace si la edición es alta importance (cuenta cambiada vs comment añadido al texto libre). Si lo haces para todo, satura al contable.

8. Reverse-sync de cambios sistémicos (no por documento)

Algunos cambios en Holded afectan a múltiples documentos:

  • Cambio en el chart of accounts (cliente renombra cuenta o crea cuenta nueva).
  • Cambio en period locks (cliente cerró trimestre).
  • Cambio en tax entity config (cliente activó SII o cambió régimen).

T4 también monitoriza estos cambios "sistémicos":

SystemicChangeSignal {
    signal_type: "CHART_OF_ACCOUNTS_CHANGED" | "PERIOD_LOCKED" | ...,
    team_id: UUID,
    detected_at: datetime,
    delta: { ... },
    affected_intelia_plans: [list of plan_ids que dependen],
    audience: ["m1_motor", "t3_calcification", "i1_facts_snapshot"]
}

Estos signals disparan invalidación masiva en T3 (reglas afectadas pasan a re-shadow) y refresh de facts/erp_snapshot/ en M1.

9. Riesgos arquitectónicos y mitigaciones

Riesgo Mitigación
Polling no detecta cambios entre polls (contable edita rápido) Hash de estado captura el "antes/después" aunque los cambios intermedios se pierdan. Para auditoría, se documenta la limitación
Holded API rate limit saturado por polling Batch polling + tiers de cadencia + monitoring de rate-limit headers
LLM extractor de comments confunde positive con negative Confidence threshold (0.7); fall-through a low_confidence_comments para review humano
Cambios sistémicos no se detectan a tiempo (cliente cerró trimestre el viernes, lunes Intelia escribe en período locked) Polling de systemic changes con cadencia más alta (cada 1h). Pre-write check en I1 valida estado fresco antes de cada write
El contable comenta cosas no útiles ("ok", "👍") y T4 las procesa como confirmation, inflando false positives Lista de patrones positive ruidosos. Solo se propaga signal si el comment es suficientemente específico
Cambios entre polls pueden sobreescribirse: contable edita y luego "deshace"; T4 solo ve estado actual Política aceptada como limitación. Documentar en runbook
Procesar comments con LLM gasta tokens por nada (comments de Intelia procesados como del contable) Filtro por prefix [Intelia] antes de pasar a LLM

10. Lo que puede salir mal

Patología 1: T4 satura Holded API. Polling agresivo + clientes grandes (50k+ documentos vivos) → 1000+ calls/min → rate limit + bloqueo Holded. Mitigación: cadencia adaptativa, tiers, batch. Monitor de holded_calls_per_minute con alarma.

Patología 2: false positives en interpretación de comments. LLM clasifica un comment ambiguo como "positive_confirmation" cuando era negativo. T3 calcifica un patrón que el contable rechaza. Mitigación: confidence threshold; algunos clientes pueden activar modo "siempre Intelia ops revisa comments" si su contable comenta mucho.

Patología 3: latencia entre cambio y propagación. Contable edita a las 9am; T4 polling caliente cada 15 min lo detecta a las 9:15am; T3 lo procesa en su próximo ciclo (cada hora?). En cliente activo, la latencia hace que el ciclo de aprendizaje sea de horas, no minutos. Aceptable trade-off por el coste de polling más alto.

Patología 4: el contable no edita en Holded pero edita el plan en dashboard Intelia. Las dos rutas (auto-Holded con T4 + dashboard) generan evidence de tipos distintos. Si el cliente migra de una a otra (porque empezó en dashboard y ahora confía en auto), T4 puede no captar correlaciones bien. Mitigación: tracking unificado de "edition events" sin importar el canal.

11. Open questions

  1. Cadencia óptima. ¿15 min caliente es el balance correcto entre frescura y coste? Necesita medición real.

  2. ¿Comments del contable en Holded se procesan inmediatamente o en batch? Inmediato = latencia baja pero más calls LLM. Batch = más eficiente, peor para evidencias urgentes (criterion_change que afecta a plans en flight).

  3. Política de retención de signals históricos. ¿Cuánto tiempo guardamos EditSignal? Propuesta: 12 meses caliente, archivar.

  4. ¿T4 puede inferir intención por sólo el patrón de cambios? Si el contable cambia 6209 → 6231 en 10 documentos del mismo proveedor sin comentar, ¿T4 emite CRITERION_CHANGED automáticamente? Sí, probablemente. Threshold + signal específico.

  5. Reverse-sync de comentarios sistémicos (no atribuibles a un documento). Si el contable deja un comment a nivel cliente "a partir de ahora todo este proveedor a 6210", ¿T4 lo capta? Probablemente requiere monitoreo de surfaces distintas en Holded.

  6. Cómo se filtran los cambios hechos por Intelia misma vs por el contable. Holded a veces no atribuye autoría claramente. Workaround: hash del estado pre-ejecución de Intelia + diff. Si el diff matchea lo que Intelia esperaba escribir → es Intelia. Si difiere → es humano.

12. Métricas operacionales

Métrica Target
t4_polling_latency_p95 <30s (desde edición del contable hasta detección) en tier caliente
t4_signals_emitted_per_day_per_team Variable. Si 0 días seguidos, alerta (sistema caído o cliente inactivo)
t4_comment_extraction_confidence_avg >0.75. Si bajo, el extractor LLM no funciona bien
t4_low_confidence_comments_per_week_per_team <5. Si alto, comments del contable son muy ambiguos
t4_holded_api_calls_per_doc_monitored <0.2 (con cache eficiente)
t4_systemic_change_detection_lag <1h para chart_of_accounts cambios

Próximos docs