Plataforma técnica · Orchestrator
Capital Service
Orchestrator Administración Capital
CapitalService gestiona los movimientos de capital social: aportes iniciales, aumentos, reducciones, capitalización de utilidades, revalorizaciones y ajustes. Es el único servicio del dominio Administración que emite hooks de dominio — su evento capital:movimiento alimenta listeners que reaccionan a cambios de patrimonio.
Modelo
Section titled “Modelo”CapitalEntry:
| Campo | Tipo | Notas |
|---|---|---|
id | UUID | |
tipo_capital | enum CapitalType | Ver tabla Tipos. |
fecha_movimiento | DATE | |
monto | numeric | Validado positivo según tipo_capital. |
moneda | string | Default CLP. |
estado | enum ACTIVO | ANULADO | PENDIENTE_APROBACION | Default ACTIVO. |
socio_id | UUID? | |
porcentaje_participacion | numeric? | Validado ∈ [0, 100]. |
representante_legal_id | UUID? | FK opcional a representantes. |
cuenta_contable_codigo | string? | Default 2301001 si no se especifica. |
cuenta_contrapartida_codigo | string? | Validada contra plan_contable. |
documento_respaldo | string? | Tipo de documento (escritura, etc.). |
numero_documento | string? | |
descripcion / observaciones | string? | |
aprobado_por | UUID? | Lleno tras approve. |
fecha_aprobacion | DATE? | |
created_by / updated_by | UUID? |
Tipos de Capital
Section titled “Tipos de Capital”tipo_capital | Categoría hook | Cuándo |
|---|---|---|
APORTE_INICIAL | APORTE | Capital fundacional. |
AUMENTO_CAPITAL | APORTE | Incremento de capital social. |
CAPITALIZACION_UTILIDADES | APORTE | Utilidades reinvertidas. |
REDUCCION_CAPITAL | RETIRO | Disminución de capital. |
REVALORIZACION | AJUSTE | Corrección monetaria. |
AJUSTE_CAPITAL | AJUSTE | Otros ajustes contables. |
Operaciones
Section titled “Operaciones”list(ctx, filters)
Section titled “list(ctx, filters)”Lista paginada con filtros opcionales (estado, tipo_capital, fecha_desde, fecha_hasta, limit, offset). Default limit=100, ordenado por fecha_movimiento DESC, created_at DESC.
getById(ctx, id)
Section titled “getById(ctx, id)”Lookup. Lanza NotFoundError("Capital entry", id) si no existe.
create(ctx, data)
Section titled “create(ctx, data)”Validaciones (antes de abrir TX):
validateRequired(data, ['tipo_capital', 'fecha_movimiento', 'monto']).- Si
tipo_capital ∈ { APORTE_INICIAL, AUMENTO_CAPITAL, CAPITALIZACION_UTILIDADES, REVALORIZACION }:monto > 0. - Si
porcentaje_participaciondefinido:∈ [0, 100].
Dentro de la TX:
- Si
cuenta_contable_codigodefinido: existe enadministracion.plan_contable. - Si
cuenta_contrapartida_codigodefinido: existe enadministracion.plan_contable. - INSERT con default
cuenta_contable_codigo = '2301001'si no se pasó. outbox.queue('capital:movimiento', { capitalId, tipoMovimiento, monto }).
Tras el COMMIT, el outbox vacía el evento al DomainEventBus. Si la TX rollback, el evento no se emite.
update(ctx, id, updates)
Section titled “update(ctx, id, updates)”Lógica diferenciada según estado del registro:
flowchart TB
IN["update(id, updates)"]
EX["findById(id)"]
AP{"existing.aprobado_por != null ?"}
SAFE["¿updates solo toca<br/>cuenta_contable_codigo,<br/>cuenta_contrapartida_codigo,<br/>descripcion, observaciones?"]
ERR["ValidationError<br/>'Asiento aprobado: solo se permiten<br/>cambios en safeFields. Bloqueados: ...'"]
FILTER["filtrar updates a safeFields<br/>(si quedó vacío → no-op return)"]
VAL["validar monto/porcentaje/cuentas<br/>(si aplican)"]
UPD["UPDATE en DB"]
IN --> EX --> AP
AP -- sí --> SAFE
SAFE -- no --> ERR
SAFE -- sí --> FILTER --> UPD
AP -- no --> VAL --> UPD Safe fields sobre un asiento aprobado: cuenta_contable_codigo, cuenta_contrapartida_codigo, descripcion, observaciones. Cualquier otro campo lanza ValidationError listando los bloqueados.
La detección es semántica (compara contra el valor actual, normaliza Date a toISOString), no por mera presencia del key — pasar el mismo valor no se considera intento de cambio.
update no emite hook. Solo create y approve (vía create si el flujo de aprobación llega ahí; ver nota más abajo).
delete(ctx, id)
Section titled “delete(ctx, id)”Bloqueado si el registro está aprobado (aprobado_por != null) — lanza ValidationError("Cannot delete approved capital entry").
approve(ctx, id)
Section titled “approve(ctx, id)”Transiciona PENDIENTE_APROBACION → ACTIVO:
UPDATE administracion.capitalSET estado = 'ACTIVO', aprobado_por = $userId, fecha_aprobacion = CURRENT_DATE, updated_by = $userId, updated_at = CURRENT_TIMESTAMPWHERE id = $id AND estado = 'PENDIENTE_APROBACION'RETURNING *La cláusula AND estado = 'PENDIENTE_APROBACION' actúa como guarda — si el row ya no está en ese estado el UPDATE no afecta filas. El servicio igualmente valida antes con findById y lanza ValidationError("Capital entry is not pending approval").
Hook emitido
Section titled “Hook emitido”capital:movimiento
Section titled “capital:movimiento”Encolado dentro de la TX de create, emitido tras COMMIT vía BaseService.flushOutbox.
Payload tipado (DomainEventMap['capital:movimiento'] + EventContext):
{capitalId: string;tipoMovimiento: 'APORTE' | 'RETIRO' | 'AJUSTE';monto: number;// Auto-añadidos por flushOutbox:tenantDb: string;userId: string;requestId?: string;}Mapeo de hook
Section titled “Mapeo de hook”tipo_capital (granularidad de negocio) se reduce a tipoMovimiento (granularidad del hook) por mapTipoMovimiento:
tipo_capital | tipoMovimiento |
|---|---|
APORTE_INICIAL | APORTE |
AUMENTO_CAPITAL | APORTE |
CAPITALIZACION_UTILIDADES | APORTE |
REDUCCION_CAPITAL | RETIRO |
REVALORIZACION | AJUSTE |
AJUSTE_CAPITAL | AJUSTE |
Los listeners no necesitan conocer el detalle de CAPITALIZACION_UTILIDADES vs AUMENTO_CAPITAL — para cualquiera, el patrimonio sube. Si un listener requiere el detalle, debe consultar el row vía capitalId.
Consumidores actuales del hook
Section titled “Consumidores actuales del hook”Ninguno con efectos reales todavía. El evento se persiste en MongoDB vía MongoEventSink (audit trail), pero no hay listeners de negocio enganchados. Documentado para que cuando se agregue CashFlowService o BalanceProyectadoService puedan suscribirse sin tocar CapitalService.
Workflow de Aprobación
Section titled “Workflow de Aprobación”flowchart LR
START(("●<br/>start"))
ACT["ACTIVO"]
PND["PENDIENTE_APROBACION"]
ANU["ANULADO"]
END_OK(("◉<br/>delete OK"))
END_BLOCK(("⊘<br/>delete bloqueado<br/>si aprobado_por"))
START -->|create default| ACT
START -->|create con estado explícito| PND
PND -->|approve| ACT
ACT -->|update estado=ANULADO| ANU
PND -->|update estado=ANULADO| ANU
ACT -.->|delete| END_BLOCK
PND -->|delete| END_OK
ANU -->|delete| END_OK Una vez aprobado_por está set (lo que ocurre en approve), delete queda bloqueado y update solo permite safe fields. El registro es esencialmente inmutable salvo por glosa contable.
Repository
Section titled “Repository”CapitalRepositoryImpl está envuelto con wrapStaticRepository (decorator común) con slowQueryMs: 500 — queries que pasen ese umbral se loguean.
export const CapitalRepository = wrapStaticRepository(CapitalRepositoryImpl, { label: "CapitalRepository", slowQueryMs: 500,});Los update usan buildUpdate (de domain/common/sqlBuilders) para construir SET dinámico, con updatedAtSql: 'CURRENT_TIMESTAMP'.
Endpoints
Section titled “Endpoints”Documentados en Administración API. Resumen:
| Método | Ruta | Service call |
|---|---|---|
GET | /api/admin/capital | list(ctx, filters) |
GET | /api/admin/capital/:id | getById(ctx, id) |
POST | /api/admin/capital | create(ctx, data) |
PUT | /api/admin/capital/:id | update(ctx, id, updates) |
DELETE | /api/admin/capital/:id | delete(ctx, id) |
POST | /api/admin/capital/:id/approve | approve(ctx, id) |