Sección 02
El problema: eventos operativos ≠ preguntas de negocio
Betterez no nos entrega "ventas por agencia": nos entrega un flujo de eventos sueltos. Modelar es tender el puente entre ese flujo y las preguntas que hace la dirección.
Lo que llega (operativo)
Un webhook por cada hecho del mundo real: se vendió un pasaje, se canceló un boleto, se cerró un manifiesto. Cada evento es un JSON crudo, con su forma y su momento, optimizado para registrar transacciones, no para sumarlas.
Lo que se pregunta (negocio)
"¿Cuánto vendimos en mayo por agencia y moneda?", "¿qué ocupación tuvo el servicio Mendoza–Buenos Aires?", "¿cuántos boletos se cancelaron parcialmente?". Preguntas agregadas, comparables y estables en el tiempo.
Tres tensiones que el modelo resuelve
Fidelidad vs. usabilidad
La copia OLTP es fiel pero cruda. La capa BI es usable pero derivada. Necesitamos las dos, no una.
Estado vivo vs. histórico
Un boleto cambia de estado; un reporte de mayo no debería cambiar en julio. El modelo fija fotos consistentes.
Detalle vs. agregado
Auditar exige el detalle; decidir exige el agregado. El grano de cada tabla define a quién sirve.
Sección 03
Las cuatro capas, como decisiones de modelado
Cada capa es una representación distinta del mismo hecho. No son copias redundantes: cada una existe porque responde una pregunta que las otras no pueden.
raw_eventspublicbi_rebuild| Capa | Pregunta que responde | Forma del dato | Quién la usa |
|---|---|---|---|
| Raw | ¿Qué nos dijo Betterez, exactamente? | JSON inmutable, tal cual llegó | Ingeniería, auditoría, reproceso |
OLTP public | ¿Cuál es el estado actual de cada boleto? | Tablas normalizadas, copia fiel | Reconciliación, backend, ETL |
BI bi_rebuild | ¿Cuánto, por agencia, ruta, estado? | Modelo estrella: hechos + dimensiones | MicroStrategy, finanzas, gerencia |
| MSTR | ¿Cómo lo veo y lo decido? | Métricas, atributos, filtros | Negocio, dirección |
public (entidades operativas) y bi_rebuild (modelo dimensional). El raw-first y el flujo de ingesta se detallan en De un evento a un KPI y en Fiabilidad de captura.Sección 04
Capa OLTP: las entidades del negocio
El schema public es la copia fiel de lo que Betterez nos entrega, normalizada en tablas. Cada una tiene un propósito claro y una clave de negocio.
| Tabla | Representa | Clave de negocio |
|---|---|---|
raw_events | Cada evento crudo recibido (auditoría + idempotencia) | idempotency_key (hash SHA-256) |
sales | Entrada contable: venta, reembolso o ítem | transaction_id |
tickets | Cada boleto dentro de una transacción | ticket_number |
payments | Medios de pago de una transacción | sale_id + tipo |
refund_details | Detalle de cada reembolso | refund_id |
sold_item_details | Ítems adicionales (seguros, impuestos) | sold_item_number |
manifest_entries | Cada pasajero en un manifiesto | ticket_number + manifest_id |
manifest_legs | Cada parada de un servicio (carga) | manifest_id + leg_ord |
Tres claves de cruce
Todo el sistema se entreteje con transaction_id, ticket_number y manifest_id. Memorizarlas es entender el modelo.
Normalización fiel
No "mejoramos" el dato acá: lo guardamos como vino. Las reglas de negocio viven aguas abajo, en BI.
Trazabilidad
Cada fila apunta a su raw_event_id: de cualquier número se puede volver al JSON original.
Sección 05
Cómo se relacionan las entidades
Una sola transacción explota en varias filas relacionadas. Entender estas cardinalidades evita los dos errores clásicos: contar de menos y contar de más.
Una transacción es muchas filas
La venta principal vive en sales (sale_type='sale'). Sus boletos van a tickets (1:N — ida y vuelta son dos). Sus pagos a payments.
Refunds e ítems son filas nuevas
Un reembolso no edita la venta: crea una fila R- en sales con montos negativos. Un seguro crea una fila PI-. Esto preserva la historia, pero obliga a filtrar bien al sumar.
Sección 06
Decisiones de diseño en la capa operativa
Cuatro elecciones que parecen técnicas pero son de negocio: determinan qué se puede auditar y qué se puede confiar.
1. Raw-first, siempre
Antes de extraer cualquier tabla, el JSON completo se guarda en Google Cloud Storage y en raw_events. Si mañana cambia una regla, reprocesamos desde el original: el dato histórico nunca se pierde.
2. Idempotencia por hash
Mismo payload → mismo idempotency_key (SHA-256). Los reintentos de Betterez se deduplican solos: un evento nunca se cuenta dos veces.
3. Refunds como filas negativas
Cada reembolso es una fila R- aparte (is_refund=true, montos negativos, original_transaction_id apunta a la venta). La venta original queda intacta y el neto se obtiene sumando.
4. Ítems vendidos como filas PI-
Seguros, menores e impuestos viven como filas PI- en sales, con su detalle en sold_item_details y un puente a su boleto. Permite medirlos sin contaminar el conteo de pasajes.
public.sales mezcla ventas, reembolsos e ítems. Sumar total a ciegas da un número sin sentido. Por eso la capa BI separa estos conceptos en hechos distintos — la próxima sección.
Sección 07
Del OLTP al modelo estrella
El schema bi_rebuild reorganiza las entidades operativas en dos tipos de tabla: hechos (lo que se mide) y dimensiones (cómo se corta lo medido).
Hechos — los verbos
Filas numéricas, una por evento de negocio. Cada fila tiene medidas (montos, conteos) y claves hacia las dimensiones. Crecen sin parar: un hecho por boleto, por venta, por parada.
Dimensiones — los sustantivos
Catálogos estables que dan contexto: agencias, rutas, fechas, servicios. Responden el "por": por agencia, por ruta, por mes. Pocas filas, nombres canónicos.
Un hecho en el centro, rodeado de las dimensiones que lo describen: la forma de "estrella" que da nombre al patrón.
Sección 08
Los hechos de bi_rebuild y su grano
El grano es la regla más importante de un hecho: define qué representa exactamente una fila. Confundirlo es la causa raíz de casi todo número mal sumado.
fact_ticket
El hecho central de pasajes. Una fila por boleto no borrado. Trae estado (is_paid, is_cancelled, cancelación parcial), montos (ticket_amount, ticket_taxes), ruta, currency, manifest_id y flags de alcance (has_nacional…).
fact_ticket_mstr
Mismo grano que fact_ticket pero "ancho" para MicroStrategy: 13 etiquetas has_*, tarifa/segmento de pasajero y ancillaries (seguro, menor) en columnas, sin tablas puente.
fact_sale
Una fila por transacción de sales. Medidas de cabecera: total_amount, num_tickets, pax_qty, gross_sale, net_sale, refounds_amount. Útil para ranking por agencia y neto comercial.
fact_sold_item
Una fila por ítem PI-. Clasifica seguro (VIAJE PROTEGIDO), menor (BOLETO SEGURO%), impuesto u otro. Alimenta v_insurance_kpi / v_minor_kpi.
fact_manifest_leg
Una fila por (manifest_id, leg_ord). Mide ocupación por tramo: pax_on, pax_off, load, load_factor. Para ocupación agregada se usa dim_manifest.
La regla de oro
Antes de sumar, preguntá: ¿qué es una fila acá? Pasajes → fact_ticket. Neto comercial → fact_sale. Seguros → fact_sold_item. Ocupación → fact_manifest_leg. Nunca mezclar granos en una misma medida.
Sección 09
Las dimensiones: el contexto estable
Las dimensiones son los catálogos por los que se cortan los hechos. Su valor está en ser canónicas: un solo nombre por agencia, una sola definición de ruta.
| Dimensión | Da contexto de | Detalle de diseño |
|---|---|---|
dim_date | Tiempo | Año, mes, semana, trimestre, día de semana |
dim_agency | Punto de venta | ~297 agencias con agency_label canónico (catálogo Betterez) |
dim_route | Origen → destino | route_id = origin_destination, distancia mediana |
dim_schedule / dim_schedule_leg | Itinerario / servicio | Distancia en km por servicio y por tramo |
dim_manifest | Servicio operado | Ocupación pico, capacidad inferida, recursos (chofer, vehículo) |
dim_channel | Canal de venta | web / callcenter / digital / physical |
dim_manifest_tag + bridge_manifest_tag | Etiquetas de servicio | 13 tags (nacional, internacional…) en relación N:M |
dim_sold_item_product | Tipo de ítem | Categoría: seguro / menor / impuesto / otro |
dim_agency el ranking se parte en dos. La dimensión absorbe el cambio y los tableros siguen sumando bien. Un job mensual la mantiene al día.
Sección 10
Granularidad y el riesgo de doble conteo
El error más caro en BI no es un dato faltante: es un dato contado dos veces. El modelo lo previene por diseño, pero hay que saber leerlo.
El riesgo
En public.sales conviven la venta padre, sus reembolsos R- y sus ítems PI-. Si un reporte suma todo junto, mezcla pasajes con seguros y resta reembolsos donde no debe.
La defensa: granos separados
BI parte cada concepto en su hecho. fact_ticket cuenta boletos; fact_sold_item cuenta ítems; fact_sale resume la transacción. Cada medida vive en su grano.
La vista de seguridad
v_fact_sale_parent_only expone solo las ventas padre, sin filas PI-% ni R-%. Es el atajo correcto cuando se quiere el total comercial sin doble conteo.
Tres preguntas, tres granos correctos
| Pregunta | Hecho correcto | Por qué |
|---|---|---|
| ¿Cuántos pasajes pagados vendimos? | fact_ticket · is_paid | El grano es el boleto, no la venta |
| ¿Cuál fue el neto comercial por agencia? | fact_sale / v_fact_sale_parent_only | Resumen por transacción, sin ítems |
| ¿Cuántos seguros se vendieron? | fact_sold_item · is_insurance | El grano es el ítem; PI- solo no alcanza |
Sección 11
Decisiones de diseño en la capa BI
Convenciones que conviene conocer para leer cualquier número de bi_rebuild sin sorpresas.
Singular, no plural
La capa activa usa nombres en singular: fact_ticket, dim_agency. El viejo schema bi (plural: fact_tickets) está congelado y no se usa. Si ves plural, es código viejo.
sale_date en hora Argentina
OLTP guarda sale_date en UTC. BI lo materializa como fecha ART (America/Argentina/Buenos_Aires). Por eso un corte mensual en BI coincide con el calendario operativo, no con UTC.
Multi-moneda sin conversión
Los montos conviven en ARS, CLP, BOB y USD con currency explícita. No se convierten entre sí. Para sumar coherente, siempre filtrar por una moneda.
Match ticket↔manifiesto por ticket_number
El vínculo oficial boleto–servicio es por ticket_number, con fallback por schedule + travel_date. schedule_id es atributo de servicio, nunca clave de unión de tickets.
Sección 12 · Cierre
El modelo, en una frase
Guardamos el evento crudo, lo normalizamos fiel en OLTP y lo reorganizamos en hechos y dimensiones con grano explícito: así una pregunta de negocio tiene una sola respuesta confiable.
Fidelidad
Raw-first y OLTP preservan el original, auditable y reprocesable.
Estructura
Hechos y dimensiones con grano claro, sin doble conteo.
Significado
Convenciones compartidas: un número, una definición.
Andesmar API · Modelo de datos · public + bi_rebuild · Junio 2026