Journal OTC
Requisitos previos de acceso
- Permiso (módulo):
viewOtc - Licencia/Feature: Ninguna
- Contenedor del menú: GENERAL → grupo Diarios de transacciones → Journal OTC (
/otc-journal)
Qué es / cuándo usar
Es el desk de OTC (mostrador) del BackOffice: lista las órdenes de cambio cripto↔fiat (típicamente USDT ↔ BRL), con el bloqueo cambiario, el total cobrado al cliente, el total en el proveedor, el spread (ingreso de la casa) y el estado interno de cada orden. Además de consultar, el operador ejecuta acciones administrativas sobre una orden: forzar actualización de estado, cancelar, liberar reserva y — en modo interno — confirmar/liquidar la operación.
Las órdenes pueden operar en dos modos:
- ENOR — liquidación mediante proveedor externo (eNor). El ciclo de vida es dirigido por el scheduler de polling que consulta al proveedor.
- INTERNAL — liquidación in-house: la plataforma debita el token de pago del usuario, acredita la tesorería y paga al usuario en el token de destino a partir de la tesorería.
Requisitos previos
- Permiso:
viewOtc(permiso doble — enum CPM + módulo dinámico en DB). Las acciones de mutación (cancelar/confirmar/liberar) son sensibles y pueden requerir reautenticación (step-up contraseña+MFA) según la política del tenant; el gating final es validado en el API-Gateway. - Licencia/Feature: ninguna.
- Dependencias con otras pantallas: Ninguna. Las órdenes nacen del flujo de OTC (PaymentManagementService). Para que el modo INTERNAL liquide, debe existir la configuración
customerToPay(cuenta de tesorería).
Paso a paso
- Acceda a Diarios de transacciones → Journal OTC.
- Revise los cards de resumen en la parte superior (totales del período filtrado).
- Aplique filtros: período (de/hasta), lado (compra/venta), ID de usuario, rango de USDT y chips de estado (selección múltiple). Haga clic en Aplicar.
- Haga clic en Exportar CSV para descargar la lista filtrada.
- Haga clic en el ícono de detalle (
receipt_long) de una orden para abrir el modal con la línea de tiempo de eventos y las acciones administrativas.
Cards de resumen
| Card | Qué muestra |
|---|---|
| Total de órdenes | Cantidad de órdenes en el período. |
| Total USDT | Volumen total en USDT. |
| Total BRL (cliente) | Suma de lo cobrado a los clientes. |
| Total spread (BRL) | Ingreso de la casa en el período (delta entre el precio del cliente y el precio del proveedor). |
| Entregadas / Pendientes / Fallidas / Canceladas | Conteos por resultado. |
Filtros y columnas
| Filtro / Columna | Qué muestra / hace | Origen del dato |
|---|---|---|
| De / Hasta | Ventana de fechas. | from / to. |
Lado (side) | client_buys_usdt (BUY) o client_sells_usdt (SELL). | side. |
| ID de usuario | Filtra por usuario. | userId. |
| USDT mín/máx | Rango de volumen. | usdtMin / usdtMax. |
| Chips de estado | Selección múltiple: LOCKED, PIX_SCHEDULED, PIX_SENT, ONCHAIN_PENDING, ONCHAIN_CONFIRMED, PAYMENT_SENT, DELIVERED, CANCELLED, EXPIRED, FAILED. | status[]. |
Fecha (createdAt) | Creación de la orden. | createdAt. |
| Usuario | Propietario de la orden. | userId. |
Modo (otcMode) | ENOR o INTERNAL. | otcMode. |
| Lado | BUY/SELL. | side. |
| USDT | Cantidad negociada. | usdtAmount. |
Bloqueo cambiario (lockedRate) | Tipo de cambio congelado para el cliente. | lockedRate. |
| BRL cliente | Total cobrado al cliente. | brlTotal. |
| BRL proveedor (eNor) | Total en el proveedor. | enorBrlTotal (cae a brlTotal si está ausente). |
| Spread | Monto + porcentaje (bps/100). Vacío (—) cuando es cero. | spreadAmount / spreadBps. |
| Estado interno | Estado de la orden (color por clase). | internalStatus. |
| Detalle (acción) | Abre el modal de la orden. | openDetail(o). |
Acciones y modales
El modal de detalle (OtcOrderDetailModalComponent) muestra la orden, el pago, la liquidación y la línea de tiempo de eventos (cada evento expandible con JSON de request/response). Las acciones abren un diálogo de confirmación con campo de motivo (auditado) y un tono visual (azul/naranja/rojo):
| Acción | Cuándo aparece | Qué hace en el backend |
|---|---|---|
Forzar actualización (force-refresh) | Siempre que exista un enorOrderId. | Reconsulta el estado en el proveedor (eNor) y actualiza el estado local; si retorna client_delivered/cancelled, marca DELIVERED/CANCELLED. Útil cuando el polling se detuvo. Registra evento ADMIN_FORCE_REFRESH. |
Cancelar (cancel) | Estado en LOCKED, PIX_SCHEDULED, PIX_SENT, ONCHAIN_PENDING, ONCHAIN_CONFIRMED, PAYMENT_SENT. | Cancela la orden en el proveedor y libera la reserva del usuario (best-effort) si existe reserveReference. Marca CANCELLED. Evento ADMIN_CANCEL. |
Liberar reserva (release-reserve) | Cuando existe una reserva pendiente o el estado es CANCELLED/FAILED/EXPIRED. | Libera la reserva bloqueada del usuario (para órdenes con reserva "colgada"). Evento ADMIN_RELEASE_RESERVE. |
Confirmar negociación (confirm-trade) | Solo modo INTERNAL y estado LOCKED. | Liquida la orden in-house: (1) compromete la reserva del usuario hacia la tesorería (debita el token de pago, acredita customerToPay); (2) paga al usuario en el token de destino a partir de la tesorería; (3) marca DELIVERED. Evento ADMIN_CONFIRM_TRADE. |
Todas las acciones requieren confirmación y aceptan un motivo, que se graba como evento de auditoría en la orden.
Reglas de negocio / precauciones
Confirmar negociación (INTERNAL) es una liquidación real e irreversible
confirm-trademueve valor de verdad: debita al usuario, acredita la tesorería y paga al usuario en el token de destino. El backend rechaza la acción si la orden no esINTERNAL, no estáLOCKED, no tiene reserva o ya fue entregada — precisamente para evitar doble liquidación. Confirme el modo y el estado antes de actuar.- Las órdenes ENOR liquidan mediante el scheduler de polling, nunca por
confirm-trade. No intente liquidar una orden ENOR manualmente aquí.
Atención
- El spread es el ingreso de la casa: el delta entre el precio cobrado al cliente (
brlTotal) y el precio del proveedor (enorBrlTotal). Es exactamente el patrón de "precio límite × ejecución" — no confunda el spread con un error de cálculo. - Cancelar ya intenta liberar la reserva; Liberar reserva es la reparación aislada para reservas que quedaron colgadas tras
CANCELLED/FAILED/EXPIRED. EXPIREDsignifica que la ventana del bloqueo cambiario venció antes de la liquidación.
- Valores financieros:
usdtAmount,lockedRate,brlTotal,spreadAmountse tratan como BigNumber/strings de alta precisión en el backend — la UI los formatea solo para lectura. No concilie por el valor mostrado sin verificar la precisión. - Idempotencia: los pasos de liquidación interna se apoyan en referencias de reserva/transacción idempotentes; un retry que retorne
E00021("already processed") es éxito.
Ejemplos
Escenario 1 — Forzar actualización de una orden ENOR bloqueada en PIX_SENT
Una orden ENOR permaneció PIX_SENT durante horas porque el polling no avanzó. Abra el detalle → Forzar actualización. El backend reconsulta al proveedor: si ya existe client_delivered, la orden pasa a DELIVERED y la línea de tiempo gana el evento ADMIN_FORCE_REFRESH. Sin mover valor — solo sincroniza el estado.
Escenario 2 — Liquidar una orden INTERNAL (compra de USDT) confirmando la negociación
Un cliente compró USDT en modo INTERNAL; la orden está LOCKED con reserva creada. Abra el detalle → Confirmar negociación → ingrese el motivo. El backend: (1) compromete la reserva del usuario hacia la tesorería customerToPay (debita BRL/token de pago, acredita la tesorería); (2) paga el USDT al usuario a partir de la tesorería; (3) marca DELIVERED. La línea de tiempo registra ADMIN_CONFIRM_TRADE. Operación irreversible.
Escenario 3 — Cancelar y liberar una reserva colgada
Una orden FAILED dejó una reserva bloqueada en el saldo del usuario. Abra el detalle → Liberar reserva → ingrese el motivo. El backend libera la reserva (best-effort) y graba ADMIN_RELEASE_RESERVE. Si la orden todavía estuviera activa, el camino sería Cancelar, que ya libera la reserva en el mismo paso.