GAA1104 · Big Data e Inteligencia de Negocios · Clase 09

Insignia: el doble conteo

Identificar cuándo una planilla deja de servir, y ver con sus propias manos cómo la forma plana destruye el margen bruto que Finanzas le rinde al directorio —24,59 % real contra 14,90 % naive— antes de aprender la forma que lo arregla.

El notebook de esta clase: https://gaa1104.vercel.app/notebooks/clase-09.ipynb

Ing. en Administración · mención Finanzas · Prof. Tomás Fernández

Clase 09 · el gráfico de hoy

Dos barras, lado a lado

Toda la clase es una comparación de dos números: el margen que sale sumando plano contra el que sale bien calculado. La forma más simple posible, porque el hallazgo tiene que golpear solo.

La forma la decide el trabajo que hace el dato —magnitud, identidad, polaridad, cambio en el tiempo, distribución—, no el gusto. Regla del curso: un solo eje.

Apertura — Active recall · 8 min

Lo que quedó planteado la sesión anterior (se proyecta la pregunta, NO la respuesta):

«Todo lo del Parcial 1 salió de una planilla plana y limpia. ¿Por qué el margen todavía puede salir equivocado?»

🔨 Actividad — Las tres del recall

Entregas: las tres respuestas escritas en tu cuaderno. 90 segundos en silencio, 2 minutos con el de al lado, 3 de puesta en común.

Apertura — Active recall · 8 min

Contéstalas de memoria, sin abrir apuntes ni el notebook de la clase anterior (espaciado 1–3–7–21):

Apertura — Active recall · 8 min
  1. Sesión 12 · En el archivo del parcial, ¿qué representaba una fila: un documento o un producto? ¿Cómo lo supiste?
  2. Sesión 10 · ¿Cuáles son los cinco criterios de la matriz de decisión de herramienta?
  3. Sesión 06 · El DSO por segmento de Austral: Público 91,6 días · Construcción 72,5 · Minería 60,6 · Industrial 56,2 · Retail 55,7. ¿Cuál de esos números es capital de trabajo tuyo financiando a otro?
Apertura — Active recall · 8 min

Par (2 min) → plenaria (3 min).

La pregunta colgada se recoge, no se responde todavía. Se anotan en la pizarra las hipótesis que digan —van a decir "porque faltan datos", "porque el archivo estaba sucio", "porque el catálogo no pegaba". Todas razonables. Y entonces:

Apertura — Active recall · 8 min
«Ninguna de esas es la de hoy. Hoy el archivo va a estar perfecto: sin errores de escritura, sin fechas dadas vuelta, sin nada que reclamarle. Y el margen va a salir mal igual, por casi 10 puntos. A las 22:10 volvemos a esta pizarra.»
Retro del Parcial 1 · 12 min
Retro del Parcial 1 · 12 min
«EA1 está cerrada. Lo que empieza ahora es lo que separa a alguien que sabe consultar de alguien que sabe modelar. Y es lo que más se paga.»
Bloque 1 — El grano: ¿qué representa una fila? · 25 min

La pregunta que abre EA2, escrita en la pizarra y ahí se queda siete semanas:

¿Qué representa exactamente UNA fila de esta tabla?

👀 Se mira — una factura entera, con sus tres líneas

Esta consulta viene resuelta: la corres y miras la salida. Acá todavía no hay nada que escribir.

Bloque 1 — El grano: ¿qué representa una fila? · 25 min
-- 👀 SE MIRA · una factura entera, con sus tres líneas
SELECT numero_factura, cliente, fecha_emision, sku, linea_negocio,
       cantidad, precio_unitario, descuento_factura, flete_despacho
FROM ventas
WHERE numero_factura = 'F-2022-000023';

Tres filas. Tres productos distintos —una luminaria, un tornillo autoperforante, un set de llaves— y en las tres:

Bloque 1 — El grano: ¿qué representa una fila? · 25 min

Y la pregunta, contando hasta cinco antes de contestar: «¿la empresa cobró ese flete una vez o tres?»

Cuando alguien diga "una vez", el hallazgo es de esa persona. Nombrarla.

Bloque 1 — El grano: ¿qué representa una fila? · 25 min
Una fila no es una factura. Una fila es una LÍNEA de una factura. Y hay dos cosas viviendo aplastadas en la misma tabla: lo que pasa por producto (cantidad, precio, costo) y lo que pasa por documento (descuento, flete, condición de pago, cliente, fecha).

👀 Se mira — la verificación que autoriza usar MAX()

También viene resuelta. Es la primera verificación del semestre que autoriza una decisión de modelamiento: mírala correr antes de creerle al MAX().

Bloque 1 — El grano: ¿qué representa una fila? · 25 min
-- 👀 SE MIRA · ¿el cargo cambia entre las líneas de una misma factura?
SELECT numero_factura, COUNT(*) AS lineas,
       MIN(descuento_factura) AS desc_min, MAX(descuento_factura) AS desc_max,
       MIN(flete_despacho)    AS flete_min, MAX(flete_despacho)   AS flete_max
FROM ventas
GROUP BY 1
ORDER BY lineas DESC
LIMIT 5;
-- las facturas de 7 líneas repiten exactamente el mismo par de valores en las 7
Bloque 1 — El grano: ¿qué representa una fila? · 25 min
-- 👀 SE MIRA · cuántas facturas tienen el cargo inconsistente
SELECT COUNT(*) AS facturas_con_cargo_inconsistente
FROM (SELECT numero_factura FROM ventas
      GROUP BY 1
      HAVING MIN(descuento_factura) <> MAX(descuento_factura)
          OR MIN(flete_despacho)    <> MAX(flete_despacho));
-- 0
Bloque 1 — El grano: ¿qué representa una fila? · 25 min
«Cero. En ninguna de las 2.951 facturas el descuento cambia entre líneas. Ese cero es lo que me autoriza a usar MAX() para recuperar el cargo una sola vez. Si hubiera dado 12, tendríamos que ir a preguntar antes de tocar nada.»

Medidas vs atributos, la regla que se llevan escrita:

Bloque 1 — El grano: ¿qué representa una fila? · 25 min
Si te dan ganas de…Entonces es…Ejemplo en Austral
sumarlamedidacantidad, precio_unitario × cantidad, costo_unitario × cantidad
filtrar o agrupar por ellaatributosegmento, linea_negocio, canal, vendedor, region_cliente
sumarla y te da un número absurdoatributo disfrazado de númerotipo_cambio, condicion_pago_dias
Bloque 1 — El grano: ¿qué representa una fila? · 25 min

El dato del día, dicho al pasar: de las 2.951 facturas, 1.970 tienen más de una línea (66,8 %). El problema no es un caso raro: son dos de cada tres documentos.

Declarar en voz alta que el dataset es sintético. Los clientes no existen; el patrón de repetición es exactamente el que exporta un ERP real.

Respiro · 5 min

Pararse, estirar, subir la luz. Clase nocturna: el cuerpo también aprende. Y lo que viene es lo más importante del semestre.

Bloque 2 — La trampa, medida por ellos · 30 min

🔨 Actividad — Mide tú la trampa

Entregas: los cuatro números anotados a mano en tu cuaderno —2,60 · 2,04 · 14,90 · 24,59— más la celda final de este bloque completada con los que te dieron a ti. 30 minutos, cada uno en su máquina.

Nadie mira la pantalla del de al lado. Corres tú las consultas de este bloque, en orden, y anotas cada resultado a mano antes de pasar a la siguiente. Los tres pasos de abajo son los pasos de la actividad.

Bloque 2 — La trampa, medida por ellos · 30 min

1 · ¿Cuántas veces se cuenta cada cargo?

-- 🔨 ACTIVIDAD · córrela y anota los dos factores
WITH por_factura AS (
  SELECT numero_factura,
         MAX(descuento_factura) AS d,
         MAX(flete_despacho)    AS f
  FROM ventas
  WHERE moneda = 'CLP'
  GROUP BY 1
)
SELECT ROUND((SELECT SUM(descuento_factura) FROM ventas WHERE moneda = 'CLP')
             / (SELECT SUM(d) FROM por_factura), 2) AS factor_descuento,
       ROUND((SELECT SUM(flete_despacho) FROM ventas WHERE moneda = 'CLP')
             / (SELECT SUM(f) FROM por_factura), 2) AS factor_flete;
-- factor_descuento 2.60 · factor_flete 2.04
Bloque 2 — La trampa, medida por ellos · 30 min
«Sumar la columna descuento_factura cuenta el descuento comercial 2,60 veces. El flete, 2,04 veces. Nadie se equivocó al escribir la consulta: la consulta hizo exactamente lo que le pediste.»

En plata, para que se vea:

Bloque 2 — La trampa, medida por ellos · 30 min
CargoSumando la columna planaCobrado de verdad
Descuento comercial1.083.814.288416.964.602
Flete de despacho427.498.928209.502.530

(Se filtra moneda = 'CLP' a propósito: las 395 líneas en dólares no se suman con las de pesos. Ese es el reflejo que se instaló en la sesión 06 y no se pierde.)

Bloque 2 — La trampa, medida por ellos · 30 min

2 · El número de la noche

Se arma en tres pasos, y cada paso se proyecta entero. Paso 1: poner al lado de cada línea el total de su propia factura.

Bloque 2 — La trampa, medida por ellos · 30 min
-- 🔨 ACTIVIDAD · paso uno de tres
-- 1) Cada línea, con el total de SU factura al lado.
CREATE OR REPLACE VIEW linea_clp AS
SELECT numero_factura,
       cantidad * precio_unitario AS venta_bruta,
       cantidad * costo_unitario  AS costo_venta,
       descuento_factura, flete_despacho,
       SUM(cantidad * precio_unitario)
         OVER (PARTITION BY numero_factura) AS bruto_factura,
       COUNT(*)
         OVER (PARTITION BY numero_factura) AS lineas_factura
FROM ventas
WHERE moneda = 'CLP';
Bloque 2 — La trampa, medida por ellos · 30 min

Paso 2: la cuota — qué parte de los cargos le toca a cada línea.

Bloque 2 — La trampa, medida por ellos · 30 min
-- 🔨 ACTIVIDAD · paso dos de tres
-- 2) Qué parte de los cargos le toca a cada línea.
CREATE OR REPLACE VIEW reparto AS
SELECT *, CASE WHEN bruto_factura <> 0
               THEN venta_bruta / bruto_factura
               ELSE 1.0 / lineas_factura END AS cuota
FROM linea_clp;
Bloque 2 — La trampa, medida por ellos · 30 min

Paso 3: el margen de las dos maneras, uno al lado del otro.

Bloque 2 — La trampa, medida por ellos · 30 min
-- 🔨 ACTIVIDAD · paso tres de tres: anota los dos márgenes
-- 3) El número de la noche.
SELECT ROUND(SUM(venta_bruta)) AS venta_clp,
       ROUND(100.0 * (SUM(venta_bruta) - SUM(costo_venta)
         - SUM(descuento_factura) - SUM(flete_despacho))
         / SUM(venta_bruta), 2) AS margen_naive_pct,
       ROUND(100.0 * (SUM(venta_bruta) - SUM(costo_venta)
         - SUM((descuento_factura + flete_despacho) * cuota))
         / SUM(venta_bruta), 2) AS margen_real_pct
FROM reparto;
-- venta 9.129.005.786 · naive 14,90 % · real 24,59 %
Bloque 2 — La trampa, medida por ellos · 30 min

Silencio de tres segundos ante la pantalla. Y recién ahí, la pizarra:

## Margen bruto real 24,59 % · Margen bruto naive 14,90 %
## 9,7 puntos porcentuales — y en pesos: 884.846.084 de margen que sí existen y el informe no ve.
Bloque 2 — La trampa, medida por ellos · 30 min
«Ese número —el margen bruto por línea de negocio— es lo que Finanzas le rinde al directorio. Con 14,90 % en la lámina, la conversación es "hay que subir precios o cortar costos". Con 24,59 %, la conversación es otra completamente distinta. Y las dos salieron del mismo archivo, sin un solo error de tipeo.»
Bloque 2 — La trampa, medida por ellos · 30 min

Y ahora se dibuja, porque el número de la noche tiene que golpear solo. La consulta ya lo calculó; el gráfico es la forma más simple que existe —dos barras, lado a lado— y ésa es la elección, no un descuido: cuando el hallazgo es una comparación de dos números, cualquier cosa más elaborada lo tapa.

Bloque 2 — La trampa, medida por ellos · 30 min
# 👀 SE MIRA · el gráfico ya viene escrito: se corre y se mira
import matplotlib.pyplot as plt

GRIS, TINTA, ALERTA = "#b8b8b8", "#102a38", "#8c2b20"

m = bd.sql("""
  SELECT ROUND(100.0 * (SUM(venta_bruta) - SUM(costo_venta)
           - SUM(descuento_factura) - SUM(flete_despacho))
           / SUM(venta_bruta), 2) AS margen_naive_pct,
         ROUND(100.0 * (SUM(venta_bruta) - SUM(costo_venta)
           - SUM((descuento_factura + flete_despacho) * cuota))
           / SUM(venta_bruta), 2) AS margen_real_pct,
         ROUND(SUM(descuento_factura) + SUM(flete_despacho)
           - SUM((descuento_factura + flete_despacho) * cuota))
           AS margen_invisible_clp
  FROM reparto""").iloc[0]

fig, eje = plt.subplots(figsize=(7.2, 5))
eje.bar(["sumando la columna plana\n(naive)", "repartiendo a prorrata\n(real)"],
        [m.margen_naive_pct, m.margen_real_pct], color=[GRIS, TINTA], width=0.5)

for x, valor in enumerate([m.margen_naive_pct, m.margen_real_pct]):
    eje.text(x, valor + 0.4, f"{valor:.2f} %".replace(".", ","),
             ha="center", fontsize=14, color=TINTA)

eje.annotate("", xy=(1, m.margen_real_pct), xytext=(1, m.margen_naive_pct),
             arrowprops=dict(arrowstyle="<->", color=ALERTA, linewidth=1.6))
eje.text(0.68, (m.margen_real_pct + m.margen_naive_pct) / 2,
         f"{m.margen_real_pct - m.margen_naive_pct:.2f} pp".replace(".", ",")
         + "\n= CLP " + f"{m.margen_invisible_clp:,.0f}".replace(",", "."),
         ha="right", va="center", fontsize=11, color=ALERTA)

eje.set_title("El mismo archivo da 14,90 % o 24,59 % según dónde se sume\n"
              "el descuento: 9,69 puntos de margen que sí existen",
              fontsize=13, color=TINTA, loc="left")
eje.set_ylabel("margen bruto sobre la venta", fontsize=9)
eje.set_ylim(0, 30)
for lado in ("top", "right"):
    eje.spines[lado].set_visible(False)
fig.tight_layout()
plt.show()
Bloque 2 — La trampa, medida por ellos · 30 min

Qué se mira en el resultado:

Bloque 2 — La trampa, medida por ellos · 30 min
  1. La distancia entre las dos barras. No hay nada más en el gráfico, y es a propósito: 9,69 puntos porcentuales, que en pesos son los 884.846.084 que el informe plano no ve. El número sale de la misma consulta, no de una calculadora aparte.
  2. Que el eje parte en cero. Un eje truncado habría hecho ver la brecha tres veces más grande, y con eso el gráfico habría mentido diciendo la verdad. Es la trampa que se desarma completa en la clase 18; hoy basta con no caer en ella.
  3. Que ninguna de las dos barras está mal calculada. Las dos consultas corrieron sin error. La diferencia no es un bug: es una decisión de grano que alguien tomó sin darse cuenta de que la estaba tomando.
Bloque 2 — La trampa, medida por ellos · 30 min

3 · El crimen visto en una sola factura

Volver a F-2022-000023, la de las tres líneas, y repartir sus cargos (56.882 + 112.864 = 169.746) a prorrata de la venta de cada línea:

Bloque 2 — La trampa, medida por ellos · 30 min
LíneaVentaPeso en la facturaCargo que le tocaCargo que le asigna el método planoMargen realMargen naive
ELE-1004 · Eléctrico975.52036,8 %62.516169.74622,2 %11,2 %
FIJ-4003 · Fijaciones217.6648,2 %13.949169.74620,8 %−50,8 %
HER-2003 · Herramientas1.455.58855,0 %93.281169.74625,0 %19,8 %
Bloque 2 — La trampa, medida por ellos · 30 min
«Mira la fila del medio. Es un tornillo autoperforante que participa con el 8 % de esta factura, y el método plano le carga el descuento y el flete completos. Le da margen de menos 50,8 %. Un gerente que ve eso propone cerrar la línea de Fijaciones. La línea gana 20,8 % en esta factura.»

Y antes de cerrar el bloque, cada uno deja sus cuatro números escritos donde no se pierdan:

Bloque 2 — La trampa, medida por ellos · 30 min
# 🔨 ACTIVIDAD — deja acá los cuatro números que te dieron a TI.
# Anótalos también a mano en el cuaderno: eso es lo que se recoge hoy.
factor_descuento = 0.0   # cuántas veces se cuenta el descuento al sumar la columna plana
factor_flete     = 0.0   # lo mismo, para el flete de despacho
margen_naive_pct = 0.0   # el margen que sale sumando plano
margen_real_pct  = 0.0   # el margen que sale repartiendo a prorrata

print("Doble conteo — descuento:", factor_descuento, "· flete:", factor_flete)
print("Margen naive:", margen_naive_pct, "% · margen real:", margen_real_pct, "%")
Bloque 3 — La estrella: la tabla se parte en cinco · 20 min

La animación del deck, narrada lámina a lámina. Un clic, una dimensión.

Bloque 3 — La estrella: la tabla se parte en cinco · 20 min
Bloque 3 — La estrella: la tabla se parte en cinco · 20 min

En el centro queda fact_venta_linea: puras llaves y puros números. Larga y flaca.

«Un centro, cinco puntas. Y la razón por la que esto existe no es estética: es que cada cosa vive en la tabla que le corresponde a su grano. El descuento vivía en la tabla equivocada, y eso costó 9,7 puntos de margen.»
Bloque 3 — La estrella: la tabla se parte en cinco · 20 min
Hilo 3 — salir de Excel rápido. Esta partición en cinco tablas no tiene equivalente en una planilla. En Excel el modelo existe igual —tiene que existir, porque alguien decidió qué va en las filas y qué en los valores— pero vive en la cabeza del que armó la tabla dinámica. Cuando esa persona cambia de trabajo, el modelo se va con ella. Aquí el modelo son cinco CREATE TABLE que cualquiera puede leer, discutir y corregir.

Dónde vive esto en la organización (30 segundos, importa):

Bloque 3 — La estrella: la tabla se parte en cinco · 20 min
«Esto es el conflicto clásico del cierre mensual: Contabilidad dice un margen y Comercial dice otro, y los dos tienen razón en su planilla. Casi siempre es esto: están sumando a granos distintos. El área que puede explicarlo en una reunión es la que gobierna el número.»
El experto de negocio interno, síntesis y cierre · 20 min

El experto de esta sesión — Jefe de Administración de Ventas:

El experto de negocio interno, síntesis y cierre · 20 min
«Antes de repartir un solo peso, hay una pregunta que no está en el archivo: ¿el descuento se negocia sobre el total de la factura, o línea por línea?

Si es sobre el total —y en Austral lo es—, el reparto a prorrata es una decisión de asignación que tú tomas y declaras, no un dato. Si fuera línea por línea, la columna debería venir distinta y el proveedor del ERP tendría un problema que reportar.

De esa respuesta, y no de la consulta, depende cuál de los dos márgenes es el verdadero. La consulta la escribes en cuatro minutos. La conversación es la que decide si sirve.»
El experto de negocio interno, síntesis y cierre · 20 min

Y la regla del curso, otra vez en la pizarra: una consulta SQL sin una conversación previa es una opinión con formato de tabla.

Síntesis, construida con ellos (se pregunta y se escribe lo que ellos digan):

El experto de negocio interno, síntesis y cierre · 20 min
  1. «¿Qué representa una fila?» → una línea de factura, no una factura.
  2. «¿Por qué salió mal el margen si el archivo estaba limpio?» → porque dos granos vivían en la misma tabla.
  3. «¿Cuánto costó?» → 2,60× y 2,04×; 9,7 puntos; 884.846.084 pesos.
  4. «¿Y qué lo arregla?» → la forma. No un filtro, no un formato condicional: la forma.
El experto de negocio interno, síntesis y cierre · 20 min

Volver a la pizarra de las 20:30 y tachar las hipótesis del inicio. Ninguna era. La respuesta era el grano.

🎫 Se lleva — Ticket T13 (TC09 en la ruta de 24 clases)

Entregas: media hoja con las tres respuestas, antes de salir. 3 minutos, sin nota. Las mismas tres van también en la celda final del notebook, que se descarga y se sube a klearning.

a) ¿Qué representa una fila del libro de ventas de Austral, y por qué esa pregunta vale plata? b) SUM(descuento_factura) sobre la planilla plana da un número ¿menor o mayor que la verdad? ¿Por qué, y con qué dos factores? c) Del 1 al 5, ¿dónde te sientes hoy y qué te falta?

El experto de negocio interno, síntesis y cierre · 20 min

Y el anuncio: la clase de hoy queda filmada en el sitio del curso; revísenla cuando estudien, no cuando estén perdidos.

Preguntas que quedan planteadas (se escriben en la pizarra y no se responden):

El experto de negocio interno, síntesis y cierre · 20 min
→ El descuento se cuenta 2,60 veces y el flete 2,04. Si los dos se repiten en las mismas líneas de las mismas facturas, ¿por qué los factores son distintos?

→ Si el promedio de líneas por factura es 2,03, ¿por qué ninguno de los dos factores es exactamente 2,03?
Clase 09 · cierre

Ticket de salida

Tres minutos. No suma nota. Sirve para armar la apertura de la próxima clase.

El notebook de esta clase:
https://gaa1104.vercel.app/notebooks/clase-09.ipynb

← → avanzar · P proyector · F pantalla completa
GAA1104 · C09 · Insignia: el doble conteo 20:30 1 / 1