Sandbox
Relojes de prueba
Tiempo virtual para el sandbox: crea un reloj fijo en una fecha, adelántalo días o semanas y usa su hora como el ‘ahora’ de tus pruebas de mora, vencimientos y permanencias. Los relojes no guardan estado: cada avance devuelve un reloj con un id nuevo.
En esta página
Hay comportamientos que solo aparecen con el paso del tiempo: una cuota que pasa a mora, una
obligación que vence, un subsidio que exige permanencia. Probarlos esperando días reales no es
opción. Un reloj de prueba (test clock) es una fuente de tiempo virtual: lo fijas en una fecha
(frozen_time) y lo adelantas las veces que quieras; su current_time es el “ahora” de tu
escenario.
Solo con llaves de sandbox
Las operaciones de relojes (
POST/v1/gateway/sandbox/test-clocks/
,
GET/v1/gateway/sandbox/test-clocks/{clock_id}/
y
POST/v1/gateway/sandbox/test-clocks/{clock_id}/advance/
) responden 403 a una llave de producción, sin importar la URL.
Cómo funcionan #
Los relojes no guardan estado en el servidor. Todo el estado (la fecha fija y los segundos
avanzados) viaja dentro del id, con la forma tcl-<datos>-<firma>:
- Cada
advancedevuelve un reloj con un id NUEVO. Guarda siempre el id que te devuelve la última llamada y continúa desde él. El id viejo sigue siendo válido, pero representa el reloj antes del avance. - El id lleva una firma de integridad: si lo editas a mano (por ejemplo, para “retroceder” el
tiempo) el gateway lo rechaza con
422. - Como el resultado depende solo del id y del avance, el mismo
advancesobre el mismo id da siempre el mismo reloj. Por eso estas rutas no usanIdempotency-Key. - Solo avanzan:
delta_secondsdebe ser>= 0y como máximo 63 días (5.443.200 segundos) por llamada. Para saltos mayores, avanza varias veces. frozen_timeacepta ISO-8601 (2026-10-01T00:00:00Z) o segundos Unix. Una fecha sin zona horaria se interpreta en UTC.
sequenceDiagram
participant T as Tu prueba
participant G as Gateway (sandbox)
T->>G: create(frozen_time = 2026-10-01T00:00:00Z)
G-->>T: tcl-A (current_time 2026-10-01, advanced 0)
T->>G: advance(tcl-A, 30 días)
G-->>T: tcl-B (current_time 2026-10-31, advanced 2.592.000)
T->>G: advance(tcl-B, 15 días)
G-->>T: tcl-C (current_time 2026-11-15, advanced 3.888.000)
T->>G: get(tcl-B)
G-->>T: tcl-B sigue en 2026-10-31Crear, adelantar y consultar #
import os
from compense_gateway import CompenseClient
client = CompenseClient(api_key=os.environ["COMPENSE_API_KEY"], environment="sandbox")
reloj = client.sandbox.test_clocks.create(frozen_time="2026-10-01T00:00:00Z")
print(reloj.id, reloj.current_time) # tcl-… 2026-10-01T00:00:00+00:00
reloj = client.sandbox.test_clocks.advance(reloj.id, delta_seconds=30 * 86400)
print(reloj.id, reloj.current_time, reloj.advanced_seconds) # id NUEVO, +30 días
mismo = client.sandbox.test_clocks.get(reloj.id)
print(mismo.frozen_time, mismo.current_time, mismo.status) # readyimport { CompenseClient } from "@compense/sdk";
const client = new CompenseClient({ apiKey: process.env.COMPENSE_API_KEY!, environment: "sandbox" });
let reloj = await client.sandbox.testClocks.create({ frozenTime: "2026-10-01T00:00:00Z" });
console.log(reloj.id, reloj.currentTime);
reloj = await client.sandbox.testClocks.advance(reloj.id, { deltaSeconds: 30 * 86400 });
console.log(reloj.id, reloj.currentTime, reloj.advancedSeconds); // id NUEVO, +30 días
const mismo = await client.sandbox.testClocks.get(reloj.id);
console.log(mismo.frozenTime, mismo.currentTime, mismo.status);<?php
require __DIR__ . '/vendor/autoload.php';
use Compense\Sdk\CompenseClient;
$client = new CompenseClient(apiKey: getenv('COMPENSE_API_KEY'), environment: 'sandbox');
$reloj = $client->sandbox->testClocks->create('2026-10-01T00:00:00Z'); // o un \DateTimeInterface
echo $reloj->id, ' ', $reloj->currentTime, PHP_EOL;
$reloj = $client->sandbox->testClocks->advance($reloj->id, deltaSeconds: 30 * 86400);
echo $reloj->id, ' ', $reloj->currentTime, ' ', $reloj->advancedSeconds, PHP_EOL; // id NUEVO
$mismo = $client->sandbox->testClocks->get($reloj->id);
echo $mismo->frozenTime, ' ', $mismo->currentTime, ' ', $mismo->status, PHP_EOL;using Compense.Sdk;
using Compense.Sdk.Models;
using var client = new CompenseClient(
Environment.GetEnvironmentVariable("COMPENSE_API_KEY")!, CompenseEnvironment.Sandbox);
TestClock reloj = await client.Sandbox.TestClocks.CreateAsync("2026-10-01T00:00:00Z"); // o DateTimeOffset / segundos Unix
Console.WriteLine($"{reloj.Id} {reloj.CurrentTime}");
reloj = await client.Sandbox.TestClocks.AdvanceAsync(reloj.Id, 30 * 86400);
Console.WriteLine($"{reloj.Id} {reloj.CurrentTime} {reloj.AdvancedSeconds}"); // id NUEVO
TestClock mismo = await client.Sandbox.TestClocks.GetAsync(reloj.Id);
Console.WriteLine($"{mismo.FrozenTime} {mismo.CurrentTime} {mismo.Status}");package main
import (
"context"
"fmt"
"log"
"os"
"time"
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)
}
reloj, err := client.Sandbox.TestClocks.Create(ctx, &compense.CreateTestClockParams{
FrozenTime: time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC),
})
if err != nil {
log.Fatal(err)
}
fmt.Println(reloj.ID, reloj.CurrentTime)
reloj, err = client.Sandbox.TestClocks.Advance(ctx, reloj.ID,
&compense.AdvanceTestClockParams{DeltaSeconds: 30 * 86400})
if err != nil {
log.Fatal(err)
}
fmt.Println(reloj.ID, reloj.CurrentTime, reloj.AdvancedSeconds) // id NUEVO
mismo, err := client.Sandbox.TestClocks.Get(ctx, reloj.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println(mismo.FrozenTime, mismo.CurrentTime, mismo.Status)
}BASE="https://sandbox.dev.compense.co/gw/v1/gateway/sandbox/test-clocks"
curl -X POST "$BASE/" \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"frozen_time": "2026-10-01T00:00:00Z"}'
curl -X POST "$BASE/$CLOCK_ID/advance/" \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"delta_seconds": 2592000}'
curl "$BASE/$CLOCK_ID/" -H "X-API-Key: $COMPENSE_API_KEY"El reloj tiene esta forma (en cURL, create y advance lo devuelven dentro de data del sobre de
escritura y get lo devuelve directo; los SDK desenvuelven el sobre por ti):
{
"id": "tcl-MTc5MDgxMjgwMC4yNTkyMDAw-9fd1bf0d88220638",
"object": "test_clock",
"status": "ready",
"frozen_time": "2026-10-01T00:00:00+00:00",
"current_time": "2026-10-31T00:00:00+00:00",
"advanced_seconds": 2592000,
"drives": ["loan_aging_dpd", "provisioning_rollforward", "subsidy_permanence"],
"environment": "sandbox"
}| Campo | Qué es |
|---|---|
id | El reloj. Cambia en cada avance. |
frozen_time | La fecha en que lo fijaste al crearlo. No cambia. |
advanced_seconds | Total de segundos avanzados desde frozen_time. |
current_time | frozen_time + advanced_seconds: el “ahora” de tu escenario. |
drives | Para qué está pensado: envejecimiento de mora (loan_aging_dpd), provisiones (provisioning_rollforward) y permanencia de subsidios (subsidy_permanence). |
El reloj es tu fuente de 'ahora', no el del gateway
En esta versión ninguna otra operación del sandbox recibe el id de un reloj: las fechas de cartera,
salud o PQRS del sandbox son fijas (por ejemplo, la primera cuota de los préstamos de prueba vence el
15 de julio de 2026). Usa current_time como el “ahora” de tu sistema durante la prueba (inyéctalo
en tu servicio de fechas) y compáralo con las fechas que devuelve el sandbox.
Caso de uso: mora de cartera #
Quieres probar que tu sistema clasifica bien la mora de una cuota según los días vencidos (0, 30, 60,
90 días o más). La primera cuota de los préstamos de prueba vence el 15 de julio de 2026. Fija un
reloj ese día y adelántalo por tramos; como cada llamada admite como máximo 63 días, el ejemplo avanza
en saltos y evalúa tu función de clasificación en cada current_time.
from datetime import datetime
MAX = 63 * 86400
reloj = client.sandbox.test_clocks.create(frozen_time="2026-07-15T00:00:00Z")
vence = datetime.fromisoformat(reloj.frozen_time)
pendiente = 95 * 86400
while pendiente > 0:
paso = min(pendiente, MAX)
reloj = client.sandbox.test_clocks.advance(reloj.id, delta_seconds=paso)
pendiente -= paso
dias = (datetime.fromisoformat(reloj.current_time) - vence).days
print(reloj.current_time, dias, clasificar_mora(dias)) # tu funciónconst MAX = 63 * 86400;
let reloj = await client.sandbox.testClocks.create({ frozenTime: "2026-07-15T00:00:00Z" });
const vence = Date.parse(reloj.frozenTime);
let pendiente = 95 * 86400;
while (pendiente > 0) {
const paso = Math.min(pendiente, MAX);
reloj = await client.sandbox.testClocks.advance(reloj.id, { deltaSeconds: paso });
pendiente -= paso;
const dias = Math.floor((Date.parse(reloj.currentTime) - vence) / 86_400_000);
console.log(reloj.currentTime, dias, clasificarMora(dias)); // tu función
}$max = 63 * 86400;
$reloj = $client->sandbox->testClocks->create('2026-07-15T00:00:00Z');
$vence = new DateTimeImmutable($reloj->frozenTime);
$pendiente = 95 * 86400;
while ($pendiente > 0) {
$paso = min($pendiente, $max);
$reloj = $client->sandbox->testClocks->advance($reloj->id, deltaSeconds: $paso);
$pendiente -= $paso;
$dias = $vence->diff(new DateTimeImmutable($reloj->currentTime))->days;
echo $reloj->currentTime, ' ', $dias, ' ', clasificarMora($dias), PHP_EOL; // tu función
}const long Max = 63 * 86400;
TestClock reloj = await client.Sandbox.TestClocks.CreateAsync("2026-07-15T00:00:00Z");
DateTimeOffset vence = DateTimeOffset.Parse(reloj.FrozenTime);
long pendiente = 95 * 86400;
while (pendiente > 0)
{
long paso = Math.Min(pendiente, Max);
reloj = await client.Sandbox.TestClocks.AdvanceAsync(reloj.Id, paso);
pendiente -= paso;
int dias = (int)(DateTimeOffset.Parse(reloj.CurrentTime) - vence).TotalDays;
Console.WriteLine($"{reloj.CurrentTime} {dias} {ClasificarMora(dias)}"); // tu función
}const max = int64(63 * 86400)
reloj, err := client.Sandbox.TestClocks.Create(ctx, &compense.CreateTestClockParams{
FrozenTime: time.Date(2026, 7, 15, 0, 0, 0, 0, time.UTC),
})
if err != nil {
log.Fatal(err)
}
vence, _ := time.Parse(time.RFC3339, reloj.FrozenTime)
for pendiente := int64(95 * 86400); pendiente > 0; {
paso := min(pendiente, max)
reloj, err = client.Sandbox.TestClocks.Advance(ctx, reloj.ID, &compense.AdvanceTestClockParams{DeltaSeconds: paso})
if err != nil {
log.Fatal(err)
}
pendiente -= paso
ahora, _ := time.Parse(time.RFC3339, reloj.CurrentTime)
dias := int(ahora.Sub(vence).Hours() / 24)
fmt.Println(reloj.CurrentTime, dias, clasificarMora(dias)) // tu función
}BASE="https://sandbox.dev.compense.co/gw/v1/gateway/sandbox/test-clocks"
ID=$(curl -s -X POST "$BASE/" -H "X-API-Key: $COMPENSE_API_KEY" -H "Content-Type: application/json" \
-d '{"frozen_time": "2026-07-15T00:00:00Z"}' | jq -r '.data.id // .id')
for PASO in 5443200 2764800; do # 63 días + 32 días = 95 días
ID=$(curl -s -X POST "$BASE/$ID/advance/" -H "X-API-Key: $COMPENSE_API_KEY" \
-H "Content-Type: application/json" -d "{\"delta_seconds\": $PASO}" | jq -r '.data.id // .id')
curl -s "$BASE/$ID/" -H "X-API-Key: $COMPENSE_API_KEY" | jq -r '.current_time'
doneCombínalo con el préstamo de prueba en mora de 95 días (CC 1000000004, estado DEFAULTED) para
comparar tu cálculo con lo que devuelve la cartera del sandbox.
Otros escenarios típicos:
- Vencimientos: fija el reloj el día antes del vencimiento de una obligación, avanza 1 día y verifica que tu sistema genera el aviso o el recargo.
- Recordatorios: avanza en saltos de 24 horas y comprueba que tu tarea programada envía cada recordatorio una sola vez.
- Permanencia: avanza meses (en saltos de hasta 63 días) para probar reglas que exigen un tiempo mínimo de afiliación.
Errores #
| HTTP | Cuándo |
|---|---|
403 | La llave es de producción. |
422 | frozen_time vacío o no es ISO-8601 ni segundos Unix; el id no es un reloj (tcl-…) o fue alterado; delta_seconds negativo, no entero o mayor que 63 días. |
Notas por SDK #
- Python:
advance(..., by_seconds=...)es el nombre deprecado (1.0) dedelta_seconds;advance(..., to=...)no existe en el gateway y lanzaCompenseConfigError;create(name=...)se ignora con un aviso (los relojes no tienen nombre). El modelo esTestClock(id,frozen_time,current_time,advanced_seconds,status,raw). - JavaScript / TS:
create({ frozenTime })acepta texto ISO-8601 o segundos Unix. - PHP:
create()acepta texto, segundos Unix (int) o\DateTimeInterface. - .NET:
CreateAsynctiene sobrecargas parastring,long(segundos Unix) yDateTimeOffset;AdvanceAsync(id, deltaSeconds)recibelong. - Go:
CreateTestClockParams.FrozenTimees untime.Time(se envía como RFC 3339 en UTC);AdvanceTestClockParams.DeltaSecondsesint64. - CLI:
compense clock create|get|advance(ver La CLI compense).