Motor de dinero y trazabilidad
Cómo opera el motor único de comisiones y margen en Go, qué garantías de trazabilidad existen (email_log, audit_events, timeline) y cómo verificarlas desde ops.
Motor de dinero y trazabilidad
Desde julio 2026 la plataforma tiene un solo ejecutor de dinero (el motor Go del backbone) y tres capas de trazabilidad que cubren cada acción que toca dinero o configuración. Esta guía es para operación: qué hace cada pieza, dónde mirar y cómo verificar que está sana.
Vista general
Un solo motor de dinero
El motor de comisiones en Go (backbone/internal/commission/, endpoint GET /commission/calculate en ingest.relo.mx) es el único ejecutor de la cadena de tasas y caps. Todos los consumidores del worker (planner, portal de partner, generación de pagos, AI assistant) pasan por el selector de engine.
Cadena de tasas, en orden de prioridad por fila:
- Tasa congelada — si ya existe un
partner_paymentspara ese periodo, gana siempre (inmutable). rate_overridepor tiers —match_rulesconrule_data.tiers[], retroactivo a la primera unidad del mes.rate_overrideplano — el específico por CID gana sobre el partner-wide; campaign-scoped gana sobre client-level.- Override de segmento —
partner_segment_commissionscon ventanaseffective_from/to. - Sin tasa — la fila aporta unidades y revenue pero cero comisión (no hay default de cliente en el lado del costo).
Semántica de caps: NULL, 0 y >0 son cosas distintas
Los caps de presupuesto son asimétricos entre partner y cliente — es el footgun más caro de la plataforma:
| Tabla | NULL | 0 | >0 |
|---|---|---|---|
partner_segment_budgets.budget_amount (costo) | Ilimitado (no hay cap, auto-pause no tiene dónde disparar) | No paga nada (kill switch) | Cap normal: payable = min(bruto, cap) |
client_relo_budgets.budget_amount (ingreso) | Ingreso ilimitado | Ingreso ilimitado | Cap normal |
Además: carve-outs por CID (budget_override con cid_token) capean independiente; la fórmula final es Σ(carve_capped) + min(restante_bruto, cap_segmento) + locked_commission. Las ventanas budget_start_from / paused_at excluyen filas completas.
Estado del soak de paridad
- El selector
COMMISSION_ENGINE(worker var) está engodesde 2026-05-03; cada call site conserva un override por llamada{ engine: 'parity' }para rollback. - Soak hasta el 2026-08-06: el parity sweep corre cada 6h comparando Go vs SQL RPC (tolerancia $0.01). Drift →
audit_eventsconactor = 'commission_parity'(cero hasta ahora). - Después del soak: se quitan los overrides, se congela el RPC como histórico read-only y se retira el sweep.
Pagos congelados son inmutables
Las tasas se congelan en partner_payments al generar el cierre y nunca se recalculan en su lugar. Al aprobar (pending → approved) se re-congela desde el engine en vivo salvo que exista adjusted_commission; si el engine falla, el pago conserva sus datos congelados. Para recalcular un cierre: borrar el pago y regenerar (scripts/regenerate-payments.js --via=go).
/margin/summary: una sola respuesta de margen
GET /margin/summary en el backbone devuelve por cliente/segmento/periodo:
revenue,income_gross,income_cap,income_cappedcost_gross,cost_capped,cost_unlimitedmargin,margin_pct,payout_ratio(= costo capped / income cap; ~10% es sano)- Flag de riesgo
capped_income_unlimited_cost: el cuadrante peligroso — ingreso capeado con costo ilimitado, donde cada venta extra quema margen. La protección ahí es el auto-pause + cascada (pausa a todos los partners cuando el cap del cliente se agota).
Los datos de margen van detrás de requireSensitiveAccess: los partners nunca los ven.
Trazabilidad: las tres capas
1. email_log — cada correo saliente
Todo email que manda la plataforma queda registrado con HTML completo, template, destinatarios, mailgun_id y status (sent/failed). Lo escribe sendEmailAndLog; si un flujo manda correo, hay fila.
2. Trigger de auditoría — cada mutación de dinero/config
Un trigger de base de datos escribe una fila en audit_events (actor = 'db_trigger', con before/after en JSONB) por cada INSERT/UPDATE/DELETE en las 9 tablas sensibles:
partner_segment_budgets, partner_segment_commissions, client_relo_budgets, match_rules, partner_clients, partners, campaigns, client_segments, client_segment_rules.
Cubre por construcción todo lo que toque esas tablas: handlers del worker, MCP write tools, consola SQL, onboard_client().
3. Timeline unificado
Un solo feed de auditoría por entidad que mezcla mutaciones, paridad de comisiones, pausas de presupuesto y correos. Lo renderiza TimelineFeed.tsx en el perfil de partner y de cliente. Los partners solo ven filas marcadas como partner-visible.
Guard de cobertura en generación de pagos
Antes de generar un cierre, handleGeneratePayments compara el revenue del espejo gated contra el gross del periodo. Si la cobertura es menor al 10% (firma de un ledger de la marca que no llegó), la respuesta incluye un coverage_warning — avisa pero nunca bloquea (los flujos de advance son legítimos). Si ves montos de cierre sospechosamente bajos, este warning es lo primero a revisar.
Quickrefs de ops
Consultar el email log (admin)
# Lista (sin html_body), filtros opcionales partner_id / client_id / q / limit / offset
curl -H "Authorization: Bearer $TOKEN" \
"https://relo-api.quomx.workers.dev/api/email-log?client_id=3&limit=50"
# Detalle con HTML completo
curl -H "Authorization: Bearer $TOKEN" \
"https://relo-api.quomx.workers.dev/api/email-log/<id>"
Como partner (solo sus propios correos): GET /api/partners/:id/email-log y GET /api/partners/:id/email-log/:emailId.
Consultar el timeline
curl -H "Authorization: Bearer $TOKEN" \
"https://relo-api.quomx.workers.dev/api/partners/<id>/timeline?limit=50&from=2026-07-01"
curl -H "Authorization: Bearer $TOKEN" \
"https://relo-api.quomx.workers.dev/api/clients/<id>/timeline?types=mutation,email"
Parámetros: limit, offset, types, from, to.
Reconstruir agregados
Los agregados ClickHouse→Supabase son cachés reconstruibles. Si algo se ve inconsistente (drift de dashboard, hueco de backfill), se regeneran con un solo path:
curl -X POST "https://ingest.relo.mx/sync/rebuild" \
-H "Content-Type: application/json" \
-d '{"client_id": 3, "from": "2026-07-01", "to": "2026-07-31"}'
Dónde cae el drift
Todo drift o evento automático aterriza en audit_events. Actores a filtrar:
| Actor | Qué reporta |
|---|---|
commission_parity | Drift Go vs SQL RPC en el parity sweep (objetivo: cero hasta 2026-08-06) |
margin_summary | Eventos del endpoint de margen |
db_trigger | Mutaciones en las 9 tablas sensibles (before/after JSONB) |
budget_parity_drift.* | Drift del modo BUDGET_ENGINE=parity |
pause_partner_segment | Auto-pause / cascada de presupuestos |
Verificación rápida de salud
- Paridad limpia:
audit_eventssin filas nuevas decommission_parity. - Margen coherente:
/margin/summaryde un mes cerrado cuadra con GlobalFinancials del dashboard. - Sin cuadrante peligroso: revisar
capped_income_unlimited_costpor segmento; si aparece, confirmar que el auto-pause/cascada está armado. - Cierres con cobertura: ningún
coverage_warningen la generación del mes. - Emails trazados: un correo reciente conocido (p.ej. aprobación de pago) aparece en
/api/email-log.