Guía de implementación
Administrativo (ERP)
Integra tu ERP con Gestión Administrativa: colaboradores, contratos y novedades de talento humano, novedades y desprendibles de nómina, comprobantes contables en borrador, terceros, impuestos y tesorería con conciliación bancaria.
En esta página
Esta guía es para el equipo técnico de una entidad (caja, fintech o IPS) que integra su ERP con
el módulo Gestión Administrativa de Compense: talento humano, nómina, contabilidad y tesorería.
Todo es server-to-server con la llave de la entidad; no hay usuario final involucrado. Los ejemplos
usan el ERP de Caja Horizonte, que identifica sus documentos con referencias HORIZONTE-ERP-….
Superficie de la empresa, no del afiliado
Aquí los recursos son de la entidad: colaboradores, novedades, comprobantes, terceros, recaudos y
extractos. Las rutas cuelgan de /v1/gateway/admin/… y, en los SDK, de client.admin (en .NET
client.Admin, en Go client.Admin) con cuatro grupos: hr, payroll, accounting y treasury
(en .NET Hr, Payroll, Accounting, Treasury; en Go HR, Payroll, Accounting,
Treasury). La entidad debe tener contratado el módulo; si no, toda llamada responde 403 con
«La entidad no tiene contratado el módulo Gestión Administrativa.».
Qué entra por API y qué se queda en el panel #
| Área | Por API | Se queda en el panel (persona con rol) |
|---|---|---|
| Talento humano | Buscar y leer colaboradores y contratos; alta o actualización de colaboradores (upsert por documento); registrar incapacidades, licencias, permisos y vacaciones | Aprobar o rechazar novedades, contratos, enlazar usuarios del panel, cambiar la cuenta bancaria de un colaborador |
| Nómina | Cargar novedades (horas extra, recargos, bonificaciones, deducciones), una o en lote; leer periodos, desprendibles y el estado de la nómina electrónica | Calcular, aprobar, revertir y contabilizar la nómina; generar y transmitir la nómina electrónica a la DIAN |
| Contabilidad | Crear comprobantes en borrador; leer comprobantes; alta o actualización de terceros; simular impuestos y retenciones | Contabilizar, reversar o anular comprobantes; cerrar periodos; plan de cuentas; datos bancarios de terceros |
| Tesorería | Registrar recaudos, egresos y cuentas de cobro en borrador; cargar extractos y lanzar el cruce automático | Contabilizar o anular los documentos; cerrar la conciliación; cuentas bancarias |
Por qué este corte. Una llave de API no es una persona: no tiene rol, ni segregación de
funciones, ni segundo factor. Todo lo que aprueba, liquida o contabiliza —y por lo tanto mueve
dinero o cierra libros— exige a alguien con rol en el panel. Lo que tu ERP envía entra como
solicitud (novedades de talento humano, en estado SOLICITADA) o como borrador (comprobantes
y documentos de tesorería, en estado DRAFT), y queda auditado con el usuario de integración y el
canal gateway.
flowchart LR
ERP[ERP de Caja Horizonte] -->|POST por API| B[Solicitud / borrador<br/>SOLICITADA · DRAFT]
B --> P{Panel de Compense<br/>persona con rol}
P -->|aprueba / contabiliza| OK[Aprobado · contabilizado]
P -->|rechaza / anula| X[Rechazado · anulado]
B -.->|webhook admin.*| ERPCuentas bancarias. El cambio de cuenta bancaria es el vector clásico de fraude por desvío de
pagos. Por API se acepta la cuenta de nómina solo en el alta de un colaborador; cualquier cambio
posterior, o cualquier dato bancario de un tercero, responde 422 y se hace en el panel, que deja la
alerta en la auditoría.
Datos sensibles (Ley 1581 de 2012). Puedes enviar el diagnóstico (CIE-10) de una incapacidad,
pero nunca se devuelve por API: las novedades de salud se leen como «Ausencia justificada», con
categoría OTRA.
Scopes #
| Scope | Permite |
|---|---|
hr:read | Colaboradores, contratos y novedades de talento humano (lectura) |
hr:write | Alta o actualización de colaboradores; registrar novedades de talento humano |
payroll:read | Novedades de nómina, periodos, desprendibles y nómina electrónica (lectura) |
payroll:write | Cargar novedades de nómina |
accounting:read | Comprobantes, terceros y simulador de impuestos |
accounting:write | Comprobantes en borrador; alta o actualización de terceros |
treasury:write | Recaudos, egresos y cuentas de cobro en borrador; extractos y cruce automático |
Hay un preset admin-erp con los siete. Los presets read-only y full no los incluyen (son
datos de colaboradores y salarios): se otorgan expresamente. payroll:write y treasury:write están
en la lista de scopes que exigirán firma de la petición cuando se active esa protección.
Convenciones #
- Envía
Idempotency-Keyen todoPOST(salvo el simulador de impuestos, que es una consulta). Un reintento con la misma llave devuelve la respuesta original conidempotent_replay: true. - Colaboradores y terceros se nombran por id (UUID) o por número de documento; las cuentas bancarias de tesorería, por id o por número de cuenta.
- Montos en COP como texto decimal (
"1500000.00"); fechasAAAA-MM-DD. - Las lecturas devuelven el cuerpo del motor; las escrituras, el sobre estándar con
"module": "admin".
Campos libres en cada SDK #
Los cuerpos del módulo son amplios (reutilizan los formularios del panel), así que los SDK piden como
argumento solo los campos obligatorios y reciben el resto como campos libres, con los nombres
del contrato en snake_case:
| SDK | Cómo se pasan los campos libres |
|---|---|
| Python | Argumentos con nombre adicionales (**fields): upsert_employee(id_number="…", first_name="Laura") |
| JavaScript / TS | Objeto fields: upsertEmployee({ idNumber: "…", fields: { first_name: "Laura" } }) |
| PHP | Argumentos con nombre en snake_case (variádicos): upsertEmployee(idNumber: '…', first_name: 'Laura') |
| .NET | Diccionario fields: new Dictionary<string, object?> { ["first_name"] = "Laura" } |
| Go | Mapa Fields: Fields: map[string]any{"first_name": "Laura"} |
Los valores nulos se descartan, y los SDK convierten a texto decimal los montos de los campos
amount, subtotal, opening_balance y closing_balance.
Talento humano #
Alta o actualización de colaboradores #
POST/v1/gateway/admin/hr/employees/
hace upsert por id_number: si el colaborador no existe lo crea
(201, created: true); si existe, actualiza solo los campos enviados (200, created: false).
Campos: los de la ficha (first_name, middle_name, last_name, second_last_name, birth_date,
sex, email_personal, email_work, phone, address, city, department, area_id,
position_id, manager_id, status, hire_date, eps_code, pension_fund_code,
severance_fund_code…). Se rechazan user_id (NOT_ALLOWED_BY_API) y el cambio de datos bancarios
de un colaborador existente (BANK_CHANGE_NOT_ALLOWED).
import os
from compense_gateway import CompenseClient
client = CompenseClient(api_key=os.environ["COMPENSE_API_KEY"], environment="sandbox")
tx = client.admin.hr.upsert_employee(
id_number="1032456789",
id_type="CC",
first_name="Laura",
last_name="Pérez",
hire_date="2026-11-01",
email_work="laura.perez@horizonte.example",
phone="3001234567",
bank_name="Banco Uno", bank_account_type="AHORROS", bank_account_number="123456789", # solo en el alta
idempotency_key="horizonte-erp-emp-1032456789-v1",
)
print(tx.data["id"], tx.data["created"])import { CompenseClient } from "@compense/sdk";
const client = new CompenseClient({ apiKey: process.env.COMPENSE_API_KEY!, environment: "sandbox" });
const tx = await client.admin.hr.upsertEmployee({
idNumber: "1032456789",
fields: {
id_type: "CC",
first_name: "Laura",
last_name: "Pérez",
hire_date: "2026-11-01",
email_work: "laura.perez@horizonte.example",
phone: "3001234567",
bank_name: "Banco Uno", bank_account_type: "AHORROS", bank_account_number: "123456789", // solo en el alta
},
idempotencyKey: "horizonte-erp-emp-1032456789-v1",
});
console.log(tx.data.id, tx.data.created);<?php
require __DIR__ . '/vendor/autoload.php';
use Compense\Sdk\CompenseClient;
$client = new CompenseClient(apiKey: getenv('COMPENSE_API_KEY'), environment: 'sandbox');
$tx = $client->admin->hr->upsertEmployee(
idNumber: '1032456789',
idempotencyKey: 'horizonte-erp-emp-1032456789-v1',
id_type: 'CC',
first_name: 'Laura',
last_name: 'Pérez',
hire_date: '2026-11-01',
email_work: 'laura.perez@horizonte.example',
phone: '3001234567',
bank_name: 'Banco Uno', bank_account_type: 'AHORROS', bank_account_number: '123456789', // solo en el alta
);
echo $tx->data['id'], ' ', var_export($tx->data['created'], true), PHP_EOL;using System.Text.Json.Nodes;
using Compense.Sdk;
using Compense.Sdk.Models;
using var client = new CompenseClient(
Environment.GetEnvironmentVariable("COMPENSE_API_KEY")!, CompenseEnvironment.Sandbox);
Transaction tx = await client.Admin.Hr.UpsertEmployeeAsync(
"1032456789",
new Dictionary<string, object?>
{
["id_type"] = "CC",
["first_name"] = "Laura",
["last_name"] = "Pérez",
["hire_date"] = "2026-11-01",
["email_work"] = "laura.perez@horizonte.example",
["phone"] = "3001234567",
["bank_name"] = "Banco Uno", ["bank_account_type"] = "AHORROS", ["bank_account_number"] = "123456789",
},
idempotencyKey: "horizonte-erp-emp-1032456789-v1");
Console.WriteLine($"{tx.Data["id"]} {tx.Data["created"]}");package main
import (
"context"
"fmt"
"log"
"os"
compense "github.com/Appiasoft/compense-saas-sdk-go"
)
func main() {
ctx := context.Background()
client, err := compense.NewClient(os.Getenv("COMPENSE_API_KEY"),
compense.WithEnvironment(compense.Sandbox))
if err != nil {
log.Fatal(err)
}
tx, err := client.Admin.HR.UpsertEmployee(ctx, &compense.UpsertEmployeeParams{
IDNumber: "1032456789",
Fields: map[string]any{
"id_type": "CC",
"first_name": "Laura",
"last_name": "Pérez",
"hire_date": "2026-11-01",
"email_work": "laura.perez@horizonte.example",
"phone": "3001234567",
// solo en el alta
"bank_name": "Banco Uno", "bank_account_type": "AHORROS", "bank_account_number": "123456789",
},
}, compense.WithIdempotencyKey("horizonte-erp-emp-1032456789-v1"))
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Data["id"], tx.Data["created"])
}curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/admin/hr/employees/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-erp-emp-1032456789-v1" \
-H "Content-Type: application/json" \
-d '{"id_number": "1032456789", "id_type": "CC", "first_name": "Laura", "last_name": "Pérez",
"hire_date": "2026-11-01", "email_work": "laura.perez@horizonte.example", "phone": "3001234567",
"bank_name": "Banco Uno", "bank_account_type": "AHORROS", "bank_account_number": "123456789"}'Consultar colaboradores y contratos #
hr.list_employees filtra por q (nombre, documento, código o correo), status (ACTIVO o
RETIRADO), document (exacto), area y position. La ficha y los contratos (salario, tipo,
estado y fechas) se piden por id o documento.
activos = client.admin.hr.list_employees(status="ACTIVO", area="Tesorería")
ficha = client.admin.hr.get_employee("1000000001")
contratos = client.admin.hr.list_employee_contracts("1000000001")
print(ficha["full_name"], [c["salary"] for c in contratos.results])const activos = await client.admin.hr.listEmployees({ status: "ACTIVO", area: "Tesorería" });
const ficha = await client.admin.hr.getEmployee("1000000001");
const contratos = await client.admin.hr.listEmployeeContracts("1000000001");
console.log(ficha.full_name, contratos.results.map((c) => c.salary));$activos = $client->admin->hr->listEmployees(status: 'ACTIVO', area: 'Tesorería');
$ficha = $client->admin->hr->getEmployee('1000000001');
$contratos = $client->admin->hr->listEmployeeContracts('1000000001');
echo $ficha['full_name'], ' ', count($contratos->results), PHP_EOL;Page activos = await client.Admin.Hr.ListEmployeesAsync(status: "ACTIVO", area: "Tesorería");
JsonObject ficha = await client.Admin.Hr.GetEmployeeAsync("1000000001");
Page contratos = await client.Admin.Hr.ListEmployeeContractsAsync("1000000001");
Console.WriteLine($"{ficha["full_name"]} {contratos.Results.Count}");activos, err := client.Admin.HR.ListEmployees(ctx, &compense.EmployeeListParams{Status: "ACTIVO", Area: "Tesorería"})
if err != nil {
log.Fatal(err)
}
ficha, err := client.Admin.HR.GetEmployee(ctx, "1000000001")
if err != nil {
log.Fatal(err)
}
contratos, err := client.Admin.HR.ListEmployeeContracts(ctx, "1000000001")
if err != nil {
log.Fatal(err)
}
fmt.Println(len(activos.Results), ficha["full_name"], len(contratos.Results))curl "https://sandbox.dev.compense.co/gw/v1/gateway/admin/hr/employees/?status=ACTIVO&area=Tesorer%C3%ADa" \
-H "X-API-Key: $COMPENSE_API_KEY"
curl https://sandbox.dev.compense.co/gw/v1/gateway/admin/hr/employees/1000000001/contracts/ \
-H "X-API-Key: $COMPENSE_API_KEY"Novedades de talento humano #
Incapacidades, licencias, permisos y vacaciones. Siempre quedan SOLICITADA: talento humano las
valida en el panel (un approve_now se ignora). Se validan días máximos, cruces con otras novedades
y saldo de vacaciones. Tipos (type_code) del catálogo por defecto: VACACIONES,
PERMISO_REMUNERADO, CITA_MEDICA, CALAMIDAD, LUTO, MATERNIDAD, PATERNIDAD,
INCAPACIDAD_COMUN, INCAPACIDAD_LABORAL, LICENCIA_REMUNERADA, LICENCIA_NO_REMUNERADA,
AUSENCIA y SUSPENSION (tu entidad puede tener los suyos).
tx = client.admin.hr.create_novelty(
employee="1000000001", type_code="INCAPACIDAD_COMUN", start_date="2026-10-20",
days=3, certificate_number="INC-998877", issuing_entity="EPS del colaborador",
diagnosis_code="J069", # se guarda, pero nunca se devuelve
idempotency_key="horizonte-erp-inc-2026-0457",
)
print(tx.data["number"], tx.data["status"], tx.data["category"]) # … SOLICITADA OTRA
pendientes = client.admin.hr.list_novelties(status="SOLICITADA", from_="2026-10-01", to="2026-10-31")const tx = await client.admin.hr.createNovelty({
employee: "1000000001",
typeCode: "INCAPACIDAD_COMUN",
startDate: "2026-10-20",
fields: { days: 3, certificate_number: "INC-998877", issuing_entity: "EPS del colaborador", diagnosis_code: "J069" },
idempotencyKey: "horizonte-erp-inc-2026-0457",
});
console.log(tx.data.number, tx.data.status, tx.data.category);
const pendientes = await client.admin.hr.listNovelties({ status: "SOLICITADA", from: "2026-10-01", to: "2026-10-31" });$tx = $client->admin->hr->createNovelty(
employee: '1000000001',
typeCode: 'INCAPACIDAD_COMUN',
startDate: '2026-10-20',
idempotencyKey: 'horizonte-erp-inc-2026-0457',
days: 3,
certificate_number: 'INC-998877',
issuing_entity: 'EPS del colaborador',
diagnosis_code: 'J069',
);
echo $tx->data['number'], ' ', $tx->data['status'], PHP_EOL;
$pendientes = $client->admin->hr->listNovelties(status: 'SOLICITADA', from: '2026-10-01', to: '2026-10-31');Transaction tx = await client.Admin.Hr.CreateNoveltyAsync(
"1000000001", "INCAPACIDAD_COMUN", "2026-10-20",
new Dictionary<string, object?>
{
["days"] = 3, ["certificate_number"] = "INC-998877",
["issuing_entity"] = "EPS del colaborador", ["diagnosis_code"] = "J069",
},
idempotencyKey: "horizonte-erp-inc-2026-0457");
Page pendientes = await client.Admin.Hr.ListNoveltiesAsync(status: "SOLICITADA", from: "2026-10-01", to: "2026-10-31");tx, err = client.Admin.HR.CreateNovelty(ctx, &compense.HRNoveltyParams{
Employee: "1000000001", TypeCode: "INCAPACIDAD_COMUN", StartDate: "2026-10-20",
Fields: map[string]any{
"days": 3, "certificate_number": "INC-998877",
"issuing_entity": "EPS del colaborador", "diagnosis_code": "J069",
},
}, compense.WithIdempotencyKey("horizonte-erp-inc-2026-0457"))
if err != nil {
log.Fatal(err)
}
pendientes, err := client.Admin.HR.ListNovelties(ctx, &compense.HRNoveltyListParams{
Status: "SOLICITADA", From: "2026-10-01", To: "2026-10-31",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Data["status"], len(pendientes.Results))curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/admin/hr/novelties/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-erp-inc-2026-0457" \
-H "Content-Type: application/json" \
-d '{"employee": "1000000001", "type_code": "INCAPACIDAD_COMUN", "start_date": "2026-10-20",
"days": 3, "certificate_number": "INC-998877", "issuing_entity": "EPS del colaborador",
"diagnosis_code": "J069"}'Nómina #
Cargar novedades de nómina #
POST/v1/gateway/admin/payroll/novelties/
recibe una novedad (campos en la raíz) o un lote en
items (máximo 500, todo o nada: un error señala items[i].campo).
- Horas extra y recargos (
HED,HEN,HRN,HRDDF,HRNDF,HEDDF,HENDF) van conquantityen horas. - Bonificaciones, comisiones y otros devengos (
BONIFICACION_S,BONIFICACION_NS,COMISION,AUXILIO_NS,OTRO_DEVENGO…) y deducciones (DESCUENTO,ANTICIPO,SINDICATO) van conamount. - Los conceptos que calcula el motor (salario, salud, pensión, retención, préstamos…) se rechazan.
period_ides opcional: si lo envías, el periodo debe estarABIERTOoCALCULADO(si no,409 PERIOD_CLOSED) y la fecha debe caer dentro; sindatese usa el inicio del periodo. Si el periodo ya tenía la prenómina calculada, la respuesta trae un aviso: nómina debe recalcularla.
periodos = client.admin.payroll.list_periods(year=2026, status="ABIERTO")
periodo = periodos.results[0]["id"]
# Una novedad
client.admin.payroll.create_novelties(
period_id=periodo, employee="1000000001", concept_code="HED", quantity=4, date="2026-10-15",
idempotency_key="horizonte-erp-he-2026-10-1000000001",
)
# Un lote (todo o nada)
tx = client.admin.payroll.create_novelties(
period_id=periodo,
items=[
{"employee": "1000000001", "concept_code": "BONIFICACION_NS", "amount": "150000", "date": "2026-10-15"},
{"employee": "1000000002", "concept_code": "DESCUENTO", "amount": "20000", "date": "2026-10-16",
"description": "Descuento autorizado"},
],
idempotency_key="horizonte-erp-lote-2026-10-A",
)
print(tx.data["created"], tx.data["warnings"])const periodos = await client.admin.payroll.listPeriods({ year: 2026, status: "ABIERTO" });
const periodo = String(periodos.results[0].id);
// Una novedad
await client.admin.payroll.createNovelties({
periodId: periodo,
fields: { employee: "1000000001", concept_code: "HED", quantity: 4, date: "2026-10-15" },
idempotencyKey: "horizonte-erp-he-2026-10-1000000001",
});
// Un lote (todo o nada)
const tx = await client.admin.payroll.createNovelties({
periodId: periodo,
items: [
{ employee: "1000000001", concept_code: "BONIFICACION_NS", amount: "150000", date: "2026-10-15" },
{ employee: "1000000002", concept_code: "DESCUENTO", amount: "20000", date: "2026-10-16", description: "Descuento autorizado" },
],
idempotencyKey: "horizonte-erp-lote-2026-10-A",
});
console.log(tx.data.created, tx.data.warnings);$periodos = $client->admin->payroll->listPeriods(status: 'ABIERTO', year: 2026);
$periodo = $periodos->results[0]['id'];
// Una novedad
$client->admin->payroll->createNovelties(
periodId: $periodo,
idempotencyKey: 'horizonte-erp-he-2026-10-1000000001',
employee: '1000000001', concept_code: 'HED', quantity: 4, date: '2026-10-15',
);
// Un lote (todo o nada)
$tx = $client->admin->payroll->createNovelties(
periodId: $periodo,
items: [
['employee' => '1000000001', 'concept_code' => 'BONIFICACION_NS', 'amount' => '150000', 'date' => '2026-10-15'],
['employee' => '1000000002', 'concept_code' => 'DESCUENTO', 'amount' => '20000', 'date' => '2026-10-16',
'description' => 'Descuento autorizado'],
],
idempotencyKey: 'horizonte-erp-lote-2026-10-A',
);
echo $tx->data['created'], PHP_EOL;Page periodos = await client.Admin.Payroll.ListPeriodsAsync(status: "ABIERTO", year: 2026);
string periodo = periodos.Results[0]["id"]!.GetValue<string>();
// Una novedad
await client.Admin.Payroll.CreateNoveltiesAsync(
periodo,
fields: new Dictionary<string, object?>
{
["employee"] = "1000000001", ["concept_code"] = "HED", ["quantity"] = 4, ["date"] = "2026-10-15",
},
idempotencyKey: "horizonte-erp-he-2026-10-1000000001");
// Un lote (todo o nada)
Transaction tx = await client.Admin.Payroll.CreateNoveltiesAsync(
periodo,
items: new[]
{
new Dictionary<string, object?> { ["employee"] = "1000000001", ["concept_code"] = "BONIFICACION_NS", ["amount"] = "150000", ["date"] = "2026-10-15" },
new Dictionary<string, object?> { ["employee"] = "1000000002", ["concept_code"] = "DESCUENTO", ["amount"] = "20000", ["date"] = "2026-10-16", ["description"] = "Descuento autorizado" },
},
idempotencyKey: "horizonte-erp-lote-2026-10-A");
Console.WriteLine(tx.Data["created"]);periodos, err := client.Admin.Payroll.ListPeriods(ctx, &compense.PayrollPeriodListParams{Year: 2026, Status: "ABIERTO"})
if err != nil {
log.Fatal(err)
}
periodo, _ := periodos.Results[0]["id"].(string)
// Una novedad
if _, err := client.Admin.Payroll.CreateNovelties(ctx, &compense.PayrollNoveltiesParams{
PeriodID: periodo,
Fields: map[string]any{"employee": "1000000001", "concept_code": "HED", "quantity": 4, "date": "2026-10-15"},
}, compense.WithIdempotencyKey("horizonte-erp-he-2026-10-1000000001")); err != nil {
log.Fatal(err)
}
// Un lote (todo o nada)
lote, err := client.Admin.Payroll.CreateNovelties(ctx, &compense.PayrollNoveltiesParams{
PeriodID: periodo,
Items: []map[string]any{
{"employee": "1000000001", "concept_code": "BONIFICACION_NS", "amount": "150000", "date": "2026-10-15"},
{"employee": "1000000002", "concept_code": "DESCUENTO", "amount": "20000", "date": "2026-10-16",
"description": "Descuento autorizado"},
},
}, compense.WithIdempotencyKey("horizonte-erp-lote-2026-10-A"))
if err != nil {
log.Fatal(err)
}
fmt.Println(lote.Data["created"])curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/admin/payroll/novelties/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-erp-lote-2026-10-A" \
-H "Content-Type: application/json" \
-d '{"period_id": "'"$PERIODO"'", "items": [
{"employee": "1000000001", "concept_code": "BONIFICACION_NS", "amount": "150000", "date": "2026-10-15"},
{"employee": "1000000002", "concept_code": "DESCUENTO", "amount": "20000", "date": "2026-10-16",
"description": "Descuento autorizado"}
]}'Periodos, desprendibles y nómina electrónica #
payroll.list_periods(status, kind, year)ypayroll.get_period(id): estado (ABIERTO,CALCULADO,APROBADO…), fechas y totales.payroll.list_period_payslips(period_id, employee=…): desprendibles del periodo (devengos, deducciones, neto, aportes y provisiones).payroll.get_payslip(id)trae el detalle por concepto.payroll.electronic(year, month, status): estado de la nómina electrónica (DIAN) del mes: proveedor, resumen por estado y documentos. La generación y transmisión se hacen en el panel.payroll.list_novelties(employee, period, status, concept, from, to): lo que ya cargaste.
Cada consulta de desprendibles queda en la bitácora de nómina (quién consultó salarios). El recorrido completo, del cargue de novedades al asiento contable, está en la receta De la nómina a la contabilidad.
Contabilidad #
Comprobantes en borrador #
POST/v1/gateway/admin/accounting/entries/
crea un comprobante siempre en DRAFT (post: true responde
422 POST_NOT_ALLOWED). El contador lo revisa y lo contabiliza en el panel, donde se valida partida
doble, cuentas de movimiento, tercero y centro de costo obligatorios, y periodo abierto.
external_id(tu id en el ERP) hace la creación idempotente en el motor: reenviar el mismoexternal_iddevuelve el comprobante existente (200,created: false). Si el contador anula el borrador, puedes reenviarlo con el mismoexternal_id.- Los comprobantes del ERP llevan
source_module: "gateway": el panel no los edita; se corrigen en el ERP y se reenvían. - Líneas:
account(código),debitocredit, y opcionalesthird_party(id o documento),cost_center,description,tax_concept,base_amount,document_refydue_date.
tx = client.admin.accounting.create_entry(
date="2026-10-27",
description="Venta de servicios octubre",
lines=[
{"account": "13050501", "debit": "1500000", "third_party": "900123456"},
{"account": "41750101", "credit": "1500000"},
],
external_id="HORIZONTE-ERP-AS-2026-000123",
voucher_type="CC",
reference="FV-1234",
idempotency_key="horizonte-erp-as-2026-000123",
)
print(tx.data["id"], tx.data["status"], tx.data["total_debit"]) # … DRAFT 1500000.00const tx = await client.admin.accounting.createEntry({
date: "2026-10-27",
description: "Venta de servicios octubre",
lines: [
{ account: "13050501", debit: "1500000", third_party: "900123456" },
{ account: "41750101", credit: "1500000" },
],
fields: { external_id: "HORIZONTE-ERP-AS-2026-000123", voucher_type: "CC", reference: "FV-1234" },
idempotencyKey: "horizonte-erp-as-2026-000123",
});
console.log(tx.data.id, tx.data.status);$tx = $client->admin->accounting->createEntry(
date: '2026-10-27',
description: 'Venta de servicios octubre',
lines: [
['account' => '13050501', 'debit' => '1500000', 'third_party' => '900123456'],
['account' => '41750101', 'credit' => '1500000'],
],
idempotencyKey: 'horizonte-erp-as-2026-000123',
external_id: 'HORIZONTE-ERP-AS-2026-000123',
voucher_type: 'CC',
reference: 'FV-1234',
);
echo $tx->data['id'], ' ', $tx->data['status'], PHP_EOL;Transaction tx = await client.Admin.Accounting.CreateEntryAsync(
"2026-10-27",
"Venta de servicios octubre",
new[]
{
new Dictionary<string, object?> { ["account"] = "13050501", ["debit"] = "1500000", ["third_party"] = "900123456" },
new Dictionary<string, object?> { ["account"] = "41750101", ["credit"] = "1500000" },
},
new Dictionary<string, object?>
{
["external_id"] = "HORIZONTE-ERP-AS-2026-000123", ["voucher_type"] = "CC", ["reference"] = "FV-1234",
},
idempotencyKey: "horizonte-erp-as-2026-000123");
Console.WriteLine($"{tx.Data["id"]} {tx.Data["status"]}");asiento, err := client.Admin.Accounting.CreateEntry(ctx, &compense.EntryParams{
Date: "2026-10-27",
Description: "Venta de servicios octubre",
Lines: []map[string]any{
{"account": "13050501", "debit": "1500000", "third_party": "900123456"},
{"account": "41750101", "credit": "1500000"},
},
Fields: map[string]any{"external_id": "HORIZONTE-ERP-AS-2026-000123", "voucher_type": "CC", "reference": "FV-1234"},
}, compense.WithIdempotencyKey("horizonte-erp-as-2026-000123"))
if err != nil {
log.Fatal(err)
}
fmt.Println(asiento.Data["id"], asiento.Data["status"])curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/admin/accounting/entries/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-erp-as-2026-000123" \
-H "Content-Type: application/json" \
-d '{"external_id": "HORIZONTE-ERP-AS-2026-000123", "voucher_type": "CC", "date": "2026-10-27",
"description": "Venta de servicios octubre", "reference": "FV-1234",
"lines": [
{"account": "13050501", "debit": "1500000", "third_party": "900123456"},
{"account": "41750101", "credit": "1500000"}
]}'Para saber qué ya contabilizó el contador, consulta accounting.list_entries con status,
voucher_type, date_from, date_to, external_id, source=gateway, q y offset (páginas de
100 con count total), o accounting.get_entry(id) para las líneas, el hash y el estado.
Terceros #
accounting.upsert_third_party hace upsert por tipo y número de documento. id_type acepta el código
DIAN (31, 13, 22…) o el alias (NIT, CC, CE, PA, TI, NUIP…). El dígito de
verificación del NIT se calcula (si lo envías y no coincide, 422). Los datos bancarios (bank_*) y
user_id_link responden 422 BANK_DATA_NOT_ALLOWED; en las lecturas, la cuenta bancaria sale
enmascarada (••••4321).
tx = client.admin.accounting.upsert_third_party(
id_type="NIT", id_number="900123456",
business_name="Papelería La Esquina SAS", is_supplier=True,
email="pagos@laesquina.example", responsibilities=["O-13"],
idempotency_key="horizonte-erp-tp-900123456",
)
proveedores = client.admin.accounting.list_third_parties(role="supplier", q="esquina", brief=True)
tercero = client.admin.accounting.get_third_party("900123456")const tx = await client.admin.accounting.upsertThirdParty({
idType: "NIT",
idNumber: "900123456",
fields: {
business_name: "Papelería La Esquina SAS",
is_supplier: true,
email: "pagos@laesquina.example",
responsibilities: ["O-13"],
},
idempotencyKey: "horizonte-erp-tp-900123456",
});
const proveedores = await client.admin.accounting.listThirdParties({ role: "supplier", q: "esquina", brief: true });
const tercero = await client.admin.accounting.getThirdParty("900123456");$tx = $client->admin->accounting->upsertThirdParty(
idType: 'NIT',
idNumber: '900123456',
idempotencyKey: 'horizonte-erp-tp-900123456',
business_name: 'Papelería La Esquina SAS',
is_supplier: true,
email: 'pagos@laesquina.example',
responsibilities: ['O-13'],
);
$proveedores = $client->admin->accounting->listThirdParties(q: 'esquina', role: 'supplier', brief: true);
$tercero = $client->admin->accounting->getThirdParty('900123456');Transaction tx = await client.Admin.Accounting.UpsertThirdPartyAsync(
"NIT", "900123456",
new Dictionary<string, object?>
{
["business_name"] = "Papelería La Esquina SAS", ["is_supplier"] = true,
["email"] = "pagos@laesquina.example", ["responsibilities"] = new[] { "O-13" },
},
idempotencyKey: "horizonte-erp-tp-900123456");
Page proveedores = await client.Admin.Accounting.ListThirdPartiesAsync(q: "esquina", role: "supplier", brief: true);
JsonObject tercero = await client.Admin.Accounting.GetThirdPartyAsync("900123456");if _, err := client.Admin.Accounting.UpsertThirdParty(ctx, &compense.ThirdPartyParams{
IDType: "NIT", IDNumber: "900123456",
Fields: map[string]any{
"business_name": "Papelería La Esquina SAS", "is_supplier": true,
"email": "pagos@laesquina.example", "responsibilities": []string{"O-13"},
},
}, compense.WithIdempotencyKey("horizonte-erp-tp-900123456")); err != nil {
log.Fatal(err)
}
tercero, err := client.Admin.Accounting.GetThirdParty(ctx, "900123456")
if err != nil {
log.Fatal(err)
}
fmt.Println(tercero["name"], tercero["bank_account_number"])curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/admin/accounting/third-parties/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-erp-tp-900123456" \
-H "Content-Type: application/json" \
-d '{"id_type": "NIT", "id_number": "900123456", "business_name": "Papelería La Esquina SAS",
"is_supplier": true, "email": "pagos@laesquina.example", "responsibilities": ["O-13"]}'Simulador de impuestos y retenciones #
POST/v1/gateway/admin/accounting/taxes/compute/
es una consulta pura: no escribe, no consume cuota y no lleva
Idempotency-Key (los SDK no la envían ni reintentan la llamada). Úsalo para calcular retenciones
antes de armar las líneas del comprobante.
calculo = client.admin.accounting.compute_taxes(
items=[{"concept": "RF_SERVICIOS", "base": "2000000", "iva": "380000"}],
date="2026-10-27", third_party="900123456",
)
for r in calculo.results:
print(r["concept"], r["base"], r["rate"], r["amount"])const calculo = await client.admin.accounting.computeTaxes({
items: [{ concept: "RF_SERVICIOS", base: "2000000", iva: "380000" }],
date: "2026-10-27",
thirdParty: "900123456",
});
for (const r of calculo.results) console.log(r.concept, r.base, r.rate, r.amount);$calculo = $client->admin->accounting->computeTaxes(
items: [['concept' => 'RF_SERVICIOS', 'base' => '2000000', 'iva' => '380000']],
date: '2026-10-27',
thirdParty: '900123456',
);
foreach ($calculo->results as $r) {
echo $r['concept'], ' ', $r['rate'], ' ', $r['amount'], PHP_EOL;
}Page calculo = await client.Admin.Accounting.ComputeTaxesAsync(
new[] { new Dictionary<string, object?> { ["concept"] = "RF_SERVICIOS", ["base"] = "2000000", ["iva"] = "380000" } },
date: "2026-10-27", thirdParty: "900123456");
foreach (JsonObject r in calculo.Results)
Console.WriteLine($"{r["concept"]} {r["rate"]} {r["amount"]}");calculo, err := client.Admin.Accounting.ComputeTaxes(ctx, &compense.ComputeTaxesParams{
Items: []map[string]any{{"concept": "RF_SERVICIOS", "base": "2000000", "iva": "380000"}},
Date: "2026-10-27",
ThirdParty: "900123456",
})
if err != nil {
log.Fatal(err)
}
for _, r := range calculo.Results {
fmt.Println(r["concept"], r["rate"], r["amount"])
}curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/admin/accounting/taxes/compute/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"date": "2026-10-27", "third_party": "900123456",
"items": [{"concept": "RF_SERVICIOS", "base": "2000000", "iva": "380000"}]}'Tesorería #
Todos los documentos quedan en DRAFT; el tesorero o el contador los contabilizan en el panel.
| Operación | Documento | Campos principales |
|---|---|---|
POST/v1/gateway/admin/treasury/receipts/ | Recaudo (recibo de caja) | third_party, bank_account, date, amount, method, reference, applications ([{document, amount}] o [{account, document_ref, amount}]) |
POST/v1/gateway/admin/treasury/payments/ | Egreso (pago a proveedor) | third_party, bank_account, date, amount, method (TRANSFERENCIA, PSE, CHEQUE, EFECTIVO, OTRO), reference, applications ([{invoice, amount}]) |
POST/v1/gateway/admin/treasury/receivables/ | Cuenta de cobro (cartera) | third_party, issue_date, due_date, description, income_account, subtotal, iva_concept, cost_center |
POST/v1/gateway/admin/treasury/statements/ | Extracto bancario | bank_account, opening_balance, closing_balance, date_from, date_to y lines ([{date, description, reference, amount}], montos con signo) o content (CSV) |
POST/v1/gateway/admin/treasury/statements/{statement_id}/auto-match/ | Cruce automático | Sin cuerpo |
Recaudo, egreso y cuenta de cobro #
recaudo = client.admin.treasury.create_receipt(
third_party="80111222", bank_account="0011223344", date="2026-10-27",
amount="850000", method="TRANSFERENCIA", reference="TRX-998877",
idempotency_key="horizonte-erp-rc-TRX-998877",
)
egreso = client.admin.treasury.create_payment(
third_party="900123456", bank_account="0011223344", date="2026-10-28",
amount="1200000", method="PSE", reference="PSE-4455",
idempotency_key="horizonte-erp-eg-PSE-4455",
)
cobro = client.admin.treasury.create_receivable(
third_party="900123456", issue_date="2026-10-27", due_date="2026-11-26",
description="Arriendo de salón octubre", income_account="41552001", subtotal="500000",
idempotency_key="horizonte-erp-cxc-2026-10-031",
)
print(recaudo.data["status"], egreso.data["status"], cobro.data["total"]) # DRAFT DRAFT …const recaudo = await client.admin.treasury.createReceipt({
thirdParty: "80111222",
fields: { bank_account: "0011223344", date: "2026-10-27", amount: "850000", method: "TRANSFERENCIA", reference: "TRX-998877" },
idempotencyKey: "horizonte-erp-rc-TRX-998877",
});
const egreso = await client.admin.treasury.createPayment({
thirdParty: "900123456",
fields: { bank_account: "0011223344", date: "2026-10-28", amount: "1200000", method: "PSE", reference: "PSE-4455" },
idempotencyKey: "horizonte-erp-eg-PSE-4455",
});
const cobro = await client.admin.treasury.createReceivable({
thirdParty: "900123456",
fields: {
issue_date: "2026-10-27", due_date: "2026-11-26", description: "Arriendo de salón octubre",
income_account: "41552001", subtotal: "500000",
},
idempotencyKey: "horizonte-erp-cxc-2026-10-031",
});$recaudo = $client->admin->treasury->createReceipt(
thirdParty: '80111222',
idempotencyKey: 'horizonte-erp-rc-TRX-998877',
bank_account: '0011223344', date: '2026-10-27', amount: '850000',
method: 'TRANSFERENCIA', reference: 'TRX-998877',
);
$egreso = $client->admin->treasury->createPayment(
thirdParty: '900123456',
idempotencyKey: 'horizonte-erp-eg-PSE-4455',
bank_account: '0011223344', date: '2026-10-28', amount: '1200000',
method: 'PSE', reference: 'PSE-4455',
);
$cobro = $client->admin->treasury->createReceivable(
thirdParty: '900123456',
idempotencyKey: 'horizonte-erp-cxc-2026-10-031',
issue_date: '2026-10-27', due_date: '2026-11-26', description: 'Arriendo de salón octubre',
income_account: '41552001', subtotal: '500000',
);Transaction recaudo = await client.Admin.Treasury.CreateReceiptAsync(
"80111222",
new Dictionary<string, object?>
{
["bank_account"] = "0011223344", ["date"] = "2026-10-27", ["amount"] = "850000",
["method"] = "TRANSFERENCIA", ["reference"] = "TRX-998877",
},
idempotencyKey: "horizonte-erp-rc-TRX-998877");
Transaction egreso = await client.Admin.Treasury.CreatePaymentAsync(
"900123456",
new Dictionary<string, object?>
{
["bank_account"] = "0011223344", ["date"] = "2026-10-28", ["amount"] = "1200000",
["method"] = "PSE", ["reference"] = "PSE-4455",
},
idempotencyKey: "horizonte-erp-eg-PSE-4455");
Transaction cobro = await client.Admin.Treasury.CreateReceivableAsync(
"900123456",
new Dictionary<string, object?>
{
["issue_date"] = "2026-10-27", ["due_date"] = "2026-11-26", ["description"] = "Arriendo de salón octubre",
["income_account"] = "41552001", ["subtotal"] = "500000",
},
idempotencyKey: "horizonte-erp-cxc-2026-10-031");recaudo, err := client.Admin.Treasury.CreateReceipt(ctx, &compense.TreasuryDocumentParams{
ThirdParty: "80111222",
Fields: map[string]any{
"bank_account": "0011223344", "date": "2026-10-27", "amount": "850000",
"method": "TRANSFERENCIA", "reference": "TRX-998877",
},
}, compense.WithIdempotencyKey("horizonte-erp-rc-TRX-998877"))
if err != nil {
log.Fatal(err)
}
if _, err := client.Admin.Treasury.CreatePayment(ctx, &compense.TreasuryDocumentParams{
ThirdParty: "900123456",
Fields: map[string]any{
"bank_account": "0011223344", "date": "2026-10-28", "amount": "1200000",
"method": "PSE", "reference": "PSE-4455",
},
}, compense.WithIdempotencyKey("horizonte-erp-eg-PSE-4455")); err != nil {
log.Fatal(err)
}
cobro, err := client.Admin.Treasury.CreateReceivable(ctx, &compense.TreasuryDocumentParams{
ThirdParty: "900123456",
Fields: map[string]any{
"issue_date": "2026-10-27", "due_date": "2026-11-26", "description": "Arriendo de salón octubre",
"income_account": "41552001", "subtotal": "500000",
},
}, compense.WithIdempotencyKey("horizonte-erp-cxc-2026-10-031"))
if err != nil {
log.Fatal(err)
}
fmt.Println(recaudo.Data["status"], cobro.Data["status"])curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/admin/treasury/receipts/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-erp-rc-TRX-998877" \
-H "Content-Type: application/json" \
-d '{"third_party": "80111222", "bank_account": "0011223344", "date": "2026-10-27",
"amount": "850000", "method": "TRANSFERENCIA", "reference": "TRX-998877"}'Importar el extracto y conciliar #
Carga el extracto del banco con lines (montos con signo: positivo entra, negativo sale) o con
content (CSV con columnas fecha/valor o débito/crédito). Luego lanza el cruce automático, que
empareja cada movimiento del extracto con un movimiento contabilizado del banco por valor exacto,
cercanía de fecha y referencia. El cruce no crea asientos ni cierra la conciliación: el tesorero
revisa y cierra en el panel.
sequenceDiagram
autonumber
participant E as ERP de Caja Horizonte
participant G as Gateway Compense
participant T as Tesorero (panel)
E->>G: POST treasury/statements/ {bank_account, lines}
G-->>E: 201 extracto OPEN, líneas UNMATCHED
G-)E: webhook admin.treasury.statement.imported
E->>G: POST treasury/statements/{id}/auto-match/
G-->>E: {matched: N}
T->>T: revisa pendientes y cierra la conciliacióntx = client.admin.treasury.import_statement(
bank_account="0011223344",
opening_balance="0",
lines=[
{"date": "2026-10-02", "description": "Consignación", "reference": "TRX-998877", "amount": "850000"},
{"date": "2026-10-03", "description": "Comisión", "reference": "C1", "amount": "-12000.50"},
],
idempotency_key="horizonte-erp-ext-0011223344-2026-10",
)
extracto = tx.data["id"]
cruce = client.admin.treasury.auto_match_statement(
extracto, idempotency_key=f"horizonte-erp-cruce-{extracto}")
print(cruce.data["matched"])const tx = await client.admin.treasury.importStatement({
bankAccount: "0011223344",
fields: {
opening_balance: "0",
lines: [
{ date: "2026-10-02", description: "Consignación", reference: "TRX-998877", amount: "850000" },
{ date: "2026-10-03", description: "Comisión", reference: "C1", amount: "-12000.50" },
],
},
idempotencyKey: "horizonte-erp-ext-0011223344-2026-10",
});
const extracto = String(tx.data.id);
const cruce = await client.admin.treasury.autoMatchStatement(extracto, {
idempotencyKey: `horizonte-erp-cruce-${extracto}`,
});
console.log(cruce.data.matched);$tx = $client->admin->treasury->importStatement(
bankAccount: '0011223344',
idempotencyKey: 'horizonte-erp-ext-0011223344-2026-10',
opening_balance: '0',
lines: [
['date' => '2026-10-02', 'description' => 'Consignación', 'reference' => 'TRX-998877', 'amount' => '850000'],
['date' => '2026-10-03', 'description' => 'Comisión', 'reference' => 'C1', 'amount' => '-12000.50'],
],
);
$extracto = $tx->data['id'];
$cruce = $client->admin->treasury->autoMatchStatement($extracto, idempotencyKey: "horizonte-erp-cruce-{$extracto}");
echo $cruce->data['matched'], PHP_EOL;Transaction tx = await client.Admin.Treasury.ImportStatementAsync(
"0011223344",
new Dictionary<string, object?>
{
["opening_balance"] = "0",
["lines"] = new[]
{
new Dictionary<string, object?> { ["date"] = "2026-10-02", ["description"] = "Consignación", ["reference"] = "TRX-998877", ["amount"] = "850000" },
new Dictionary<string, object?> { ["date"] = "2026-10-03", ["description"] = "Comisión", ["reference"] = "C1", ["amount"] = "-12000.50" },
},
},
idempotencyKey: "horizonte-erp-ext-0011223344-2026-10");
string extracto = tx.Data["id"]!.GetValue<string>();
Transaction cruce = await client.Admin.Treasury.AutoMatchStatementAsync(
extracto, idempotencyKey: $"horizonte-erp-cruce-{extracto}");
Console.WriteLine(cruce.Data["matched"]);ext, err := client.Admin.Treasury.ImportStatement(ctx, &compense.ImportStatementParams{
BankAccount: "0011223344",
Fields: map[string]any{
"opening_balance": "0",
"lines": []map[string]any{
{"date": "2026-10-02", "description": "Consignación", "reference": "TRX-998877", "amount": "850000"},
{"date": "2026-10-03", "description": "Comisión", "reference": "C1", "amount": "-12000.50"},
},
},
}, compense.WithIdempotencyKey("horizonte-erp-ext-0011223344-2026-10"))
if err != nil {
log.Fatal(err)
}
extracto, _ := ext.Data["id"].(string)
cruce, err := client.Admin.Treasury.AutoMatchStatement(ctx, extracto,
compense.WithIdempotencyKey("horizonte-erp-cruce-"+extracto))
if err != nil {
log.Fatal(err)
}
fmt.Println(cruce.Data["matched"])curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/admin/treasury/statements/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-erp-ext-0011223344-2026-10" \
-H "Content-Type: application/json" \
-d '{"bank_account": "0011223344", "opening_balance": "0",
"lines": [
{"date": "2026-10-02", "description": "Consignación", "reference": "TRX-998877", "amount": "850000"},
{"date": "2026-10-03", "description": "Comisión", "reference": "C1", "amount": "-12000.50"}
]}'
curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/admin/treasury/statements/$EXTRACTO/auto-match/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-erp-cruce-$EXTRACTO"Errores frecuentes #
| HTTP | Código del motor | Cuándo |
|---|---|---|
401 | — | Falta X-API-Key |
403 | — | Llave sin el scope, IP no permitida o módulo Gestión Administrativa no contratado |
404 | NOT_FOUND | Colaborador, periodo, desprendible, comprobante o tercero inexistente |
409 | PERIOD_CLOSED | El periodo de nómina está aprobado o revertido |
409 | — | Conflicto de idempotencia, o conflicto de negocio (la novedad se cruza con otra) |
422 | VALIDATION_ERROR | detail dice el campo y el motivo en español |
422 | POST_NOT_ALLOWED | Intentaste contabilizar un comprobante por API |
422 | BANK_CHANGE_NOT_ALLOWED / BANK_DATA_NOT_ALLOWED | Datos bancarios de un colaborador existente o de un tercero |
422 | NOT_ALLOWED_BY_API | Campos reservados al panel (por ejemplo user_id) |
429 | — | Límite por minuto o cuota mensual |
502 / 504 | — | El motor falló o no respondió (cita el trace_id) |
{"type": "https://errors.compense.co/gateway/upstream-error", "title": "Request rejected by engine",
"status": 422,
"detail": "Por API los comprobantes se crean en borrador; el contador los contabiliza en el panel.",
"trace_id": "9f1c2e7a4b8d4e6fa1c0d2e3f4a5b6c7"}Eventos #
Se emiten en la misma transacción que el registro, con event_id derivado de la Idempotency-Key
(un reintento no duplica el callback). Ninguno significa que se movió dinero: son solicitudes o
borradores. Todos llevan channel: "gateway" y el sobre estándar; nunca diagnósticos ni datos
bancarios.
Administrativo #
| Evento | Desde | Cuándo se emite |
|---|---|---|
admin.hr.novelty.received | 2026-10-27 | Se registró una novedad de talento humano. |
admin.payroll.novelty.received | 2026-10-27 | Se cargó una novedad de nómina. |
admin.accounting.entry.drafted | 2026-10-27 | Se creó un comprobante contable en borrador. |
admin.treasury.document.registered | 2026-10-27 | Se registró un recaudo, egreso o cuenta de cobro en borrador (kind). |
admin.treasury.statement.imported | 2026-10-27 | Se cargó un extracto bancario. |
| Evento | Campos de resource |
|---|---|
admin.payroll.novelty.received | novelty_id, employee_id, concept_code, date, quantity, amount, period_id, status |
admin.hr.novelty.received | novelty_id, number, employee_id, category (OTRA si es de salud), fechas, status |
admin.accounting.entry.drafted | entry_id, external_id, voucher_type, date, totales, status |
admin.treasury.document.registered | document_id, kind (receipt, payment o receivable), third_party_id, date, amount, status, reference |
admin.treasury.statement.imported | statement_id, bank_account_id, rango de fechas, lines, closing_balance |
Hoy no hay evento cuando talento humano aprueba o rechaza una novedad, ni cuando nómina aprueba el periodo: consulta el estado con las lecturas.
Sandbox #
Con una llave de sandbox las respuestas son deterministas y no tocan datos reales:
- Colaboradores de prueba: CC 1000000001 (Laura Sandbox) y CC 1000000002 (Andrés Sandbox) ya
existen: un upsert responde
created: falsey enviar datos bancarios da422. Cualquier otro documento se «crea». CC 9999999999 →404. - Periodos:
payroll.list_periodsdevuelve uno ABIERTO (per-O-…, octubre de 2026) y uno APROBADO (per-C-…, septiembre de 2026): cargarle novedades da409 PERIOD_CLOSED. Los conceptos del sistema (SALARIO,SALUD…) →422. - Comprobantes: siempre
DRAFT; mismoexternal_id→ mismo id;post: true→422. - Terceros: NIT 900000001 ya existe (
created: false); datos bancarios →422. - Tesorería: documentos
DRAFT; valor<= 0→422; el cruce automático respondematched: 0. - El simulador de impuestos aplica una tarifa fija del 4 % a cada base.