Skip to content

Plataforma técnica · Orchestrator

Plan de Cuentas Sync

Orchestrator Command Plan Cuentas

PlanCuentasSyncService es la herramienta operacional para mantener consistente el plan de cuentas template (la base de referencia que vive en accounting_template.plan_contable) con cada base de tenant. Resuelve preguntas como: “¿qué cuentas le faltan al tenant X?”, “¿qué tiene de más?”, “agregué una cuenta nueva al template, propágala a todos”.

El servicio compara accounting_template.plan_contable contra nostromo_<rut>.plan_contable y clasifica cada código en una de cuatro categorías:

CategoríaDefiniciónAcción típica
FaltantesEstán en template pero no en tenant.insert_missing
HuérfanasEstán en tenant pero no en template y no son instanciables locales.delete_orphans (con checks)
LocalesEstán en tenant pero no en template y su padre está en INSTANCIABLE_PARENTS (actualmente { '2204000' }).Se respetan — son legítimas.
DivergentesExisten en ambos, pero algún campo de COMPARE_FIELDS difiere.update_divergent

COMPARE_FIELDS compara: codigo_padre, nombre, nombre_ifrs, tipo, naturaleza, nivel, es_imputable, requiere_tercero, requiere_cc, requiere_proyecto, orden_presentacion, categoria_ifrs.

flowchart LR
  T[("accounting_template<br/>plan_contable")]
  TE1[("tenant 1")]
  TE2[("tenant 2")]
  TE3[("tenant N")]
  S["PlanCuentasSyncService"]
  RES["TenantSummary[]<br/>{ faltantes_count<br/>huerfanas_count<br/>divergentes_count<br/>locales_count }"]

  T --> S
  TE1 --> S
  TE2 --> S
  TE3 --> S
  S --> RES

getOverview() ejecuta el diff contra todos los tenants activos en paralelo (Promise.allSettled) y devuelve un resumen por base. getDiff(database) devuelve el diff detallado de un solo tenant con las listas completas de códigos en cada categoría.

Sincronización: syncTenant(database, options)

Section titled “Sincronización: syncTenant(database, options)”

SyncOptions:

CampoTipoSignificado
dry_runbooleanSi true, solo devuelve qué haría sin escribir.
insert_missingbooleanInserta cuentas faltantes (ordenado por nivel ↑).
update_divergentbooleanRe-aplica campos divergentes desde el template.
delete_orphansbooleanBorra huérfanas (con verificación de seguridad).
target_databasesstring[]Si está vacío, opera sobre todos los tenants activos.
flowchart TB
  A["syncTenant(db, opts)"]
  V["validateSyncOptions"]
  D["getDiff(db)"]
  M["insertMissing<br/>(ordenado por nivel)"]
  U["updateDivergent<br/>(re-aplica template)"]
  O["deleteOrphans<br/>(check dependencies)"]
  R["SyncResult"]

  A --> V --> D
  D -. dry_run = true .-> R
  D --> M --> U --> O --> R

Por qué ordenar por nivel para insertar: las cuentas tienen codigo_padre; insertar primero los padres evita violar foreign keys.

CampoTipoContenido
inserted_countnumberInsertadas con éxito.
inserted_codigosstring[]Códigos insertados (o que se insertarían si dry_run).
updated_countnumberUpdates aplicados.
updated_codigosstring[]Códigos actualizados.
deleted_countnumberHuérfanas eliminadas.
deleted_codigosstring[]Códigos eliminados.
skipped_countnumberBorrados saltados por dependencias.
skippedArray<{ codigo, reason }>Detalle de saltos: 'has_movements', 'has_children', o 'has_movements,has_children'.
errorsArray<{ codigo, error }>Errores por código durante el sync.
errorstring | nullError fatal previo al sync.

deleteOrphans no es destructivo a ciegas. Para cada huérfana llama getOrphanCheck, que retorna:

CampoSignificado
movimientos_countCantidad de líneas contables que referencian la cuenta.
has_childrenExisten cuentas con codigo_padre = este código.
can_deletemovimientos_count === 0 && !has_children.

Si can_delete === false, la cuenta se salta y se reporta en skipped. Nunca se borra una cuenta con histórico de movimientos.

El template es el punto de partida, no inmutable. Tres operaciones administrativas:

Crea una cuenta en el template. Valida:

  • Formato del código: ^[0-9]+$.
  • nivel ∈ [1..6].
  • naturaleza ∈ { 'D', 'C' }.
  • tipo ∈ { 'ACTIVO', 'PASIVO', 'PATRIMONIO', 'INGRESO', 'GASTO' }.
  • Si nivel > 1, el padre debe existir en el template.

Si propagate=true, intenta insertar la cuenta en cada tenant activo. Los errores por tenant no fallan el batch — se acumulan en errors[].

updateCuentaInTemplate(codigo, patch, propagate)

Section titled “updateCuentaInTemplate(codigo, patch, propagate)”

Actualiza campos del template (no permite cambiar codigo). Si propagate=true, replica la actualización en cada tenant que ya tenga el código.

Elimina una cuenta del template. Comportamiento:

  • Si propagate=false y algún tenant la tiene, rechaza con cuenta_in_use_in_tenants incluyendo la lista de bases afectadas.
  • Si propagate=true, intenta borrarla en cada tenant aplicando el mismo getOrphanCheck (no borra con movimientos / con hijos).
  • Borra del template al final, solo si la propia tabla del template pasa el check.

Promueve una cuenta local (creada por un tenant bajo un padre instanciable) al template canónico. Validaciones:

  • La cuenta debe existir en el tenant.
  • No debe existir aún en el template.
  • Si tiene codigo_padre, el padre debe estar en el template.

No la propaga al resto de tenants — para distribuirla, correr syncTenant con insert_missing=true o createCuentaInTemplate({ propagate: true }) desde cero.

Antes de cualquier sync destructivo, correr con dry_run: true:

Dry run
POST /api/command/plan-cuentas/sync/nostromo_60004317
{
"dry_run": true,
"insert_missing": true,
"update_divergent": true,
"delete_orphans": true,
"target_databases": []
}

El SyncResult devuelve listas en inserted_codigos, updated_codigos, deleted_codigos sin tocar la base. Si todo se ve bien, re-ejecutar con dry_run: false.

MensajeCausa
tenant_not_foundLa base no está en listActiveTenants.
cannot_sync_templateSe intentó pasar accounting_template como destino.
cannot_change_codigoPatch incluyó codigo.
cannot_delete_with_dependenciesgetOrphanCheck detectó movimientos o hijos. Trae movimientos_count y has_children adjuntos.
cuenta_in_use_in_tenantsDelete sin propagate=true cuando hay tenants afectados. Trae tenants[] adjunto.
cuenta_not_found_in_tenantPromote desde una cuenta que no existe en el tenant.
cuenta_already_in_templatePromote de un código que ya está en el template.
orphan_padre_missing_in_templateCreate/promote con codigo_padre que no existe.
cuenta_not_orphanremoveOrphan sobre una cuenta que sí está en el template (o no en el tenant).
  • getOverview() corre dumpPlanContable contra todos los tenants en Promise.allSettled. Con 50 tenants y 1000 cuentas cada uno, ~5-10s.
  • syncTenant es secuencial por categoría (insert, then update, then delete) y secuencial dentro de cada lista — diseñado para correctitud sobre velocidad. No es operación hot-path.

Si se vuelve un cuello de botella, hay dos caminos: paralelizar insertMissing por nivel (todos los del nivel N antes que N+1) o mover a un job en background con persistencia del SyncResult. Ninguno implementado.