Andesmar BI · Documentación interna

Diccionario de datos para Strategy

De dónde sale cada atributo y cada métrica de la capa bi_rebuild: qué significa, qué valores trae, en qué se equivoca la gente y cómo se calcula la carga media de un servicio.

Septiembre 2026 Schema: bi_rebuild Columnas verificadas contra la base
ⓘ
Para quién: quien arma informes o administra las carpetas de Strategy sobre la capa
bi_rebuild (Planificación, Comercial, Contaduría, administración de MSTR).
Qué resuelve: para cada atributo y métrica que se puede llevar a Strategy dice
qué es, de dónde sale, qué valores trae y dónde se equivoca la gente.
Versión: 2026-09-29. Columnas verificadas contra el catálogo real de la base
(information_schema.columns + COMMENT ON COLUMN) ese mismo día; valores de ejemplo
medidos sobre datos de producción.
Tarea: ClickUp 86e3geh0w (pedido de Mariela Ramos: ordenar y renombrar carpetas).

Este documento no reemplaza a las guías temáticas: las resume en un solo lugar y enlaza a cada una para el detalle. Si algo de acá contradice a una guía, la sección Inconsistencias encontradas dice cuál está bien.

0

Cómo usarlo

Cada sección es un tema de negocio y tiene una tabla por objeto de la base. Las columnas de esas tablas son siempre las mismas:

Columna del diccionarioQué dice
ColumnaNombre técnico exacto, tabla.columna (la tabla va en el título de la sección). Es lo que se mapea en Architect.
Nombre en StrategyNombre sugerido en castellano para el atributo o la métrica. Es una propuesta; lo aplica quien administra MSTR.
Qué significaEn lenguaje de negocio.
De dónde saleCadena de origen: dato de Betterez → tabla public.* (copia fiel de Betterez) → paso del ETL incremental que lo escribe en bi_rebuild (el número de paso es el encabezado -- N) de scripts/sql/bi_rebuild/operativo/bi_rebuild_incremental.sql).
Tipo / valoresTipo en Postgres y valores posibles. smallint 1/0 = bandera (1 sí, 0 no).
Cuidado conLa trampa conocida. Vacío = no hay trampa conocida.

Rol en Strategy: cuando dice atributo va a una carpeta de atributos; métrica se arma con una agregación (Sum, Count, Max) sobre esa columna; clave es un ID de join que no se muestra al usuario; técnica no se publica en tableros.

Refresco: bi_rebuild se recalcula dos veces por día (Cloud Run Job bi-rebuild-incremental, 00:00 y 12:00 ART). Los cubos de Strategy deben refrescarse después de esas corridas.

1

Reglas de oro

Si se respetan estas diez, el 90% de los números "que no cierran" desaparece.

  1. La plata es fact_sale.net_sale. Nunca sumar total_amount. public.sales mezcla transacciones de Betterez con filas sintéticas R-<boleto> (devolución) y PI-<ítem> (seguro, percepción) que desglosan plata ya incluida en otra transacción. net_sale las pone en 0 cuando tienen contenedor; Sum(total_amount) las cuenta dos veces. Sum(net_sale) = pagos con signo (ADR 2026-09-29).
  2. Boletos se cuentan en fact_ticket_mstr (o fact_ticket) con Count(ticket_id), nunca contando ventas. Una venta trae 1,59 boletos en promedio: contar ventas subestima ~40%.
  3. Importes por medio de pago van en fact_payment / fact_sale, nunca repartidos por boleto. fact_ticket_mstr.primary_payment_type es sólo un atributo (con qué se pagó), no un monto.
  4. No se suman monedas distintas. Toda métrica de dinero lleva filtro de currency (ARS, CLP, BOB, USD). No hay conversión de tipo de cambio en bi_rebuild.
  5. Banderas is_* / has_* son smallint 1/0, no verdadero/falso. Filtrar con = 1 / = 0. Única excepción de texto: viaja_con_menor_3 vale 'Si' / 'No'.
  6. sale_date ya está en hora argentina (ART). Una venta a las 22:00 del 30/09 es del 30/09, no del 01/10. No recalcular desde UTC.
  7. Boleto pagado = is_paid = 1 (neto de cancelaciones y cambios). Para cuadrar contra el reporte web de Betterez por agencia, is_paid_including_partial_cancel = 1.
  8. dim_manifest.origin / destination describen el RECORRIDO del bus (primera y última parada), no dónde subió el pasajero. Para el tramo del pasajero: fact_ticket.origin / destination.
  9. Tres "alcances" distintos que no se unifican: alcance_ruta (etiqueta del manifiesto), alcance_tramo (geografía del viaje del pasajero) y tipo_liquidacion (N/P/I con que liquidó SAP). Para cuadrar con SAP, tipo_liquidacion; para analizar viajes, alcance_tramo.
  10. Itinerario = schedule_id, no el texto. Hay varios schedule_id con el mismo nombre. El km del servicio se agrega con Max(schedule_distance_km), nunca Sum por boleto.

Reglas complementarias: PI- no significa seguro (seguro = is_insurance = 1); el schema bi (plural: fact_tickets, fact_sales) es legacy y no se usa; los tableros leen bi_rebuild, nunca public.

2

Mapa: qué tabla para qué pregunta

PreguntaTablaGrano (1 fila =)Métrica base
¿Cuánta plata entró? (venta neta)fact_sale1 fila de public.sales (transacción o desglose)Sum(net_sale) + filtro currency
¿Cuánto se vendió en bruto (sin devoluciones)?v_fact_sale_parent_only1 venta padreSum(gross_sale)
¿Cuántos boletos?fact_ticket_mstr / fact_ticket1 boletoCount(ticket_id) con is_paid = 1
¿Con qué medio se pagó y cuánto?fact_payment (+ fact_sale)1 pagoSum(amount) con signo (ver §4)
¿Cuántos boletos por medio de pago?fact_ticket_mstr1 boletoCount(ticket_id) por primary_payment_type
¿Seguros y menores de 3 vendidos?fact_sold_item1 ítemCount(sold_item_id), Sum(item_amount)
¿Qué tan lleno fue el bus?dim_manifest1 manifiesto (servicio en un día)peak_load, peak_load_factor
¿Cuántos suben / bajan en cada parada? ¿Carga media?fact_manifest_leg1 tramo entre dos paradas de un manifiestoSum(pax_on), carga media (§12)
¿Qué agencia?dim_agency1 agenciaatributo
¿Qué servicio / recorrido?dim_schedule, dim_route1 itinerario / 1 rutaatributo

Diagrama de relaciones (claves de join):

dim_date.date_id ─────────── fact_*.sale_date  (y manifest_date en fact_manifest_leg / dim_manifest)
dim_mes_anio.id_mes_anio ─── dim_date.id_mes_anio ─── fact_ticket.mes_anio
dim_agency.agency_id ─────── fact_sale / fact_ticket / fact_payment / fact_sold_item .agency_id
dim_channel.channel ──────── fact_*.channel
fact_sale.sale_id ────────── fact_ticket.sale_id, fact_payment.sale_id, fact_sold_item.sale_id
fact_ticket.ticket_id ────── fact_ticket_mstr.ticket_id (1:1), fact_sold_item.ticket_id
dim_manifest.manifest_id ─── fact_ticket(.mstr).manifest_id, fact_manifest_leg.manifest_id, bridge_manifest_tag.manifest_id
dim_schedule.schedule_id ─── fact_ticket(.mstr).schedule_id, dim_manifest.schedule_id, fact_manifest_leg.schedule_id, dim_schedule_leg.schedule_id
dim_route.route_id ───────── fact_ticket(.mstr).route_id, dim_schedule.route_id, dim_manifest.route_id
dim_payment_method.payment_type ── fact_payment.payment_type, fact_sale.primary_payment_type, fact_ticket_mstr.primary_payment_type
dim_sold_item_product.product_name ── fact_sold_item.product_name
dim_manifest_tag.tag_name_norm ── bridge_manifest_tag.tag_name_norm
3

Ventas y plata

3.1 bi_rebuild.fact_sale — 31 columnas

Grano: 1 fila de public.sales. Incluye transacciones de Betterez y las filas sintéticas R- y PI-. ETL: paso 8 (y 8c para las columnas de pago). Guías: GUIA_TABLERO_AGENCIAS §5.3, GUIA_FORMA_DE_PAGO, ADR docs/decisions/2026-09-29_devoluciones_contenedor_y_grano_transaccion.md.

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
sale_idVenta (ID)Identificador interno de la fila de ventapublic.sales.id → paso 8uuidClave de join; mapear como texto en MSTR. No es el código que ve Contaduría (ese es transaction_id, en fact_ticket)
net_saleVenta netaPlata de la transacción con signo: una cancelación resta, un cambio suma su netosales.total con regla de grano → paso 8numericLa métrica de plata oficial. Vale 0 en filas R- con source_transaction_id y PI- con venta madre (su plata ya está en otra fila). Filtrar currency
gross_saleVenta brutaTotal de la fila si es positivo; 0 si es negativo o es un desglose con contenedorsales.total → paso 8numericNo resta devoluciones. Para "venta bruta" de tablero usar v_fact_sale_parent_only
total_amountTotal informado (técnico)Total crudo de la fila tal como viene de Betterezsales.total → paso 8numericNunca sumar para plata: duplica devoluciones y seguros. Sólo auditoría
cancellation_amountImporte canceladoTotal de la fila cuando status = cancelledsales.total + sales.status → paso 8numericInformativo; ya está dentro de net_sale
refounds_amountImporte devueltoABS(total) de las filas refunded (siempre positivo)sales.total → paso 8numericInformativo. No restarlo de net_sale (ya está descontado). Nombre con typo histórico ("refound")
is_refundEs devolución1 si la fila es un reembolsosales.is_refund → paso 8smallint 1/0—
is_refound(no publicar)Copia exacta de is_refundsales.is_refund → paso 8smallint 1/0Duplicado legacy con typo; iguales en el 100% de las filas. Usar is_refund
sale_typeTipo de transacciónsale = venta; refund = fila R-; cancellation = transacción que sólo devuelvesales.sale_type → paso 8text: sale, refund, cancellationSirve para abrir la venta neta por tipo
source_transaction_idTransacción contenedoraEn filas R-: la transacción de Betterez que contiene esa devoluciónsales.source_transaction_id (payload crudo) → paso 8textSi no es NULL, net_sale = 0 en esa fila
statusEstado de la ventaEstado de la transacción en Betterezsales.status → paso 8text: confirmed, refunded, cancelled, pendingNo confundir con el estado del boleto
channelCanal de ventaCanal del boleto de la ventatickets.channel → paso 8text: agency-backoffice, websales, callcenter, unknownunknown (~27%) son filas sin boleto propio (R-, PI-, cancelaciones), no un error
sale_dateFecha de ventaDía de la venta en hora argentinasales.sale_date (timestamptz) → AT TIME ZONE 'America/Argentina/Buenos_Aires' → paso 8dateYa en ART (regla 6)
sale_hourHora de ventaHora 0–23 ARTideminteger—
fecha_liquidacionFecha de liquidaciónFecha contable de la transacción: la primera liquidación SAP que la incluyeLa menor entre sus boletos, sus devoluciones (R-), las devoluciones que contiene (cancelación o cambio) y sus ítems (PI-) → paso 11bdateCasi siempre sale_date + 1. NULL = no liquidada por SAP (call center, Chile/Bolivia/Perú, agencias excluidas, antes de junio 2026): ~6% del net_sale de sep-2026
num_ticketsBoletos en la ventaCantidad de boletos no borrados de la ventaCOUNT(tickets) → paso 8integerPara contar boletos usar fact_ticket_mstr (regla 2)
pax_qtyPasajeros en la ventaCantidad de pasajeros según Betterezsales.pax_qty → paso 8integer—
currencyMonedaISO 4217 de la ventasales.currency (default ARS) → paso 8varchar(3): ARS, CLP, BOB, USDRegla 4
agency_idAgencia (ID)Agencia resuelta de la ventasales.agency_id; si falta y es devolución, la de la transacción original → paso 8text; sin_agency_id = sin agenciaJoin a dim_agency. sin_agency_id se muestra como Backoffice
agency_codeCódigo de agenciaCódigo de negocio (ej. 128, M42, PTF)dim_agency.agency_code → paso 8textUn código puede tener varios agency_id
agency_labelAgencia"{código} - {nombre}" (ej. 128 - REALICO)dim_agency.agency_label → paso 8textUsar esta y no public.sales.agency_name
agency_countryPaís de la agenciaPaís del punto de ventaCatálogo Betterez si es ≠ AR; si no, sales.shift_location_country → paso 8text: AR, CL, BO, PE, NULLBetterez informa AR en agencias chilenas; el catálogo gana (29/09)
agency_id_raw(técnica)agency_id tal como vinosales.agency_id → paso 8textAuditoría
agency_id_resolved(técnica)Igual a agency_idpaso 8textRedundante con agency_id
agency_resolution_method(técnica)Cómo se resolvió la agenciapaso 8text: raw, from_original_transaction, from_associated_trx, fallback_unknownAuditoría
agency_resolution_confidence(técnica)Confianza de esa resoluciónpaso 8text: high, medium, lowAuditoría
primary_payment_typeMedio de pago principalEl pago success de mayor importe de la ventapublic.payments → paso 8ctext (FK dim_payment_method)NULL si la venta no tiene pagos
primary_payment_categoryCategoría de pago principalCategoría de negocio del medio principaldim_payment_method.payment_category → paso 8ctext: ver §4.2—
payment_method_countCantidad de medios de pagoMedios distintos con pago successpaso 8cinteger—
is_multi_tenderPago combinado1 si se pagó con más de un mediopaso 8csmallint 1/0—
paid_amountImporte cobrado (sin signo)Suma de pagos success de la ventapayments.amount → paso 8cnumericSin signo: en devoluciones es la plata que sale y viene positiva. Para neto: CASE WHEN total_amount < 0 THEN -paid_amount ELSE paid_amount END

Vistas derivadas:

  • v_fact_sale_parent_only (28 columnas, las mismas de fact_sale sin sale_type ni

source_transaction_id): sólo ventas padre, sin R-, PI- ni cancelaciones. Es venta bruta, no neta. Nombre sugerido: "Ventas padre (bruto)".

  • v_fact_sale_agency_canonica (15 columnas): fact_sale con agency_id_reporting (agencia

canónica). Uso técnico; no publicar sin revisar con Ingeniería.

Métricas recomendadas:

Métrica (Strategy)DefiniciónFiltros obligatorios
Venta netaSum(fact_sale.net_sale)currency fija
Venta brutaSum(v_fact_sale_parent_only.gross_sale)currency fija
Cantidad de transaccionesCount(v_fact_sale_parent_only.sale_id)—
Cobrado netoSum(CASE WHEN total_amount < 0 THEN -paid_amount ELSE paid_amount END)currency fija
4

Pagos

4.1 bi_rebuild.fact_payment — 15 columnas

Grano: 1 pago de una venta (una venta puede tener varios). ETL: paso 8c, desde public.payments (arreglo data.payments[] del webhook). Guía: GUIA_FORMA_DE_PAGO_BI_REBUILD.md.

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
payment_idPago (ID)Identificador del pagopayments.id → 8cuuidClave
sale_idVenta (ID)Venta a la que pertenecepayments.sale_id → 8cuuidJoin a fact_sale
payment_typeMedio de pago (código)Código Betterez del medio (cash, visa, plataforma10, …)payments.type → 8ctextJoin a dim_payment_method
display_nameMedio de pagoNombre legible del mediodim_payment_method.display_name → 8ctext—
providerProveedorProcesador del pago (Prisma, Mercado Pago, …)payments.provider → 8ctext—
amountImporte pagadoImporte del pago, en unidades de la monedapayments.amount normalizado (escala ×100.000 corregida) → 8cnumericSiempre positivo: en devoluciones/cancelaciones es plata que sale. Neto: invertir signo cuando fact_sale.total_amount < 0
currencyMonedaISO de la ventasales.currency → 8cvarchar(3)Regla 4
statusEstado del pagoResultado del cobropayments.status → 8ctext: success, error, pending—
is_successPago aprobado1 si status = success8csmallint 1/0Filtrar = 1 para importes
sale_dateFecha de ventaFecha ART de la ventasales.sale_date → 8cdateEs la fecha de la venta, no del pago
fecha_liquidacionFecha de liquidaciónFecha contable de la venta del pagofact_sale.fecha_liquidacion por sale_id → 11bdateEl pago es de la transacción: en una cancelación lo lleva la fila contenedora
sale_hourHora de venta0–23 ARTideminteger—
channelCanal de ventaCanal de la ventafact_sale.channel → 8ctext—
agency_idAgencia (ID)Agencia de la ventafact_sale.agency_id → 8ctext—
updated_at(técnica)Última escritura ETL8ctimestamptz—

Métrica: Importe por medio de pago (neto) = Sum(CASE WHEN fact_sale.total_amount < 0 THEN -amount ELSE amount END) con is_success = 1 y currency fija. Desde el 28/09/2026 no hay pagos duplicados en la tabla.

4.2 bi_rebuild.dim_payment_method — 11 columnas

Grano: 1 código de medio de pago (32 medios). Semilla manual en bi_rebuild_schema_patch_once.sql.

ColumnaNombre en StrategyQué significaTipo / valoresCuidado con
payment_typeMedio de pago (código)Clavetext—
display_nameMedio de pagoNombre legible (incluye tarjeta / banco)text—
providerProveedorProcesadortext—
payment_categoryCategoría de pagoNivel gruesotext: cash, card_terminal, card_credit, card_debit, online, external_channel, voucher, no_payment, otherno_payment es legítimo (cortesías, movimientos IROP). other = código nuevo sin catalogar
payment_groupGrupo de pagoAgrupación para gráficostext: physical, online, external_channel, voucher, system—
is_card / is_cash / is_online / is_external_channel / is_no_paymentEs tarjeta / efectivo / online / canal externo / sin cobroBanderas de categoríasmallint 1/0—
is_activeMedio activoVigente en el catálogosmallint 1/0—
5

Boletos

5.1 bi_rebuild.fact_ticket — 85 columnas

Grano: 1 boleto (1 pasajero en 1 tramo). ETL: paso 8b (base), 9–10 (etiquetas y alcance de ruta), 10-sync (nombre de itinerario), 10-geo (provincias y alcance_tramo), 10b (transportista, alícuota), 10d (liquidación SAP), 10e (transaction_id), 10f (conexiones), 11 (viaja_con_menor_3). Guías: SCHEMA_FACT_TICKET.md, GUIA_ESTADOS_TICKET_BI_REBUILD.md.

Identificación y venta

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
ticket_idBoleto (ID)ID interno del boletopublic.tickets.id → 8buuidClave; mapear como texto. Count(ticket_id) = boletos
ticket_numberNúmero de boletoCódigo visible del pasaje (ej. BRHKTV)Betterez ticketNumber → tickets.ticket_number → 8bvarcharBetterez reutiliza números para boletos distintos (~15/día): no usarlo como clave
sale_idVenta (ID)Venta a la que pertenecetickets.sale_id → 8buuidJoin a fact_sale / fact_payment
transaction_idNúmero de transacciónCódigo Betterez de la venta (el que ve Contaduría)sales.transaction_id → 10etextPara joins usar sale_id
channelCanal de ventaCanal del boletoBetterez channel → tickets.channel → 8btext: agency-backoffice, websales, callcenter, unknown, manual-backfill—
channel_groupGrupo de canalAgrupación del canaldim_channel.channel_group → 8btext: physical, web, callcenter—
agency_idAgencia (ID)Agencia de la ventasales.agency_id → 8btext; sin_agency_id = Backofficefact_ticket_mstr no tiene agencia: para boletos por agencia usar este hecho o unir por ticket_id
agency_codeCódigo de agenciadim_agency → 8btext—
agency_labelAgencia"{código} - {nombre}"dim_agency → 8btext—
sale_dateFecha de ventaDía de venta ARTsales.sale_date → 8bdateRegla 6
sale_hourHora de venta0–23 ARTideminteger—
mes_anioMes de ventaYYYYMM de sale_date8binteger (ej. 202609)Join a dim_mes_anio. Año anterior = mes_anio - 100

Importes, tarifa e impuestos

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
ticket_amountImporte del pasajePrecio del boleto (con IVA)Betterez total → tickets.total (normalizado) → 8bnumericEs el precio del boleto, no la caja: para plata cobrada usar net_sale (regla 1). Filtrar currency; ~85 pasajes ARS legítimos > 500.000 (outliers)
ticket_taxesIVA del pasajeIVA incluido en el precioBetterez displayTotalTaxes → tickets.display_total_taxes → 8bnumericNo mezclar con la percepción RG3819 (fact_sold_item)
vat_rateAlícuota de IVAAlícuota declarada por Bettereztickets.taxes_detail.value (milésimas: 10500) → 10b, guardada en porcentajenumeric: 10.50 o NULLNULL en tramos internacionales (no tributan IVA argentino)
currencyMonedaISO de la ventasales.currency → 8bvarchar(3)Regla 4
fareTarifaNombre de la tarifa en Bettereztickets.fare → 8bvarchar (ej. Adulto, Cortesia 20%, NIÑO BUS MENDOZA)—
pax_segmentSegmento de pasajeroClasificación de la tarifaRegla sobre fare → 8bvarchar: adult, child_fare, courtesy, othercourtesy no es "gratis": el 98% son tarifas "Cortesia 10/20/30%" que se pagan con descuento (medido sep-2026). other = estudiante, jubilado, promociones
is_child_fareTarifa niño1 si pax_segment = child_fare8bsmallint 1/0No es menor de 3 (eso es viaja_con_menor_3 / fact_sold_item)

Estado del boleto

Detalle y árbol de decisión: GUIA_ESTADOS_TICKET_BI_REBUILD.md.

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
is_paidBoleto pagadoPagado y sin cancelación, reembolso ni cambiotickets.status + public.refund_details → 8b (1b)smallint 1/0Filtro por defecto para contar boletos
is_paid_including_partial_cancelPagado (criterio Betterez web)is_paid o cancelación parcial con penalidad retenidaidem → 8bsmallint 1/0Sólo para cuadrar con el reporte web de Betterez por agencia
effective_ticket_statusEstado del boletoEstado curado (prioriza refund_details)8btext: paid, cancelled, changed, payment expired, waiting for payment, movedbyirop, createdPara contar pagados usar is_paid, no = 'paid' (ver Inconsistencias)
is_cancelledCanceladoStatus cancelled o hay cancelación en refund_details8b / 1esmallint 1/0—
is_partial_cancelCancelación parcialCancelado con reembolso menor al preciorefund_details → 8bsmallint 1/0Betterez lo sigue contando como vendido
is_full_cancelCancelación totalReembolso ≥ precioidemsmallint 1/0—
is_refundedReembolsadoStatus refunded o reembolso en refund_detailsidemsmallint 1/0—
has_refund_recordTiene devolución registradaExiste fila en refund_detailsidemsmallint 1/0—
refund_typeTipo de devoluciónTipos presentes en refund_detailsidemtext: cancel, change, NULL—
refund_amountImporte reembolsadoSuma devuelta por el boletorefund_details.refunded → 8bnumericInformativo; la plata neta está en fact_sale
cancel_penalty_amountPenalidad retenidaPrecio − importe cancelado (cancelación parcial)8bnumeric—
ticket_status(técnica) Estado crudoStatus tal como lo manda Bettereztickets.status → 8btextSólo auditoría
current_status(técnica)Espejo del estado OLTP8btextEvitar en KPI
is_final_paid(técnica)Espejo OLTP8bsmallint 1/0Evitar en KPI
status_synced_at(técnica)Última sincronización del estado8btimestamp—
successor_ticket_numberBoleto de reemplazoNúmero del boleto que reemplazó a este en un cambioHeurística + API changedFromTicketId → job semanal successortextNULL si no hubo cambio o no se pudo enlazar
successor_transaction_idTransacción del reemplazoTransacción Betterez del boleto nuevoidemtext—
successor_match_methodMétodo de enlace del reemplazoCómo se encontró el reemplazoidemtext: heuristic_unique, api, same_webhook, heuristic_ambiguous, same_transaction, heuristic_disambiguatedheuristic_ambiguous = baja confianza

Viaje, ruta e itinerario

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
originOrigen del pasajeroParada donde subeBetterez origin → tickets.origin → 8btextEs el tramo del pasajero, no del bus
destinationDestino del pasajeroParada donde bajatickets.destination → 8btextidem
origin_province / destination_provinceProvincia origen / destinoProvincia de la paradadim_station_geo + dim_province → 10-geotext—
origin_country / destination_countryPaís origen / destinoPaís de la paradaidemtext (Argentina, Chile, …)—
alcance_tramoAlcance del tramoGeografía del viaje del pasajeroProvincias/países de origen y destino (respaldo: etiquetas) → 10-geotext: Nacional, Provincial, Internacional, Sin clasificarInternacional = alguna punta fuera de AR. No es alcance_ruta ni tipo_liquidacion (§10)
alcance_rutaAlcance de la ruta (etiqueta)Clasificación del servicio por etiqueta del manifiestomanifest_entries.manifest_tags → bridge → paso 10text: Internacional, Nacional, Provincial, Sin clasificacionUna etiqueta nueva en Betterez queda "Sin clasificacion" sin error
tipo_servicioTipo de servicioLínea o refuerzo, por etiquetaidem → paso 10text: Linea, Refuerzo, Sin clasificacion, NULLTrae NULL además de "Sin clasificacion" (ver Inconsistencias)
has_nacional / has_internacional / has_linea_provincialEtiqueta Nacional / Internacional / Línea provincialEl manifiesto tiene esa etiquetabridge → paso 10smallint 1/0INTERNACIONAL CHILE y NACIONAL CHILE cuentan como internacional. has_linea_provincial mira el manifiesto actual: un boleto devuelto sale del manifiesto y queda en 0. Para venta provincial usar es_linea_provincial
es_linea_provincialLínea provincial (incluye devueltos)1 si el boleto es de línea provincial aunque se haya cancelado o cambiadoManifiesto actual → manifiesto que tuvo (manifest_entries) → mayoría de los manifiestos etiquetados del itinerario → paso 10gsmallint 1/0Usar esta, no has_linea_provincial, para venta provincial con devoluciones
origen_linea_provincial(técnica) Origen de la marca provincialDe dónde salió es_linea_provincial10gtext: manifiesto, manifiesto_historico, itinerario, sin_datoAuditoría
fecha_devolucionFecha de devoluciónDía (ART) de la devolución del boletoFila R- (refund_details → sales.sale_date) → 10gdateNULL = sin devolución. Con refund_amount, para restar devoluciones por el mes en que ocurrieron
has_linea / has_refuerzoEtiqueta Línea / Refuerzoidemidemsmallint 1/0—
manifest_tags(técnica)Etiquetas crudas del manifiestomanifest_entries.manifest_tags → 8bjsonbNo usar en MSTR: usar has_*
route_idRuta (ID)Ruta de Inventory de Betterezdim_schedule.route_id / schedules.route_id / dim_manifest.route_id → 8btext (ObjectId)~59.000 boletos viejos traen un route_id con formato ORIGEN_DESTINO que no existe en dim_route
itineraryItinerario (nombre)Nombre del servicio (ej. 13:00 Mendoza-Uspallata)dim_schedule.display_name → 8b / 10-synctextNo usar como ID (regla 10)
schedule_idItinerario (ID)Servicio de Bettereztickets.schedule_id o del manifiesto → 8btextID del atributo Itinerario
schedule_distance_kmKm del itinerarioKm del recorrido completo del serviciodim_schedule.distance_total_km → 8bnumericMax, nunca Sum por boleto
travel_distanceKm del tramoKm del tramo que compró el pasajeroBetterez travelDistance; si falta, suma de tramos de schedule_legs (paso 1d) → 8bintegerCobertura ~56%. No es km del servicio
distanceKm de la ruta (mediana)Mediana de km de los itinerarios de la rutadim_route.distance → 8bnumericSólo confiable si dim_route.distance_confiable = 1
operating_company_nameTransportistaRazón social o UTE que opera el serviciodim_schedule.operating_company_name → 10btextNo es la marca (brand_name). Clave del Tablero Contable
product_familyFamilia de productoFamilia Bettereztickets.product_family → 8btext: reservation, …Tours comparten reservation
product_typeTipo de productoTipo concretotickets.product_type → 8btext: reservation, city_tour_mendoza, citytour_cordoba, nueva_cuyanita, boleto_caliente, ac80_-_la_salada, combo_millonario, …Pasajeros interurbanos = product_type = 'reservation'
trip_typeTipo de tramofull_route si el tramo coincide con el recorrido del manifiesto8btext: full_route, partial_segmentpartial_segment no es cancelación parcial
route_match_flag(técnica)1 si el tramo = recorrido del manifiesto8binteger 1/0—

Fechas del viaje y manifiesto

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
travel_dateFecha de viaje del tramoDía en que el pasajero viaja su tramotickets.travel_date → 8bdateDifiere de la salida del servicio en ~25% (nocturnos, largos)
departure_datetime_utcSalida (UTC)Fecha y hora de salida del tramotickets.departure_datetime → 8btimestamptz—
departure_date_arFecha de salida del tramo (ART)Fecha argentina de la salidaidem en ART → 8bdate—
manifest_dateFecha del servicioFecha de salida del bus (manifiesto)dim_manifest.manifest_date → 10-geodateLa que usa Planificación. NULL sin manifiesto
manifest_idManifiesto (ID)Servicio en un día concretoMatch por ticket_number o por itinerario + fecha → 8btextNULL si no matcheó
manifest_match_methodCómo se asignó el manifiestoMétodo o motivo de no-match8btext: ticket_number, path_schedule, chain_reissue_heuristic, non_reservation_product, non_paid_status, cancelled_no_manifest, refunded_no_manifest, no_matchSólo no_match es un huérfano a investigar
date_match_flag(técnica)1 si travel_date = manifest_date8binteger 1/0—
has_manifest_entry_oltp(técnica)Existe en manifest_entries8bsmallint 1/0—

Conexiones (viaje con trasbordo)

Betterez vende un viaje con trasbordo como un boleto por tramo, en la misma venta. Guía: GUIA_CONEXIONES_BI_REBUILD.md. Regla: un boleto es un tramo; para contar viajes, filtrar tramo_orden = 1.

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
journey_idViaje (ID)Viaje de Betterez; todos sus tramos lo compartentickets.journey_id → 10ftextNULL antes de marzo 2026: para contar viajes usar tramo_orden = 1, no Count Distinct journey_id
tramo_ordenN.º de tramoOrden del tramo dentro del viajejourney_order + 1 → 10finteger: 1, 2, 3Un viaje directo es un solo tramo con 1
cantidad_tramosCantidad de tramosTramos vendidos del viaje10finteger: 1 directo, 2 o 3 con conexiónNo cambia si se cancela un tramo
is_conexionEs conexión1 si el boleto es tramo de un viaje con trasbordo10fsmallint 1/0Contar filas con is_conexion = 1 da tramos, no viajes
parada_conexionParada de trasbordoDónde trasborda (con dos: MENDOZA > SAN LUIS)10ftext; NULL si es directoNombre de parada tal como lo manda Betterez
origen_viaje / destino_viajeOrigen / destino del viajePunta a punta del viaje completo que compró el pasajero10ftextorigin / destination son los del tramo

Liquidación SAP y ancillaries

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
numero_liquidacionN.º de liquidación SAPLiquidación de agencia que incluyó el boletosap.liquidacion_sap_ticket → 10dintegerSe numera por agencia: la clave es agency_code + número. Ver §10
fecha_liquidacionFecha de liquidaciónFecha contable del boletoidem → 10ddateCasi siempre sale_date + 1
tipo_liquidacionTipo de liquidación SAPN / P / I con que SAP liquidósap.liquidacion_sap_ticket.tipo_servicio → 10dtext: N, P, I, NULLVer §10
viaja_con_menor_3Viaja con menor de 3El adulto lleva un ítem BOLETO SEGUROfact_sold_item → paso 11varchar: 'Si' / 'No'Única bandera de texto (regla 5)

5.2 bi_rebuild.fact_ticket_mstr — 68 columnas

Grano: 1 boleto (misma cantidad de filas que fact_ticket, 1:1 por ticket_id). Es la tabla "ancha" pensada para el cubo de Strategy. ETL: paso 10 (+ 10b, 10c, 10d, 10e, 10f, 11). Guía: SCHEMA_FACT_TICKET_MSTR.md.

53 columnas compartidas con fact_ticket, con el mismo significado (ver §5.1): ticket_id, sale_date, departure_date_ar, travel_date, ticket_amount, manifest_id, channel, channel_group, origin, destination, route_id, itinerary, is_paid, effective_ticket_status, product_type, product_family, has_nacional, has_internacional, has_linea_provincial, has_linea, has_refuerzo, currency, ticket_taxes, fare, pax_segment, is_child_fare, schedule_id, schedule_distance_km, successor_ticket_number, successor_transaction_id, successor_match_method, viaja_con_menor_3, origin_province, destination_province, origin_country, destination_country, alcance_tramo, manifest_date, operating_company_name, vat_rate, numero_liquidacion, fecha_liquidacion, tipo_liquidacion, transaction_id, journey_id, tramo_orden, cantidad_tramos, is_conexion, parada_conexion, origen_viaje, destino_viaje, es_linea_provincial, origen_linea_provincial.

15 columnas propias:

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
has_viajes_especialesEtiqueta Viajes especialesManifiesto con esa etiquetabridge → paso 10smallint 1/0Sin manifiesto: todos los has_* = 0
has_auxilioEtiqueta Auxilioidemidemsmallint 1/0—
has_bus_turisticoEtiqueta Bus turísticoidemidemsmallint 1/0—
has_combo_millonarioEtiqueta Combo millonarioidemidemsmallint 1/0—
has_la_cuyanitaEtiqueta La Cuyanitaidemidemsmallint 1/0—
has_encomiendasEtiqueta Encomiendasidemidemsmallint 1/0—
has_ipEtiqueta IPidemidemsmallint 1/0—
has_ficticioEtiqueta FicticioManifiesto ficticio (no operó como tal)idemsmallint 1/0Conviene excluirlos (= 0) en ocupación
has_viaje_protegidoTiene seguroEl boleto tiene VIAJE PROTEGIDO asociadofact_sold_item → 11smallint 1/0Subestima los ítems sin ticket_number
viaje_protegido_amountImporte del seguro del boletoSuma de seguros del boletoidemnumericPara recaudación de seguros usar fact_sold_item
has_boleto_seguroTiene boleto de menor de 3El boleto tiene BOLETO SEGURO asociadoidemsmallint 1/0Misma señal que viaja_con_menor_3
boleto_seguro_amountImporte boleto menor de 3Suma de esos ítemsidemnumericidem
primary_payment_typeMedio de pago principalMedio principal de la venta del boletofact_sale.primary_payment_type → 10ctextAtributo, no importe (regla 3)
primary_payment_categoryCategoría de pago principalidem, nivel gruesofact_sale → 10ctext (ver §4.2)idem
updated_at(técnica)Última escritura ETLpaso 10timestamptz—

Lo que NO tiene (y hay que ir a fact_ticket por ticket_id): agency_id / agency_code / agency_label, ticket_number, sale_id, sale_hour, mes_anio, alcance_ruta, tipo_servicio, is_partial_cancel, is_paid_including_partial_cancel, is_cancelled, is_refunded, refund_amount, manifest_match_method, trip_type, travel_distance.

5.3 bi_rebuild.v_conexion_trasbordo — 35 columnas (vista)

Desde 02/10/2026 (migración v_conexion_trasbordo). Una fila por trasbordo: une el tramo que llega a la parada de conexión (llega_*: ticket, itinerario, schedule_id, manifiesto, transportista, salida y llegada en UTC) con el que parte de ella (parte_*), dentro del mismo viaje y pasajero. Un viaje de 3 tramos da 2 filas (trasbordo_orden 1 y 2). Especificación del tablero: ESPEC_TABLERO_CONEXIONES_MSTR.md.

  • viaje_id es el journey_id; en el histórico sin journey_id es v: + un hash de venta, viaje

y pasajero (no expone el documento). 12 tramos históricos ambiguos quedan afuera.

  • espera_min: minutos entre la llegada programada del tramo que llega y la salida del

siguiente. Promediar solo con espera_valida = 1 (entre 0 y 24 h).

  • is_paid_trasbordo = 1 cuando los dos tramos están vigentes. No filtra: un trasbordo con un

tramo cancelado también es dato (llega_is_paid, parte_is_paid).

  • Métrica "Pasajeros que trasbordan" = Count(llega_ticket_id) con is_paid_trasbordo = 1.
  • Septiembre 2026: 1.771 trasbordos, 1.573 vigentes, espera mediana 70 min. Pares más frecuentes:

PPCO → GJ01 en Córdoba (205) y JG03 → COPB en Córdoba (173).

  • No duplica boletos: cada tramo aparece a lo sumo una vez como "llega" y una como "parte". Se

calcula al consultarla (~0,2 s el reporte por par de servicios).

6

Manifiestos y ocupación

Un manifiesto es un servicio (itinerario) en un día concreto: el bus que salió el 15/03 a las 13:00. Guías: GUIA_FACT_MANIFEST_LEG_BI_REBUILD.md, GUIA_TABLERO_ETIQUETAS_MANIFIESTO.md.

6.1 bi_rebuild.dim_manifest — 32 columnas

Grano: 1 manifiesto. ETL: paso 7, desde public.manifest_legs y public.manifest_entries (Betterez GET /manifests/{id}, webhooks manifests.* y job andesmar-manifests-sync).

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
manifest_idManifiesto (ID)ID Betterez del manifiestomanifest_legs.manifest_id → 7textClave
manifest_dateFecha del servicioDía de salida del serviciomanifest_legs.manifest_date → 7dateFiltro temporal de ocupación (no sale_date)
schedule_idItinerario (ID)Serviciomanifest_legs.schedule → 7text—
schedule_display_nameItinerarioNombre del serviciomanifest_legs.schedule_display_name → 7textPuede repetirse entre schedule_id
route_idRuta (ID)Ruta Inventory→ 7text—
originOrigen del recorridoPrimera parada del busPrimera station de manifest_legs por leg_ord → 7 (CTE ruta_od)text; Sin datos si no hayRegla 8: no es donde subió el pasajero
destinationDestino del recorridoÚltima parada del busÚltima to_station → 7textidem
departure_time_refHora de salidaHora de salida de la primera paradamanifest_legs.departure_time → 7text HH:MMidem
departure_hourHora de salida (hora)Hora entera de la salidaderivada → 7integer—
manifest_statusEstado del manifiestoEstado en Betterezmanifest_entries.manifest_status → 7text: published, paused, emptied, canceled, Sin datosPara ocupación filtrar published; Sin datos = manifiesto sin boletos
peak_loadPasajeros en el picoMáxima cantidad de pasajeros a bordo en algún tramoMAX(manifest_legs.load) → 7integerEs el pico, no la carga media (§12)
peak_load_factorOcupación pico (%)Máximo % de ocupación en algún tramoMAX(manifest_legs.load_factor) → 7numeric (0–100)idem
bus_capacityCapacidad del busAsientos del manifiestoBetterez manifest.capacity o vehicle.capacity → 7numeric—
inferred_capacityCapacidad (estimada)bus_capacity, o peak_load / peak_load_factor si falta→ 7numeric—
available_seats_estAsientos libres en el pico (est.)Capacidad inferida − pico→ 7numericEstimación
trip_scope(técnica) AlcanceInternacional / nacional por etiquetaetiquetas → 7text: internacional, nacional, NULLMinúsculas y sin "provincial": usar fact_manifest_leg.alcance_ruta
manifest_tags(técnica)Etiquetas crudasmanifest_entries.manifest_tags → 7jsonbUsar bridge_manifest_tag
vehicle_id / vehicle_name / vehicle_plate / vehicle_number / vehicle_capacity / bus_idVehículo / Patente / Interno / Capacidad del vehículoUnidad asignadaBetterez vehicle.* → manifest_legs → 7text / numericCobertura parcial (ver has_vehicle_data)
vehicle_json(técnica)Objeto vehículo completo→ 7jsonb—
driver_primary_first_name / driver_primary_last_name / driver_primary_external_id / driver_primary_orderChofer principalPrimer chofer asignadoBetterez drivers[] → 7text / integerCobertura parcial
drivers_json(técnica)Todos los choferes→ 7jsonb—
driver_source(técnica)Origen del dato de recursos→ 7text—
has_driver_data / has_vehicle_dataTiene chofer / Tiene vehículoHay datos de chofer / vehículo→ 7smallint 1/0—

6.2 bi_rebuild.fact_manifest_leg — 21 columnas

Grano: 1 tramo entre dos paradas consecutivas de un manifiesto (manifest_id + leg_ord). ETL: paso 7b (y un UPDATE posterior al paso 10 para alcance_ruta / product_type), desde public.manifest_legs (Betterez manifest.legs[]). Cubre desde abril de 2025.

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
manifest_idManifiesto (ID)manifest_legs → 7btextJoin a dim_manifest
leg_ordOrden de la paradaPosición del tramo en el recorrido (0 = primera)Betterez legs[] → 7binteger—
manifest_dateFecha del servicio→ 7bdateFiltro temporal
route_id, schedule_id, schedule_display_nameRuta / ItinerarioIgual que en dim_manifest→ 7btext—
station_id / stationParada (ID) / ParadaParada donde empieza el tramoBetterez stationId / station → 7btextNombres con variantes: agrupar por station_id
to_station_id / to_stationParada siguienteParada donde termina el tramo→ 7btext—
arrival_time / departure_timeHora de llegada / salidaHorario en esa paradaBetterez arrivalTime / departureTime → 7btext HH:MMTexto, no timestamp
duration_minutesDuración del tramo (min)Minutos hasta la parada siguienteBetterez leg → 7binteger~14% en 0 o NULL
pax_onSubenPasajeros que suben en stationBetterez paxOn → 7binteger—
pax_offBajanPasajeros que bajan en stationBetterez paxOff → 7binteger—
loadPasajeros a bordoPasajeros a bordo al salir de station hacia to_station (= carga anterior + suben − bajan)Betterez load → 7bintegerEs un stock, no un flujo: no sumar entre paradas (ver §12)
load_factorOcupación del tramo (%)load / capacidad × 100, redondeado por BetterezBetterez loadFactor → 7binteger 0–100Entero redondeado: para promedios, recalcular con load y bus_capacity
distance_kmKm del tramoKm entre station y to_stationdim_schedule_leg.distance_km por estaciones → 7b (desde 29/09/2026)numericNULL si Betterez informa 0 o no se encuentra (~22% en sep-2026): dato faltante. Para carga media usar v_manifest_carga_media (§12)
alcance_rutaAlcance de la rutaIgual que fact_ticket.alcance_rutaetiquetas → post-paso 10text—
product_typeTipo de productoProducto dominante del manifiestomoda de fact_ticket.product_type → post-paso 10text—
updated_at(técnica)→ 7btimestamptz—

6.3 Etiquetas: dim_manifest_tag (9) y bridge_manifest_tag (2)

ColumnaNombre en StrategyQué significaTipo / valoresCuidado con
dim_manifest_tag.tag_name_normEtiqueta (ID)Etiqueta normalizada (minúsculas, sin acento)textClave
dim_manifest_tag.tag_nameEtiquetaNombre como en Bettereztext: NACIONAL, INTERNACIONAL, INTERNACIONAL CHILE, NACIONAL CHILE, LINEA PROVINCIAL, LINEA, REFUERZO, VIAJES ESPECIALES, AUXILIO, Bus Turistico, COMBO MILLONARIO, La Cuyanita, ENCOMIENDAS, IP, FICTICIO—
dim_manifest_tag.facetFamilia de etiquetaAgrupadortext: route_scope, service_type, function, status—
dim_manifest_tag.description / color / sort_orderDescripción / color / ordenPresentacióntext / smallint—
dim_manifest_tag.is_activeEtiqueta activasmallint 1/0—
dim_manifest_tag.first_seen_at / last_seen_at(técnica)Primera y última vez vistatimestamptz—
bridge_manifest_tag.manifest_id / tag_name_norm(clave)Relación N:M manifiesto ↔ etiqueta (paso 9)textEn Strategy preferir los has_* de los hechos; el bridge es para auditoría
7

Agencias y canales

7.1 bi_rebuild.dim_agency — 11 columnas

Grano: 1 agencia Betterez (319). ETL: paso 4 + backfill_dim_agency_catalog.sql (export de agencias de Betterez). Guía: GUIA_TABLERO_AGENCIAS_BI_REBUILD.md.

ColumnaNombre en StrategyQué significaTipo / valoresCuidado con
agency_idAgencia (ID)ID Mongo de BettereztextClave; sin_agency_id = Backoffice (call center sin agencia)
agency_codeCódigo de agenciaCódigo de negociotextUn código puede tener varios IDs
agency_display_nameNombre de agenciaNombre sin códigotext—
agency_labelAgencia"{código} - {nombre}"textLa DESC a mostrar
agency_name(no publicar)Alias de agency_labeltextCompatibilidad con cubos viejos
agency_countryPaís de la agenciatext: AR, CL, BO, PE, NULL (46)El país explícito del catálogo ≠ AR gana
is_activeAgencia activaVigente hoy en Betterezsmallint 1/0No usar como filtro por defecto: una inactiva puede tener ventas
is_blacklistedAgencia duplicadaDuplicado del catálogo, no ofrecer en el buscadorsmallint 1/0El filtro del buscador es is_blacklisted = 0
catalog_source(técnica)Origen de la filatext: betterez_export, system_sentinel—
first_seen_sale_date / last_seen_sale_datePrimera / última ventaRango de actividaddate—

agency_blacklist (5 columnas: agency_id, agency_code, agency_label, motivo, created_at) es la lista decidida por Andesmar de la que se deriva is_blacklisted. No se publica.

7.2 bi_rebuild.dim_channel — 2 columnas

ColumnaNombre en StrategyTipo / valores
channelCanal de ventaagency-backoffice, websales, callcenter, manual-backfill, unknown
channel_groupGrupo de canalphysical, web, callcenter

Plataforma 10 no es un canal: es una agencia (agency_code = 'PTF') dentro del grupo web.

8

Rutas, itinerarios y geografía

Tres ideas que se mezclan: itinerario (el servicio con horario: dim_schedule), ruta (agrupador comercial de Betterez: dim_route) y tramo (lo que compró el pasajero: fact_ticket.origin/destination). Detalle: CONVENCIONES §4.1.

8.1 bi_rebuild.dim_schedule — 20 columnas

Grano: 1 itinerario. ETL: paso 6c, desde public.schedules (webhooks schedule.* y backfill_schedules.py).

ColumnaNombre en StrategyQué significaTipo / valoresCuidado con
schedule_idItinerario (ID)textClave
display_nameItinerarioNombre legibletextHay nombres repetidos (hasta 90 versiones de un recorrido): agrupar por route_id para "el mismo servicio"
route_id / route_nameRuta (ID) / RutaRuta de Inventorytext—
travel_route_id / travel_route_nameRecorrido comercialTravel route de Bettereztext—
from_station / to_station / from_id / to_idCabecera / Destino finalExtremos del itinerariotext—
departure_timeHora de salidatext—
from_date_ymd / to_date_ymdVigencia desde / hastatext YYYY-MM-DDTexto, no fecha
distance_total_kmKm del itinerarioKm del recorrido completonumeric—
duration_total_minutesDuración (min)integer—
capacityCapacidad declaradainteger—
brand_nameMarca comercialEj. ANDESMARtextAgrupa varias razones sociales
operating_company_nameTransportistaRazón social / UTEtextLa del Tablero Contable
is_deletedItinerario borradosmallint 1/0—
updated_at(técnica)timestamptz—

8.2 bi_rebuild.dim_schedule_leg — 8 columnas

Grano: 1 tramo del itinerario (schedule_id, leg_ord). Columnas: from_id, from_name, to_id, to_name, distance_km (km del tramo), duration_minutes. Es la única fuente de km por tramo (la usa la carga media, §12). Cuidado: el leg_ord del itinerario no siempre coincide con el del manifiesto; unir por schedule_id + from_id + to_id.

8.3 bi_rebuild.dim_route — 7 columnas

Grano: 1 ruta (181). ETL: paso 6.

ColumnaNombre en StrategyQué significaTipo / valoresCuidado con
route_idRuta (ID)ObjectId Bettereztext—
rutaRutaorigen - destino legibletext—
origin / destinationOrigen / destino de la rutaDel recorrido principaltextSi recorridos > 1 la ruta agrupa otros pares que no se ven acá
distanceKm de la ruta (mediana)Mediana de km de sus itinerariosnumericSólo sirve si distance_confiable = 1
recorridosCantidad de recorridosPares origen-destino que agrupasmallint—
distance_confiableKm confiable1 si recorridos = 1smallint 1/0Para km por boleto usar schedule_distance_km

8.4 Estaciones: dim_station (4), dim_station_geo (5), dim_province (3)

ColumnaNombre en StrategyQué significa
dim_station.station_id / station_nameParada (ID) / ParadaCatálogo de paradas vistas en manifiestos (paso 6b, 430 paradas)
dim_station.name_norm / aliases(técnica)Nombre normalizado y variantes de escritura
dim_station_geo.name_key / sample_name(técnica)Clave por nombre normalizado (paso 6b-geo)
dim_station_geo.province / countryProvincia / País de la paradaGeografía de la parada
dim_station_geo.geo_source(técnica)betterez_inventory, manual_curado, manual_limite (parada en límite provincial: país sí, provincia no)
dim_province.province_norm / province_label / countryProvinciaCatálogo de provincias con etiqueta legible
9

Seguros, menores de 3 y otros ítems

Guía: GUIA_TABLERO_SEGUROS_Y_MENORES.md, SCHEMA_FACT_SOLD_ITEM.md.

9.1 bi_rebuild.fact_sold_item — 27 columnas

Grano: 1 ítem vendido aparte del pasaje (fila PI-). ETL: paso 11, desde public.sold_item_details.

ColumnaNombre en StrategyQué significaDe dónde saleTipo / valoresCuidado con
sold_item_idÍtem (ID)sold_item_details.id → 11uuidCount = ítems
sold_item_numberNúmero de ítemEj. PI-ABC123→ 11text—
sale_id / parent_sale_id / parent_transaction_idVenta del ítem / Venta madreFila PI- y la venta del pasaje que la contiene→ 11uuid / text—
ticket_number / ticket_idBoleto asociadoBoleto al que se agregó el ítem→ 11text / uuidPuede ser NULL (~20% de menores)
product_nameProductoNombre Betterez (VIAJE PROTEGIDO, BOLETO SEGURO NACIONAL, …)→ 11text—
product_categoryCategoría de productodim_sold_item_product → 11text: insurance_passenger, minor_under_3, tax_cross_border, other_paid_inother_paid_in = producto nuevo sin catalogar
product_type / product_family(técnica)Suelen ser paid_in / item→ 11text—
is_insuranceEs seguro1 = VIAJE PROTEGIDO→ 11smallint 1/0PI- ≠ seguro
is_minor_boleto_seguroEs boleto menor de 31 = BOLETO SEGURO%→ 11smallint 1/0Cuenta ítems, no boletos. No es tarifa niño
item_amountImporte del ítemCon IVAsold_item_details.total → 11numericFiltrar currency y status = 'paid'
item_netNeto del ítemSin IVAsold_item_details.price → 11numeric—
item_taxesIVA del ítemCalculado total − price→ 11numericsold_item_details.total_taxes viene contaminado en origen y no se usa
vat_rateAlícuota del ítemitem_taxes / item_net × 100→ 11numeric0,00 en seguros y boleto seguro internacional
quantityCantidad→ 11integer—
statusEstado del ítem→ 11text: paid, waiting for payment, cancelled, changed, payment expired, createdUn ítem devuelto puede seguir en paid: cruzar con la transacción de reembolso
sale_date / sale_hourFecha / hora de ventaARTsales.sale_date (o created_at) → 11date / integer—
fecha_liquidacionFecha de liquidaciónFecha de la liquidación SAP que incluye el ítemsap.liquidacion_sap_item_detalle (fila de venta) → 11bdateCasi siempre sale_date + 1
channel / channel_groupCanal / Grupo de canalDel boleto padre→ 11text—
agency_idAgencia (ID)→ 11text—
currencyMoneda→ 11varchar(3)Regla 4
updated_at(técnica)→ 11timestamptz—

9.2 Catálogo y vistas

  • dim_sold_item_product (4): product_name (clave), product_category, betterez_catalog_name,

is_active. 7 productos: VIAJE PROTEGIDO, 5 variantes de BOLETO SEGURO, IMPUESTO NACIONAL ARGENTINO - RG3819.

  • v_insurance_kpi (7) y v_minor_kpi (7): agregados por sale_date, channel, channel_group,

agency_id, currency, con insurance_items_sold / insurance_revenue y minor_items_sold / minor_revenue. Ya filtran status = paid. Nombres sugeridos: "Seguros vendidos", "Recaudación seguros", "Boletos menor de 3 vendidos", "Recaudación boletos menor de 3".

10

Liquidación SAP y contable

Viven como columnas de fact_ticket y fact_ticket_mstr (§5.1): numero_liquidacion, fecha_liquidacion, tipo_liquidacion, más operating_company_name y vat_rate del Tablero Contable. Origen: sap.liquidacion_sap_ticket (pipeline sap-liquidacion-pipeline, 05:00 ART). fecha_liquidacion también está en fact_sale, fact_payment y fact_sold_item (desde 02/10/2026, paso 11b), para filtrar plata, pagos e ítems por fecha contable.

  • Venta provincial: es_linea_provincial = 1 (no has_linea_provincial, que pierde los boletos

devueltos). Neto por cohorte = Sum(ticket_amount) − Sum(refund_amount) por sale_date. Neto por movimientos del mes = Sum(ticket_amount) por sale_date − Sum(refund_amount) por fecha_devolucion. Septiembre 2026 (ARS): bruta $272,2 M, neto A $259,3 M, neto B $259,0 M. Guía: GUIA_VENTA_PROVINCIAL_TABLERO_CONTABLE.md.

  • NULL es legítimo: call center, ventas en Chile / Bolivia / Perú, agencias excluidas de SAP

(sap.ag_excluidas) y todo mayo de 2026 (la tabla SAP arranca en junio).

  • El número es por agencia: la liquidación 799 existe en muchas agencias. Clave =

agency_code + numero_liquidacion.

  • Fecha contable: Andesmar fecha el boleto en sus planillas por fecha_liquidacion, no por

sale_date.

Los tres alcances (no son intercambiables):

ColumnaQué clasificaCriterioValoresUsar para
alcance_rutaEl servicioEtiqueta que Andesmar pone al manifiesto en BetterezInternacional, Nacional, Provincial, Sin clasificacionFiltrar por tipo de servicio
alcance_tramoEl viaje del pasajeroGeografía de origen y destino (internacional si alguna punta está fuera de AR)Internacional, Nacional, Provincial, Sin clasificarAnalizar viajes
tipo_liquidacionCómo liquidó SAPRéplica del on-premise: P sólo dentro de Mendoza o de Córdoba; I = boleto sin IVAN, P, I, NULLCuadrar contra SAP

alcance_tramo y tipo_liquidacion coinciden ~96%. La diferencia es de definición, no un error.

11

Fechas

11.1 bi_rebuild.dim_date — 9 columnas y dim_mes_anio — 2 columnas

ETL: pasos 2, 2b y 3.

ColumnaNombre en StrategyQué significaTipo / valoresCuidado con
dim_date.date_idDíaClave de fechadate (desde 2025-06-02)Llega hasta 2144 (ver Inconsistencias): acotar el rango en los filtros
year / month / dayAño / Mes (número) / Día del mesinteger—
month_name(no publicar)Nombre del mestext en inglés (September)Para el nombre en castellano usar dim_mes_anio.descripcion_mes_anio
weekSemana ISOintegerSemana ISO: la del 1 de enero puede ser la 52/53 del año anterior
quarterTrimestreinteger 1–4—
day_of_weekDía de la semana1 = lunes … 7 = domingointeger—
id_mes_anioMes (ID)YYYYMMintegerJoin a dim_mes_anio
dim_mes_anio.id_mes_anioMes (ID)integer—
dim_mes_anio.descripcion_mes_anioMesEj. Septiembre 2026textOrdenar por id_mes_anio, no por el texto

11.2 ¿Qué fecha uso?

PreguntaFechaDónde
¿Cuánto se vendió en septiembre?sale_date (ART)todos los hechos
¿Cuántos viajan en septiembre (ventas futuras)?travel_date o departure_date_arfact_ticket(.mstr)
¿Cuántos pasajeros llevó el servicio del día X? (Planificación)manifest_datefact_ticket(.mstr), dim_manifest, fact_manifest_leg
¿Qué entró en la liquidación contable del día X?fecha_liquidacionfact_ticket(.mstr), fact_sale, fact_payment, fact_sold_item
12

Carga media de un servicio ("CM")

Pregunta abierta con Mariela Ramos: si "CM de un servicio" es carga media. Mientras se confirma, esto es lo que se puede calcular hoy con los datos existentes.

12.1 Qué hay

fact_manifest_leg tiene, para cada tramo entre dos paradas de un manifiesto, los pasajeros a bordo (load) y el % de ocupación (load_factor), además de suben / bajan. dim_manifest tiene la capacidad (bus_capacity) y el pico (peak_load). El km de cada tramo no está en fact_manifest_leg: hay que traerlo de dim_schedule_leg.distance_km. Ejemplo real (servicio Caleta Olivia → Resistencia, 20/09/2026, 58 asientos): arranca vacío, llega a 56 pasajeros entre Sierra Grande y Las Grutas, y baja a 16 después de Rosario.

12.2 Tres definiciones posibles

DefiniciónFórmula por manifiestoQué respondeEjemplo (servicio de arriba)
Pico (ya existe)dim_manifest.peak_load / peak_load_factor¿Cuánto se llenó en el peor tramo?56 pax (97%)
Carga media ponderada por km (recomendada)Σ(load × km_tramo) / Σ(km_tramo) = pasajeros-km / km recorridos¿Cuántos pasajeros llevó en promedio a lo largo del recorrido?31,0 pax
Factor de ocupación medioΣ(load × km_tramo) / (Σ km_tramo × bus_capacity)Mismo, en % de la capacidad53,4%
Carga media simpleAvg(load) sobre los tramosPromedio por parada (sesgado: una parada cada 5 km pesa lo mismo que un tramo de 180 km)30,3 pax
Pasajeros transportadosSum(pax_on)¿Cuántos pasajeros distintos subieron?122 pax

La carga media ponderada por km es la definición habitual en transporte de pasajeros (pasajeros-km sobre kilómetros recorridos). Si Planificación entiende "CM" como pasajeros transportados por servicio, la fórmula es Sum(pax_on), que ya está disponible sin nada extra.

Para un servicio en un período (ej. "el 13:00 Mendoza-Uspallata en septiembre"), agregar pasajeros-km y km de todos sus manifiestos y dividir al final (no promediar los promedios).

12.3 En Strategy: v_manifest_carga_media (desde 29/09/2026)

Implementado (migración carga_media_manifiesto): fact_manifest_leg.distance_km trae el km de cada tramo (paso 7b del incremental, unido por estaciones) y la vista bi_rebuild.v_manifest_carga_media da una fila por manifiesto, lista para Strategy sin joins:

ColumnaNombre en StrategyQué esAditiva
pax_kmPasajeros-kmΣ pasajeros a bordo × km de cada tramo con kmSí
km_con_datoKm con datoΣ km de los tramos con distancia conocidaSí
carga_media_kmCarga mediapax_km / km_con_datoNo
ocupacion_media_pctOcupación media (%)carga media / capacidad × 100No
carga_media_simpleCarga media simplePromedio de pasajeros a bordo por tramoNo
pico_pasajeros / ocupacion_pico_pctPico / Ocupación pico (%)Tramo más llenoNo
pasajeros_subidosPasajeros transportadosΣ pax_onSí
capacidadCapacidaddim_manifest.bus_capacity—
tramos / tramos_con_km / km_completo—Calidad del dato: km_completo = 0 si falta el km de algún tramo—
manifest_status, schedule_id, schedule_display_name, route_id, origin, destination, alcance_ruta, manifest_date—Atributos para filtrar y agrupar—

Métricas en Strategy para un servicio o un período (sumar primero, dividir al final; nunca promediar carga_media_km):

  • Carga media = Sum(pax_km) / Sum(km_con_dato)
  • Ocupación media % = 100 × Sum(pax_km) / Sum(km_con_dato × capacidad) (conviene exponer

km_con_dato × capacidad como hecho, o calcularlo en la vista del reporte)

  • Filtro por defecto: manifest_status = 'published'.

Validación: scripts/sql/bi_rebuild/operativo/validate_manifest_carga_media.sql. Backfill del histórico: scripts/sql/bi_rebuild/backfill/backfill_fact_manifest_leg_km.sql (aplicado 29/09).

12.4 Limitaciones medidas (29/09/2026)

  1. Km faltante. distance_km es NULL cuando Betterez informa el tramo en 0 (~17% en septiembre, ej. Rawson → Trelew) o el itinerario no está en dim_schedule_leg. Cobertura de tramos con km: ~78% en septiembre 2026, 58–65% antes de abril 2026 (itinerarios viejos menos cubiertos). En septiembre, de 7.839 manifiestos publicados: 4.462 con km completo, 3.037 parcial, 340 sin km. La carga media se pondera solo con los tramos que tienen km; para comparaciones finas, filtrar km_completo = 1.
  2. No unir por leg_ord: el orden del itinerario no coincide con el del manifiesto en ~12% de los tramos (la vista ya une por estaciones).
  3. load_factor viene redondeado a entero por Betterez: no promediarlo; usar la vista.
  4. Manifiestos no operativos: emptied, canceled, paused y Sin datos tienen carga 0 o parcial: filtrar published.
  5. Si Planificación entiende "CM" como pasajeros transportados, es Sum(pasajeros_subidos).
13

Objetos que no van a Strategy

ObjetoPor qué
Schema bi (plural: fact_tickets, fact_sales, v_tickets_detalle, …)Legacy, sin refresco desde 2026-04-15
etl_controlMarca de agua del ETL (last_run, lookback_interval)
stg_*, tmp_*Staging y tablas temporales de backfill
v_agency_catalog_driftControl de calidad del catálogo de agencias
aux_combustible, aux_consumo_axion, aux_flota_andesmar, aux_ypf_ruta, f_combustiblesTema combustible, con su propio runbook (scripts/combustible_sync/RUNBOOK_AUX_COMBUSTIBLE_SYNC.md); fuera del alcance de este diccionario
public.*Copia operativa de Betterez; los tableros no la leen
14

Inconsistencias encontradas

Relevadas el 29/09/2026 comparando las guías contra la base. La base manda sobre qué columnas y valores existen; las guías, sobre el significado.

#DóndeQué dice la guíaQué dice la base / el ETLEstado
1SCHEMA_FACT_TICKET_MSTR.md, COMMENT de successor_match_method3 valores (guía) / 4 valores (comentario)6 valores: heuristic_unique, api, same_webhook, heuristic_ambiguous, same_transaction, heuristic_disambiguatedCorregido en la guía; el COMMENT de la base queda desactualizado (se corrige por migración, no en esta tarea)
2INFORME_MAESTRO_BI_REBUILD.md §2.6sale_type incluye changeSólo sale, refund, cancellation: el enum de la API prevé change pero la ingesta no lo escribeCorregido
3GUIA_TABLERO_AGENCIAS_BI_REBUILD.md §3, INFORME_MAESTRO §2.5catalog_source = betterez_export | historical_onlybetterez_export y system_sentinel (fila de sin_agency_id)Corregido
4GUIA_TABLERO_AGENCIAS_BI_REBUILD.md §5.4 vs §2.1§5.4: "en tableros operativos, filtrar activas"; §2.1: "is_active no es filtro por defecto"—Contradicción interna: vale §2.1. Corregido §5.4
5SCHEMA_FACT_SOLD_ITEM.mdchannel_group = physical / onlinephysical, web, callcenterCorregido
6CONVENCIONES §4, SCHEMA_FACT_TICKET_MSTR.mdcurrency: ARS, CLP, USDTambién BOB (4.055 ventas)Corregido en CONVENCIONES
7CONVENCIONES §4.1 (ejemplo)route_id = MENDOZA_Uspallata Terminalroute_id es ObjectId de Betterez; el formato ORIGEN_DESTINO quedó en ~59.000 boletos viejos que no unen a dim_routeAnotado; ejemplo no corregido (es ilustrativo). Pendiente: remediar esos route_id
8CONVENCIONES §4dim_route.distance = "mediana histórica por par OD"Mediana de los itinerarios de la ruta; confiable sólo con distance_confiable = 1 (COMMENT de la base)Anotado
9SCHEMA_FACT_TICKET.mddistance = "km de la ruta (o del tramo si no hay ruta)"El ETL copia sólo dim_route.distanceCorregido
10TICKET_AMOUNT_OUTLIERS_REGLAS_MSTR.md, GUIA_TABLERO_METRICAS §3Revenue / IVA con effective_ticket_status = 'paid'GUIA_TABLERO_METRICAS §5 dice "no usar effective_ticket_status = 'paid' para conteos ejecutivos; usar is_paid"Contradicción entre guías: vale is_paid = 1. No corregido (afecta ejemplos SQL)
11GUIA_TABLERO_METRICAS §1, SCHEMA_FACT_TICKET_MSTR.md"Revenue pasajes" = Sum(ticket_amount)La plata oficial es Sum(fact_sale.net_sale) (ADR 29/09). ticket_amount es precio del boletoAnotado: renombrar la métrica a "Importe de pasajes" en Strategy
12SCHEMA_FACT_TICKET.md, CONVENCIONES §4tipo_servicio cae a Sin clasificacion sin flags125.750 boletos con tipo_servicio NULL además de 474.717 Sin clasificacionAnotado (revisar paso 10)
13alcance_ruta vs alcance_tramo—Uno dice Sin clasificacion, el otro Sin clasificarAnotado: unificar en Strategy con un alias
14dim_date"horizonte futuro"Llega a 2144-12-13 (43.659 días); month_name en inglésAnotado (bug conocido de extensión de dim_date)
15dim_manifest.trip_scopenacional / internacionalMinúsculas, sin provincial, NULL en 2.111 manifiestos de septiembreAnotado: usar alcance_ruta
16COMMENT de fact_ticket.is_child_fare"true cuando…"Es smallint 1/0Anotado
17GUIA_FORMA_DE_PAGO §4, INFORME_MAESTRO §2.6bfact_ticket_mstr "no tiene sale_id a propósito", no se puede llegar a la ventaSigue sin sale_id, pero desde el 28/09 tiene transaction_id, que permite ese joinAnotado
18CONVENCIONES §4pax_segment = 'courtesy' sin aclaración30% de los boletos de septiembre; casi todos "Cortesia 20%/30%", pagos con descuentoAnotado en este diccionario
19GUIA_FACT_MANIFEST_LEG_BI_REBUILD.mdSin mención de km por tramodim_schedule_leg se une por estaciones, no por leg_ordAnotado en §12
15

Propuesta de carpetas para Strategy

Propuesta para quien administra MSTR. Criterio: una carpeta por tema de negocio, adentro Atributos y Métricas separados, y el grano en el nombre de la métrica cuando puede confundirse.

Andesmar BI (bi_rebuild)
├── 01 Ventas y plata
│   ├── Atributos: Tipo de transacción, Estado de la venta, Canal de venta, Moneda
│   └── Métricas:  Venta neta, Venta bruta, Cantidad de transacciones, Cobrado neto
├── 02 Boletos
│   ├── Atributos: Estado del boleto, Tarifa, Segmento de pasajero, Tipo de producto,
│   │              Alcance del tramo, Origen / Destino del pasajero, Provincia / País
│   └── Métricas:  Boletos pagados, Boletos pagados (criterio Betterez web),
│                  Boletos cancelados, Importe de pasajes, IVA de pasajes, Km vendidos
├── 03 Forma de pago
│   ├── Atributos: Medio de pago, Categoría de pago, Grupo de pago, Pago combinado
│   └── Métricas:  Importe por medio de pago, Boletos por medio de pago, % pago combinado
├── 04 Seguros y menores de 3
│   ├── Atributos: Producto, Categoría de producto
│   └── Métricas:  Seguros vendidos, Recaudación seguros, Boletos menor de 3, Recaudación menor de 3,
│                  Percepción RG3819
├── 05 Servicios, manifiestos y ocupación
│   ├── Atributos: Manifiesto, Fecha del servicio, Hora de salida, Estado del manifiesto,
│   │              Origen / Destino del recorrido, Parada, Orden de la parada, Alcance de la ruta,
│   │              Tipo de servicio, Etiquetas (Línea, Refuerzo, La Cuyanita, ...), Vehículo, Chofer
│   └── Métricas:  Suben, Bajan, Pasajeros a bordo, Pico de pasajeros, Ocupación pico (%),
│                  Carga media, Ocupación media (%), Pasajeros transportados, Capacidad
├── 06 Agencias y canales
│   ├── Atributos: Agencia, Código de agencia, País de la agencia, Agencia activa,
│   │              Canal de venta, Grupo de canal
│   └── Métricas:  (las de 01 y 02, filtradas por agencia; no se duplican)
├── 07 Rutas e itinerarios
│   ├── Atributos: Itinerario, Ruta, Recorrido comercial, Transportista, Marca comercial,
│   │              Cabecera / Destino final
│   └── Métricas:  Km del itinerario (Max), Duración del itinerario
├── 08 Contable y liquidación SAP
│   ├── Atributos: N.º de liquidación SAP, Fecha de liquidación, Tipo de liquidación SAP,
│   │              Alícuota de IVA, Transportista
│   └── Métricas:  Importe liquidado, IVA liquidado
├── 09 Fechas
│   └── Atributos: Día, Semana, Mes, Trimestre, Año, Día de la semana
│                  (una jerarquía por fecha: de venta, de viaje, del servicio, de liquidación)
├── 90 Auditoría (no usar en tableros)
│   └── Total informado, Estado crudo, Método de match de manifiesto, Resolución de agencia, ...
└── 99 Obsoleto — schema bi (a borrar)

Convenciones de nombres sugeridas:

  1. En castellano, sin nombres técnicos visibles: "Venta neta", no net_sale.
  2. Métricas de dinero con la moneda en el filtro de la métrica y, si hay varias, en el nombre: "Venta neta (ARS)", "Venta neta (CLP)".
  3. Métricas de conteo con la unidad: "Boletos pagados", "Transacciones", "Ítems de seguro". Nunca "Ventas" a secas (no se sabe si son boletos o transacciones).
  4. Atributos de fecha con el calificativo: "Fecha de venta", "Fecha del servicio", "Fecha de viaje", "Fecha de liquidación". Nunca "Fecha" sola.
  5. Las banderas se muestran como Sí / No en el atributo (el dato es 1/0).
  6. Un solo nombre por concepto en todo el proyecto: si "Agencia" es agency_label, no hay otra "Agencia" armada con agency_name.
16

Mantenimiento

  • Al agregar una columna a bi_rebuild (siempre por migración Alembic), agregar su fila acá y en

la guía temática, con COMMENT ON COLUMN en la migración.

  • Verificación de columnas contra la base:
SELECT table_name, column_name, data_type,
       col_description(format('%I.%I', table_schema, table_name)::regclass, ordinal_position) AS comentario
FROM information_schema.columns
WHERE table_schema = 'bi_rebuild'
ORDER BY table_name, ordinal_position;

(glosario), GUIA_TABLERO_METRICAS_BI_REBUILD.md (índice de métricas), INFORME_MAESTRO_BI_REBUILD.md (capa técnica). Versión HTML: docs/presentaciones/diccionario-datos.html.