Plataforma técnica · Orchestrator
Declaraciones Service
Orchestrator Declaraciones F29
DeclaracionesService orquesta el ciclo de vida del F29. No calcula valores (eso es del F29GeneratorService); coordina generación, lectura, transición de estados con guardas, ajuste de baja de remanente y regeneración de asientos contables anuales.
Operaciones
Section titled “Operaciones”| Método | Para |
|---|---|
generateF29(ctx, params) | Genera o regenera borrador. Delega cálculo a F29GeneratorService. |
list(ctx, filter) | Lista headers F29 con filtros (período/año/mes/rut/estado). |
getById(ctx, id) / getByPeriod(ctx, periodo, rut) | Recupera F29 completo (header + detalles). |
updateStatus(ctx, id, estado) | Transición de estado con guardas y efectos contables (ver abajo). |
delete(ctx, id) | Borrado físico dentro de TX. |
registrarAjusteBajaRemanente(ctx, f29Id, input) | Aplica baja de remanente notificada por SII, regenera F29, emite asiento. |
eliminarAjusteBajaRemanente(ctx, f29Id) | Revierte el ajuste, regenera F29. |
getAjusteBajaRemanente(ctx, f29Id) | Recupera el ajuste vigente del F29. |
regenerarAsientosAnual(ctx, anio) | Re-emite asientos contables declaraciones.asientos_f29 para el año. |
generateF29(ctx, params)
Section titled “generateF29(ctx, params)”Valida formato del período (YYYY-MM), delega a f29Generator.generate(ctx, params) que abre su propia TX y emite f29:generada post-COMMIT. Después lee el F29 completo con getFull(pool, id) y lo retorna.
Sobrescritura controlada: si ya existe un F29 con estado VALIDADO, DECLARADO o ANULADO para el mismo período/RUT, el INSERT del generator viola la UNIQUE constraint (23505) y este service traduce a:
ValidationError("Ya existe una declaración (Validada, Declarada o Anulada)para este período. No se puede sobrescribir con un borrador.")Solo borradores se pueden sobrescribir libremente (el generator hace DELETE WHERE estado = 'BORRADOR' antes del INSERT).
updateStatus(ctx, id, estado) — máquina de estados con efectos
Section titled “updateStatus(ctx, id, estado) — máquina de estados con efectos”Normaliza nombres femeninos legacy (DECLARADA → DECLARADO, etc.) y valida contra ['BORRADOR', 'VALIDADO', 'DECLARADO', 'ANULADO', 'RECTIFICADO'].
flowchart TB
IN["updateStatus(id, estado)"]
NORM["normalizar enum<br/>(DECLARADA → DECLARADO)"]
VAL["validar estado ∈<br/>{BORRADOR, VALIDADO, DECLARADO,<br/>ANULADO, RECTIFICADO}"]
TX["withTransaction"]
FIND["findById(id)"]
GUARD{"estado actual = DECLARADO<br/>y target ∈ {BORRADOR, VALIDADO, ANULADO}?"}
CHECK["existsIvaPagadoInPeriod(año, mes)"]
REJ["ValidationError<br/>'No se puede revertir: IVA en PAGADO'"]
UPD["updateStatus(id, target, userId)"]
ROLLB{"venía de DECLARADO?"}
REVIVA["revertirIvaPeriodo<br/>(PAGADO → CONTABILIZADO)"]
REGAN["generarAsientosF29Anual"]
TODECL{"target = DECLARADO?"}
MARK["contabilizarIvaPeriodo<br/>(CONTABILIZADO → PAGADO)"]
REGAN2["generarAsientosF29Anual"]
EMIT["outbox.queue<br/>(f29:status_cambiado)<br/>solo si cambia"]
COMMIT["COMMIT"]
IN --> NORM --> VAL --> TX --> FIND --> GUARD
GUARD -- sí --> CHECK
CHECK -- existe pagado --> REJ
CHECK -- todos CONTABILIZADO --> UPD
GUARD -- no --> UPD
UPD --> ROLLB
ROLLB -- sí --> REVIVA --> REGAN --> TODECL
ROLLB -- no --> TODECL
TODECL -- sí --> MARK --> REGAN2 --> EMIT
TODECL -- no --> EMIT
EMIT --> COMMIT Efectos al pasar a DECLARADO
Section titled “Efectos al pasar a DECLARADO”| Paso | Tabla afectada |
|---|---|
contabilizarIvaPeriodo | operaciones_sii.compras_ventas_detalle (CONTABILIZADO → PAGADO en el período) |
generarAsientosF29Anual | declaraciones.asientos_f29 (regenera para el año del F29) |
Guard al revertir desde DECLARADO
Section titled “Guard al revertir desde DECLARADO”Solo permite volver a BORRADOR/VALIDADO/ANULADO si todas las líneas IVA del período están en CONTABILIZADO (ninguna PAGADO). Si hay pagado, lanza:
ValidationError("No se puede revertir el F29: existen IVA en estado PAGADOpara el periodo. Solo se permite revertir cuando todos están en CONTABILIZADO.")Ajuste de Baja de Remanente
Section titled “Ajuste de Baja de Remanente”Cuando el SII notifica que el remanente declarado en un período fue reducido (rechazo parcial), el contador debe registrar la baja. El servicio:
- Valida que el F29 esté en
BORRADORoVALIDADO(unDECLARADOse debe revertir primero). - Valida que el monto sea positivo y no exceda el
remanenteArrastradoOriginal(remanente anterior + ajuste previo si lo hubo). - Si está
VALIDADO, lo regresa aBORRADOR(la regeneración va a sobreescribirlo). - Upsert el ajuste en
declaraciones.ajustes_remanentecon uncuenta_gasto_codigo(default3701040). - Regenera el F29 con
F29GeneratorService.generateInTransaction(client)(sin emitir hook — el caller decide). - Re-upsert el ajuste asociándolo al nuevo
f29Id. - Emite asiento contable en
declaraciones.asientos_f29:- Debe: cuenta de gasto del ajuste (
3701040). - Haber:
1108002(IVA Crédito Fiscal). - Concepto:
AJUSTE_BAJA_REMANENTE_<periodo>.
- Debe: cuenta de gasto del ajuste (
sequenceDiagram
autonumber
participant H as Handler
participant DS as DeclaracionesService
participant F29G as F29GeneratorService
participant Repo as Repositories
participant DB as PostgreSQL
H->>DS: registrarAjusteBajaRemanente(f29Id, { monto, motivo })
DS->>Repo: findById(f29Id) + validaciones
alt VALIDADO
DS->>Repo: updateStatus(f29Id, 'BORRADOR')
end
DS->>Repo: AjusteRemanenteRepository.upsert(f29Id, datos)
DS->>F29G: generateInTransaction(client, params)
F29G->>DB: regenerar F29 (header + detalle)
F29G-->>DS: nuevo f29Id
DS->>Repo: AjusteRemanenteRepository.upsert(nuevoF29Id, datos)
DS->>DB: DELETE asientos_f29 con concepto AJUSTE_BAJA_REMANENTE_<periodo>
DS->>DB: INSERT 2 filas (cargo gasto, abono IVA crédito)
DS->>DB: COMMIT
DS-->>H: F29Full actualizado Eliminación (eliminarAjusteBajaRemanente) es simétrica: valida estado, borra el ajuste y los asientos, regenera el F29 sin ajuste.
regenerarAsientosAnual(ctx, anio)
Section titled “regenerarAsientosAnual(ctx, anio)”Para uso administrativo: re-genera todos los declaraciones.asientos_f29 del año a partir de los F29 existentes en estado DECLARADO. Falla si no hay ningún F29 declarado en el año (ValidationError).
Útil cuando se modifica la fórmula de asientos o se necesita reconstruir el libro mayor desde cero por inconsistencia.
Estados y transiciones permitidas
Section titled “Estados y transiciones permitidas”| Target | Permitido | Efecto adicional |
|---|---|---|
VALIDADO | ✅ | Solo cambio de estado. |
DECLARADO | ✅ | + contabiliza IVA + asientos. |
ANULADO | ✅ | Solo cambio de estado. |
RECTIFICADO | ✅ | Solo cambio de estado. |
| Target | Permitido | Efecto adicional |
|---|---|---|
BORRADOR | ✅ | Solo cambio de estado. |
DECLARADO | ✅ | + contabiliza IVA + asientos. |
ANULADO | ✅ | Solo cambio de estado. |
RECTIFICADO | ✅ | Solo cambio de estado. |
| Target | Permitido | Guard |
|---|---|---|
BORRADOR | ⚠️ | Solo si no hay IVA PAGADO en el período. |
VALIDADO | ⚠️ | Mismo guard. |
ANULADO | ⚠️ | Mismo guard. |
RECTIFICADO | ✅ | Sin guard adicional. |
Al revertir exitosamente, ejecuta revertirIvaPeriodo (PAGADO → CONTABILIZADO) y regenera asientos.
Hook emitido
Section titled “Hook emitido”| Evento | Cuándo | Payload |
|---|---|---|
f29:status_cambiado | updateStatus cuando normalizedEstado !== estadoActual | { declaracionId, periodo: { anio, mes }, estado, prevEstado } |
No emite en delete, registrarAjusteBajaRemanente ni eliminarAjusteBajaRemanente — esas operaciones invocan F29GeneratorService.generateInTransaction que tampoco emite (por diseño, ver nota en index).
Endpoints
Section titled “Endpoints”| Método | Ruta | Service call |
|---|---|---|
POST | /api/declaraciones/f29/generate | generateF29(ctx, params) |
GET | /api/declaraciones/f29 | list(ctx, filter) |
GET | /api/declaraciones/f29/:id | getById(ctx, id) |
GET | /api/declaraciones/f29/periodo/:periodo/:rut | getByPeriod(ctx, periodo, rut) |
PATCH | /api/declaraciones/f29/:id/status | updateStatus(ctx, id, estado) |
DELETE | /api/declaraciones/f29/:id | delete(ctx, id) |
POST | /api/declaraciones/f29/:id/ajuste-remanente | registrarAjusteBajaRemanente(...) |
DELETE | /api/declaraciones/f29/:id/ajuste-remanente | eliminarAjusteBajaRemanente(ctx, id) |
GET | /api/declaraciones/f29/:id/ajuste-remanente | getAjusteBajaRemanente(ctx, id) |
POST | /api/declaraciones/f29/regenerar-asientos/:anio | regenerarAsientosAnual(ctx, anio) |