Ir al contenido

Fechas y dinero

Dos formatos que aparecen en casi todo recurso, y que se equivocan fácil cuando el integrador supone en vez de verificar.

La fecha con hora sale en ISO 8601, siempre en UTC, con la Z al final:

2026-09-16T14:30:00Z

Un campo que guarda solo fecha, sin hora, sale así:

2026-09-16

El mismo formato vale en los dos sentidos: lo que usted envía en una fecha sigue la misma regla que lo que recibe, incluso en los filtros de Paginación.

Todo campo de fecha y hora sale así: created_at y updated_at de cualquier recurso, key.expires_at en GET /public/v1/me, next_follow_up y last_interaction_at en el cliente, due_date, started_at y completed_at en la tarea, starts_at y ends_at en la cita de la agenda, scheduled_start, scheduled_end, started_at y completed_at en la orden de servicio, valid_until, estimated_start_date, estimated_completion_date, approved_at y rejected_at en el presupuesto, y estimated_delivery_date y canceled_at en la venta (y canceled_at y created_at en cada cobro de ella). El movimiento del historial de stock solo tiene created_at, porque nunca cambia después de grabado. El campo de solo fecha usa YYYY-MM-DD, como el birth_date del cliente y el paid_at y el due_date del plan de pago de la conversión de presupuesto en venta, y en la venta el sale_date, el due_date y el paid_date de cada cuota, el paid_at de cada cobro y las fechas de los filtros sale_date_after y sale_date_before. En finanzas, transaction_date, due_date y payment_date del movimiento son solo fecha, en los dos sentidos, como los filtros due_after y due_before.

UTC en la API, huso de la empresa en la pantalla

Sección titulada «UTC en la API, huso de la empresa en la pantalla»

La API no devuelve hora local. Devuelve UTC y dice cuál es el huso de la empresa, en organization.timezone en la respuesta de GET /public/v1/me:

{
"organization": {
"timezone": "America/Sao_Paulo",
"currency": "BRL"
}
}

El panel muestra las mismas fechas convertidas a ese huso. Así que el mismo registro aparece como 2026-09-16T14:30:00Z en la API y como 16/09/2026 11:30 en la pantalla, y los dos están bien.

Si su sistema muestra una fecha a una persona, convierta de UTC a organization.timezone al mostrarla, y nunca guarde la fecha ya convertida: guardar en UTC es lo que mantiene correcta la cuenta de horario de verano y de cambio de huso después.

El dinero es entero, en centavos, y el nombre del campo lo dice con todas las letras:

{ "total_cents": 150000 }

150000 son BRL 1.500,00. El nombre siempre termina en _cents, así que no existe campo de dinero en el que tenga que adivinar la unidad.

La moneda viene en organization.currency, en la respuesta de GET /public/v1/me. La API no convierte moneda: el entero está en la moneda que ese campo indica.

El primer campo de dinero disponible es el price_cents de la cita de la agenda: price_cents: 15000 son BRL 150,00, y null quiere decir cita sin importe. La orden de servicio también trae dinero en centavos: subtotal_cents, discount_cents, additional_costs_cents y total_cents, más unit_price_cents y total_cents en cada ítem. El presupuesto también: subtotal_cents, discount_cents, additional_costs_cents, total_cents y recurring_total_cents (la mensualidad, que nunca suma con el total), más unit_price_cents y total_cents en cada ítem, y entry_amount_cents y amount_cents en el plan de pago de la conversión en venta. La venta trae subtotal_cents, discount_cents, shipping_cents, additional_costs_cents, total_cents y paid_cents, más unit_price_cents, original_price_cents y total_price_cents en cada ítem, amount_cents, paid_cents y remaining_cents en cada cuota y amount_cents en cada cobro; el cobro y la reprogramación también reciben amount_cents. El producto trae price_cents y promotional_price_cents (y el mismo par en cada variación), y el servicio trae base_price_cents. La alícuota de ISS del servicio (iss_rate) es fracción y sale como número: 0.05 quiere decir 5%. El porcentaje de descuento (discount_percentage) no es dinero y sale como número: 10 quiere decir 10%. El movimiento de finanzas trae amount_cents y, en las cuotas, original_amount_cents; las cuotas reciben total_amount_cents o installment_amount_cents. La cuenta trae opening_balance_cents, current_balance_cents y, en la tarjeta, credit_limit_cents, credit_used_cents y credit_available_cents; el alert_percentage de la cuenta es porcentaje y sale como número. Clientes, direcciones, leads, tareas y equipo no tienen campo de dinero. La misma regla vale para los recursos que llegan en las próximas fases.

Un número decimal con punto flotante pierde centavos. No es teoría: en cualquier lenguaje que use punto flotante binario, 0.1 + 0.2 no da 0.3, y una suma de mil ítems acumula la diferencia hasta que el total cierra mal.

Con entero eso no pasa. Sumar, restar y multiplicar por cantidad son cuentas exactas. La división sigue pidiendo cuidado, porque ahí entra el redondeo, y ahí usted decide la regla en vez de descubrir el resultado después.

Los ejemplos imprimen el valor formateado y vuelven de texto a centavos con aritmética entera.

Ventana de terminal
CENTS=150000
# cURL solo transporta la respuesta. La cuenta es del shell, con enteros.
printf 'BRL %d,%02d\n' "$((CENTS / 100))" "$((CENTS % 100))"
# De vuelta, de "1500,00" a centavos, sin pasar por decimal.
VALOR="1500,00"
ENTERA=${VALOR%%,*}
FRACCION=${VALOR##*,}
printf '%d\n' "$((ENTERA * 100 + 10#$FRACCION))"

Ejecute con bash. El 10# obliga a la lectura en base 10, si no 08 y 09 se vuelven un error de octal. El separador de miles queda afuera: el agrupamiento por idioma es trabajo del lenguaje que arma la pantalla, no del shell.

  • Paginación: los filtros de fecha de las listas usan este mismo formato.
  • Versiones y changelog: por qué un campo nuevo en una respuesta no rompe su integración.