Sprint 3: Semana 2 - Joins y métricas financieras en SQL#

Propósito: usar SQL para calcular métricas financieras (ingresos, costos, margen, ROI), estructurar y comunicar resultados, y analizar tendencias.

Agenda#

  1. Review de conceptos clave de SQL (Joins, Agregaciones)

  2. Calculating Key Financial Metrics

    • Ingresos, Costos, Margen Bruto

    • ROI (Return on Investment)

    • Análisis de tendencias y comparaciones año a año

Relaciones entre tablas JOIN#

Para refrescar los conceptos de joins, repasemos las relaciones entre tablas en nuestra base de datos Chinook:

  1. Uno a Uno (1:1) 🔗 Una fila en una tabla se relaciona con una sola fila en otra tabla.

  2. Uno a Muchos (1:N) 🔗 Una fila en una tabla se relaciona con muchas filas en otra tabla.

  3. Muchos a Muchos (N:N) 🔗 Muchas filas en una tabla se relacionan con muchas filas en otra.

➡️ Con una tabla intermedia (tabla de asociación)

image.png

EN NUESTRA DATABASE CHINOOK,#

¿Puedes identificar los 3 tipos de relaciones?

¿Qué es un JOIN?#

Un JOIN permite combinar columnas de dos o más tablas relacionadas mediante una clave común (como CustomerId, ArtistId, etc.).

Tipo de JOIN

Qué hace

INNER JOIN

Devuelve solo las filas que coinciden en ambas tablas

LEFT JOIN

Devuelve todas las filas de la tabla izquierda, y las coincidentes (o NULL)

RIGHT JOIN

Devuelve todas las filas de la derecha (solo en bases que lo soportan)

FULL OUTER JOIN

Devuelve todas las filas, coincidan o no (SQLite no lo soporta directamente)

alt text

INNER JOIN#

Muestra el nombre de las canciones y el nombre del género al que pertenecen.

import sqlite3
import pandas as pd

# Hacemos la conexión a la Database
conn = sqlite3.connect("chinook.db")

query='''
SELECT
  t.Name AS cancion,
  g.Name AS genero
FROM track t
INNER JOIN genre g ON t.GenreId = g.GenreId;
'''
pd.read_sql(query,con=conn)
cancion genero
0 For Those About To Rock (We Salute You) Rock
1 Balls to the Wall Rock
2 Fast As a Shark Rock
3 Restless and Wild Rock
4 Princess of the Dawn Rock
... ... ...
3498 Pini Di Roma (Pinien Von Rom) \ I Pini Della V... Classical
3499 String Quartet No. 12 in C Minor, D. 703 "Quar... Classical
3500 L'orfeo, Act 3, Sinfonia (Orchestra) Classical
3501 Quintet for Horn, Violin, 2 Violas, and Cello ... Classical
3502 Koyaanisqatsi Soundtrack

3503 rows × 2 columns

LEFT JOIN#

Muestra todos los clientes y el total de sus compras (si las tienen).

query='''
SELECT
  c.CustomerId AS id_cliente,
  c.FirstName AS nombre,
  c.LastName AS apellido,
  i.Total AS total_compra
FROM customer c
LEFT JOIN invoice i ON c.CustomerId = i.CustomerId;

'''
pd.read_sql(query,con=conn)
id_cliente nombre apellido total_compra
0 1 Luís Gonçalves 3.98
1 1 Luís Gonçalves 3.96
2 1 Luís Gonçalves 5.94
3 1 Luís Gonçalves 0.99
4 1 Luís Gonçalves 1.98
... ... ... ... ...
407 59 Puja Srivastava 5.94
408 59 Puja Srivastava 1.99
409 59 Puja Srivastava 1.98
410 59 Puja Srivastava 13.86
411 59 Puja Srivastava 8.91

412 rows × 4 columns

INNER JOIN: canciones con su álbum#

Muestra el nombre de cada canción junto al título del álbum al que pertenece.

query='''
SELECT
  t.Name AS cancion,
  al.Title AS album
FROM track t
INNER JOIN album al ON t.AlbumId = al.AlbumId;
'''
pd.read_sql(query,con=conn)
cancion album
0 For Those About To Rock (We Salute You) For Those About To Rock We Salute You
1 Balls to the Wall Balls to the Wall
2 Fast As a Shark Restless and Wild
3 Restless and Wild Restless and Wild
4 Princess of the Dawn Restless and Wild
... ... ...
3498 Pini Di Roma (Pinien Von Rom) \ I Pini Della V... Respighi:Pines of Rome
3499 String Quartet No. 12 in C Minor, D. 703 "Quar... Schubert: The Late String Quartets & String Qu...
3500 L'orfeo, Act 3, Sinfonia (Orchestra) Monteverdi: L'Orfeo
3501 Quintet for Horn, Violin, 2 Violas, and Cello ... Mozart: Chamber Music
3502 Koyaanisqatsi Koyaanisqatsi (Soundtrack from the Motion Pict...

3503 rows × 2 columns

JOIN de tres tablas: factura + línea + canción#

Muestra el número de factura, el nombre de la canción comprada y el precio unitario.

query='''
SELECT
  i.InvoiceId,
  t.Name AS cancion,
  ii.UnitPrice
FROM invoiceLine ii
JOIN invoice i ON ii.InvoiceId = i.InvoiceId
JOIN track t ON ii.TrackId = t.TrackId;
'''
pd.read_sql(query,con=conn)
InvoiceId cancion UnitPrice
0 1 Balls to the Wall 0.99
1 1 Restless and Wild 0.99
2 2 Put The Finger On You 0.99
3 2 Inject The Venom 0.99
4 2 Evil Walks 0.99
... ... ... ...
2235 411 Looking For Love 0.99
2236 411 Sweet Lady Luck 0.99
2237 411 Feirinha da Pavuna/Luz do Repente/Bagaço da La... 0.99
2238 411 Samba pras moças 0.99
2239 412 Hot Girl 1.99

2240 rows × 3 columns

UNION VS UNIONALL#

Operador

Qué hace

UNION

Une los resultados y elimina duplicados

UNION ALL

Une los resultados y conserva todos los duplicados

📌 Regla importante: Ambas consultas deben tener el mismo número de columnas y tipos compatibles.

Ejemplo Correos de clientes y empleados (UNION ALL)#

Obtén todos los correos, incluyendo los que puedan repetirse.

query='''
SELECT Email FROM customer
UNION
SELECT Email FROM employee;
'''
pd.read_sql(query,con=conn)
Email
0 aaronmitchell@yahoo.ca
1 alero@uol.com.br
2 andrew@chinookcorp.com
3 astrid.gruber@apple.at
4 bjorn.hansen@yahoo.no
... ...
62 steve@chinookcorp.com
63 terhi.hamalainen@apple.fi
64 tgoyer@apple.com
65 vstevens@yahoo.com
66 wyatt.girard@yahoo.fr

67 rows × 1 columns

Ejemplo correos de clientes y empleados (UNION ALL)#

Obtén todos los correos, incluyendo los que puedan repetirse.

query='''
SELECT Email FROM customer
UNION ALL
SELECT Email FROM employee;
'''
pd.read_sql(query,con=conn)
Email
0 luisg@embraer.com.br
1 leonekohler@surfeu.de
2 ftremblay@gmail.com
3 bjorn.hansen@yahoo.no
4 frantisekw@jetbrains.com
... ...
62 margaret@chinookcorp.com
63 steve@chinookcorp.com
64 michael@chinookcorp.com
65 robert@chinookcorp.com
66 laura@chinookcorp.com

67 rows × 1 columns

Ejercicios en clase:#

#Empleados que atienden más clientes
query='''
SELECT
    e.EmployeeId,
    e.FirstName,
    e.LastName,
    COUNT(c.CustomerId) AS num_customers
FROM Employee AS e
JOIN Customer AS c
    ON c.SupportRepId = e.EmployeeId
GROUP BY
    e.EmployeeId,
    e.FirstName,
    e.LastName
ORDER BY num_customers DESC;
'''
pd.read_sql(query,con=conn)
EmployeeId FirstName LastName num_customers
0 3 Jane Peacock 21
1 4 Margaret Park 20
2 5 Steve Johnson 18
# 3. Artistas con más álbumes
query = '''
SELECT
    ar.ArtistId,
    ar.Name AS ArtistName,
    COUNT(al.AlbumId) AS num_albums
FROM Artist AS ar
JOIN Album AS al
    ON al.ArtistId = ar.ArtistId
GROUP BY
    ar.ArtistId,
    ar.Name
ORDER BY num_albums DESC
LIMIT 10;
'''
pd.read_sql(query, con=conn)
ArtistId ArtistName num_albums
0 90 Iron Maiden 21
1 22 Led Zeppelin 14
2 58 Deep Purple 11
3 50 Metallica 10
4 150 U2 10
5 114 Ozzy Osbourne 6
6 118 Pearl Jam 5
7 21 Various Artists 4
8 82 Faith No More 4
9 84 Foo Fighters 4
# 4. Géneros que generan más ventas
query = '''
SELECT
    g.GenreId,
    g.Name AS GenreName,
    SUM(il.UnitPrice * il.Quantity) AS revenue
FROM Genre AS g
JOIN Track AS t
    ON t.GenreId = g.GenreId
JOIN InvoiceLine AS il
    ON il.TrackId = t.TrackId
GROUP BY
    g.GenreId,
    g.Name
ORDER BY revenue DESC;
'''
pd.read_sql(query, con=conn)
GenreId GenreName revenue
0 1 Rock 826.65
1 7 Latin 382.14
2 3 Metal 261.36
3 4 Alternative & Punk 241.56
4 19 TV Shows 93.53
5 2 Jazz 79.20
6 6 Blues 60.39
7 21 Drama 57.71
8 14 R&B/Soul 40.59
9 24 Classical 40.59
10 20 Sci Fi & Fantasy 39.80
11 8 Reggae 29.70
12 9 Pop 27.72
13 10 Soundtrack 19.80
14 22 Comedy 17.91
15 17 Hip Hop/Rap 16.83
16 11 Bossa Nova 14.85
17 23 Alternative 13.86
18 16 World 12.87
19 18 Science Fiction 11.94
20 13 Heavy Metal 11.88
21 15 Electronica/Dance 11.88
22 12 Easy Listening 9.90
23 5 Rock And Roll 5.94
# 5. Clientes que más gastan
query = '''
SELECT
    c.CustomerId,
    c.FirstName,
    c.LastName,
    c.Country,
    SUM(i.Total) AS total_spent
FROM Customer AS c
JOIN Invoice AS i
    ON i.CustomerId = c.CustomerId
GROUP BY
    c.CustomerId,
    c.FirstName,
    c.LastName,
    c.Country
ORDER BY total_spent DESC;
'''
pd.read_sql(query, con=conn)
CustomerId FirstName LastName Country total_spent
0 6 Helena Holý Czech Republic 49.62
1 26 Richard Cunningham USA 47.62
2 57 Luis Rojas Chile 46.62
3 45 Ladislav Kovács Hungary 45.62
4 46 Hugh O'Reilly Ireland 45.62
5 24 Frank Ralston USA 43.62
6 28 Julia Barnett USA 43.62
7 37 Fynn Zimmermann Germany 43.62
8 7 Astrid Gruber Austria 42.62
9 25 Victor Stevens USA 42.62
10 44 Terhi Hämäläinen Finland 41.62
11 5 František Wichterlová Czech Republic 40.62
12 43 Isabelle Mercier France 40.62
13 48 Johannes Van der Berg Netherlands 40.62
14 1 Luís Gonçalves Brazil 39.62
15 3 François Tremblay Canada 39.62
16 4 Bjørn Hansen Norway 39.62
17 17 Jack Smith USA 39.62
18 20 Dan Miller USA 39.62
19 22 Heather Leacock USA 39.62
20 34 João Fernandes Portugal 39.62
21 42 Wyatt Girard France 39.62
22 15 Jennifer Peterson Canada 38.62
23 19 Tim Goyer USA 38.62
24 39 Camille Bernard France 38.62
25 40 Dominique Lefebvre France 38.62
26 51 Joakim Johansson Sweden 38.62
27 58 Manoj Pareek India 38.62
28 2 Leonie Köhler Germany 37.62
29 8 Daan Peeters Belgium 37.62
30 9 Kara Nielsen Denmark 37.62
31 10 Eduardo Martins Brazil 37.62
32 11 Alexandre Rocha Brazil 37.62
33 12 Roberto Almeida Brazil 37.62
34 13 Fernanda Ramos Brazil 37.62
35 14 Mark Philips Canada 37.62
36 16 Frank Harris USA 37.62
37 18 Michelle Brooks USA 37.62
38 21 Kathy Chase USA 37.62
39 23 John Gordon USA 37.62
40 27 Patrick Gray USA 37.62
41 29 Robert Brown Canada 37.62
42 30 Edward Francis Canada 37.62
43 31 Martha Silk Canada 37.62
44 32 Aaron Mitchell Canada 37.62
45 33 Ellie Sullivan Canada 37.62
46 35 Madalena Sampaio Portugal 37.62
47 36 Hannah Schneider Germany 37.62
48 38 Niklas Schröder Germany 37.62
49 41 Marc Dubois France 37.62
50 47 Lucas Mancini Italy 37.62
51 49 Stanisław Wójcik Poland 37.62
52 50 Enrique Muñoz Spain 37.62
53 52 Emma Jones United Kingdom 37.62
54 53 Phil Hughes United Kingdom 37.62
55 54 Steve Murray United Kingdom 37.62
56 55 Mark Taylor Australia 37.62
57 56 Diego Gutiérrez Argentina 37.62
58 59 Puja Srivastava India 36.64
# 6. Empleados que han vendido más (facturas)
query = '''
SELECT
    e.EmployeeId,
    e.FirstName,
    e.LastName,
    SUM(i.Total) AS total_sales
FROM Employee AS e
JOIN Customer AS c
    ON c.SupportRepId = e.EmployeeId
JOIN Invoice AS i
    ON i.CustomerId = c.CustomerId
GROUP BY
    e.EmployeeId,
    e.FirstName,
    e.LastName
ORDER BY total_sales DESC;
'''
pd.read_sql(query, con=conn)
EmployeeId FirstName LastName total_sales
0 3 Jane Peacock 833.04
1 4 Margaret Park 775.40
2 5 Steve Johnson 720.16
# 7. Álbumes más vendidos
query = '''
SELECT
    al.AlbumId,
    al.Title,
    COUNT(il.InvoiceLineId) AS num_items,
    SUM(il.Quantity) AS total_tracks_sold
FROM Album AS al
JOIN Track AS t
    ON t.AlbumId = al.AlbumId
JOIN InvoiceLine AS il
    ON il.TrackId = t.TrackId
GROUP BY
    al.AlbumId,
    al.Title
ORDER BY total_tracks_sold DESC;
'''
pd.read_sql(query, con=conn)
AlbumId Title num_items total_tracks_sold
0 23 Minha Historia 27 27
1 141 Greatest Hits 26 26
2 73 Unplugged 25 25
3 224 Acústico 22 22
4 37 Greatest Kiss 20 20
... ... ... ... ...
299 335 J.S. Bach: Chaconne, Suite in E Minor, Partita... 1 1
300 337 Szymanowski: Piano Works, Vol. 1 1 1
301 338 Nielsen: The Six Symphonies 1 1
302 340 Liszt - 12 Études D'Execution Transcendante 1 1
303 343 Respighi:Pines of Rome 1 1

304 rows × 4 columns

# 8a. Número de canciones por playlist
query = '''
SELECT
    pl.PlaylistId,
    pl.Name AS PlaylistName,
    COUNT(pt.TrackId) AS num_tracks
FROM Playlist AS pl
JOIN PlaylistTrack AS pt
    ON pt.PlaylistId = pl.PlaylistId
GROUP BY
    pl.PlaylistId,
    pl.Name
ORDER BY num_tracks DESC;
'''
pd.read_sql(query, con=conn)
PlaylistId PlaylistName num_tracks
0 1 Music 3290
1 8 Music 3290
2 5 90’s Music 1477
3 3 TV Shows 213
4 10 TV Shows 213
5 12 Classical 75
6 11 Brazilian Music 39
7 17 Heavy Metal Classic 26
8 13 Classical 101 - Deep Cuts 25
9 14 Classical 101 - Next Steps 25
10 15 Classical 101 - The Basics 25
11 16 Grunge 15
12 9 Music Videos 1
13 18 On-The-Go 1 1
# 8b. Géneros por playlist
query = '''
SELECT
    pl.PlaylistId,
    pl.Name AS PlaylistName,
    g.Name AS GenreName,
    COUNT(t.TrackId) AS num_tracks
FROM Playlist AS pl
JOIN PlaylistTrack AS pt
    ON pt.PlaylistId = pl.PlaylistId
JOIN Track AS t
    ON t.TrackId = pt.TrackId
JOIN Genre AS g
    ON g.GenreId = t.GenreId
GROUP BY
    pl.PlaylistId,
    pl.Name,
    g.Name
ORDER BY
    pl.Name,
    num_tracks DESC;
'''
pd.read_sql(query, con=conn)
PlaylistId PlaylistName GenreName num_tracks
0 5 90’s Music Rock 621
1 5 90’s Music Latin 257
2 5 90’s Music Alternative & Punk 193
3 5 90’s Music Metal 164
4 5 90’s Music Classical 40
... ... ... ... ...
77 10 TV Shows Sci Fi & Fantasy 26
78 3 TV Shows Comedy 17
79 10 TV Shows Comedy 17
80 3 TV Shows Science Fiction 13
81 10 TV Shows Science Fiction 13

82 rows × 4 columns

# 9. Clientes que compraron canciones de un artista específico
query = '''
SELECT DISTINCT
    c.CustomerId,
    c.FirstName,
    c.LastName,
    c.Email,
    c.Country
FROM Customer AS c
JOIN Invoice AS i
    ON i.CustomerId = c.CustomerId
JOIN InvoiceLine AS il
    ON il.InvoiceId = i.InvoiceId
JOIN Track AS t
    ON t.TrackId = il.TrackId
JOIN Album AS al
    ON al.AlbumId = t.AlbumId
JOIN Artist AS ar
    ON ar.ArtistId = al.ArtistId
WHERE ar.Name = 'AC/DC'
ORDER BY c.Country, c.LastName;
'''
pd.read_sql(query, con=conn)
CustomerId FirstName LastName Email Country
0 8 Daan Peeters daan_peeters@apple.be Belgium
1 13 Fernanda Ramos fernadaramos4@uol.com.br Brazil
2 33 Ellie Sullivan ellie.sullivan@shaw.ca Canada
3 47 Lucas Mancini lucas.mancini@yahoo.it Italy
4 4 Bjørn Hansen bjorn.hansen@yahoo.no Norway
5 53 Phil Hughes phil.hughes@gmail.com United Kingdom

KPIs de negocio#

KPIs de Negocio: Ingresos, Costos, Margen Bruto, ROI y Análisis de Tendencias

1. Ingresos, Costos y Margen Bruto

1.1 Ingresos (Revenue)

Los ingresos son el valor total de las ventas en un periodo. Es el dinero que entra antes de restar ningún tipo de costo.

Fórmula básica de ingresos:
Revenue = Precio de venta × Cantidad vendida

Ejemplo:

  • Precio por unidad: 50 USD
  • Cantidad vendida en el mes: 500 unidades

Cálculo:
Revenue(Ingresos) = 50 × 500 = 25 000 USD

1.2 Costos

Los costos son todos los gastos necesarios para producir u ofrecer un producto o servicio.

Costos fijos
No cambian según cuántas unidades se produzcan o vendan.
(Ej.: alquiler, salarios administrativos, seguros, servicios generales).
Costos variables
Cambian según el volumen producido o vendido.
(Ej.: materia prima, empaques, comisiones por venta).
Costo Total (visión global de empresa)
Costo Total = Costos Fijos + Costos Variables

Esta fórmula describe los costos operativos generales de la empresa.
Útil para analizar eficiencia, punto de equilibrio y rentabilidad global.

Importante:
El cálculo de Margen Bruto no utiliza los costos fijos ni todos los costos variables.
Para Margen Bruto solo usamos los costos directos de producir lo que se vendió, también conocidos como COGS (Cost of Goods Sold).

Piensa en COGS como “el costo directo por unidad vendida”, no como el costo total de la empresa.

Margen Bruto: conectando ingresos con costos directos (COGS)

El Margen Bruto mide qué tan rentable es el producto en sí, antes de considerar otros costos operativos como marketing, administración, logística, etc.

Para calcular el Margen Bruto NO usamos el costo total de la empresa.
Usamos únicamente los costos directos de producir lo que vendimos.

Estos costos directos se conocen como COGS (Cost of Goods Sold).

¿Qué es exactamente COGS?

COGS incluye únicamente los costos necesarios para que el producto exista y pueda ser vendido. No incluye marketing, administración ni gastos fijos generales de la empresa.

COGS incluye:
• Materia prima
• Mano de obra directa
• Costo de adquisición del inventario
• Ensamble, empaque directo
• Costos directos por unidad vendida
COGS NO incluye:
• Marketing y publicidad
• Sueldos administrativos
• Alquiler de oficinas
• Servicios generales
• Tecnología, contabilidad, impuestos

En resumen:
COGS se enfoca solo en los costos directos.
No representa todos los costos de la empresa.

Fórmula 1: Utilidad Bruta
Utilidad Bruta = Ingresos − COGS
Fórmula 2: Margen Bruto (%)
Margen Bruto (%) = (Utilidad Bruta / Ingresos) × 100

Ejemplo simple

  • Ingresos: 10,000 USD
  • COGS: 6,000 USD (solo costos directos)

Utilidad Bruta:
10,000 − 6,000 = 4,000 USD

Margen Bruto (%):
(4,000 ÷ 10,000) × 100 = 40%

Interpretación:
Un margen bruto del 40% significa que por cada 1 USD vendido, la empresa obtiene 0.40 USD después de cubrir los costos directos.

Este margen es clave para entender si un producto es rentable y cuánto “aire” queda para cubrir el resto de los gastos (operativos, administrativos, financieros, etc.).

1. ROI (Return on Investment)

El ROI mide qué tan rentable fue una inversión respecto al dinero que se invirtió. Es una métrica clave para decidir si un proyecto, campaña o iniciativa valió la pena.

Fórmula de ROI:
ROI = (Ganancia de la inversión − Monto invertido) ÷ Monto invertido
ROI (%) = ROI × 100

Ejemplo:

  • Inversión en campaña de marketing: 5 000 USD
  • Ingresos generados por esa campaña: 9 000 USD

Paso 1. Calcular la ganancia de la inversión:
Ganancia = 9 000 − 5 000 = 4 000 USD

Paso 2. Calcular ROI:
ROI = 4 000 ÷ 5 000 = 0.8
ROI (%) = 0.8 × 100 = 80 %

Interpretación:
Un ROI de 80 % significa que por cada 1 USD invertido la empresa ganó 0.80 USD adicionales.

3. Análisis de tendencias y comparaciones año a año (YoY)

El análisis de tendencias permite ver cómo evolucionan los KPIs a lo largo del tiempo (mes a mes, trimestre a trimestre, año a año).

3.1 Crecimiento porcentual entre periodos

Se usa para ver cuánto creció o decreció un KPI respecto a un periodo anterior.

Fórmula de crecimiento:
Crecimiento (%) = (Valor actual − Valor anterior) ÷ Valor anterior × 100

Ejemplo (ingresos mensuales):

Mes Ingresos (USD)
Enero 20 000
Febrero 24 000

Cálculo del crecimiento de Febrero vs Enero:
Crecimiento (%) = (24 000 − 20 000) ÷ 20 000 × 100 = 20 %

Interpretación:
Los ingresos crecieron 20 % de enero a febrero.

3.2 Comparación año a año (YoY: Year over Year)

La comparación YoY contrasta el mismo periodo en años distintos (por ejemplo, ingresos de Q1 2024 vs Q1 2023) para ver si el negocio está creciendo o no.

Fórmula YoY:
YoY (%) = (Valor actual − Valor mismo periodo año anterior) ÷ Valor año anterior × 100

Ejemplo (ingresos anuales):

Año Ingresos (USD)
2023 200 000
2024 230 000

Cálculo YoY:
YoY (%) = (230 000 − 200 000) ÷ 200 000 × 100 = 15 %

Interpretación:
El negocio creció un 15 % en ingresos respecto al año anterior. Este tipo de métrica es clave para evaluar la salud y el crecimiento de la empresa en el tiempo.

3.3 Conexión entre KPIs

En la práctica, un analista no mira cada KPI aislado, sino en conjunto:

  • Los ingresos muestran tamaño del negocio.
  • Los costos y el margen bruto muestran eficiencia operativa.
  • El ROI muestra si las inversiones específicas están funcionando.
  • Las tendencias y YoY muestran si el negocio mejora, se estanca o empeora.

Afianzando conceptos clave (con respuestas ocultas)

Usa esta sección para comprobar tu comprensión sobre Ingresos, COGS, Costos Totales, Margen Bruto, ROI y análisis de tendencias. Haz clic en los botones para mostrar las respuestas.

1. Preguntas conceptuales

  1. ¿Qué diferencia existe entre COGS y Costos Totales?
    Piensa en costos directos vs costos de operación.
    Ver respuesta
    COGS incluye solo los costos directos de producir lo que se vendió (materia prima, mano de obra directa, costo de inventario, etc.).
    Los Costos Totales incluyen COGS más todos los gastos operativos: administración, marketing, alquiler, tecnología, etc.
  2. ¿Por qué el Margen Bruto no utiliza costos fijos ni todos los costos variables?
    Ver respuesta
    Porque el Margen Bruto mide la rentabilidad del producto, no de toda la empresa. Por eso solo descuenta los costos directos (COGS) asociados a producir lo que se vendió, y deja fuera los costos operativos generales (fijos o variables) como marketing, administración, etc.
  3. ¿Qué significa que un producto tenga un margen bruto del 60%?
    Ver respuesta
    Que por cada 1 unidad de moneda vendida (por ejemplo, 1 USD), la empresa se queda con 0.60 después de cubrir los costos directos de producción (COGS). Ese 60% es lo que queda disponible para cubrir otros gastos y generar utilidad neta.
  4. ¿El ROI analiza la rentabilidad de un producto o de una inversión?
    Ver respuesta
    El ROI analiza la rentabilidad de una inversión específica (una campaña, un proyecto, una iniciativa), comparando cuánto se ganó respecto a lo que se invirtió.
  5. ¿Qué información aporta comparar ingresos mes a mes (MoM) vs año a año (YoY)?
    Ver respuesta
    MoM (Month over Month) muestra variaciones de corto plazo entre meses consecutivos.
    YoY (Year over Year) compara el mismo periodo entre años distintos (ej. enero 2024 vs enero 2023) y sirve para ver crecimiento estructural, evitando efectos estacionales.

2. Mini ejercicios

Ejercicio A — Cálculo rápido de COGS
Vendiste 12 unidades de un producto cuyo costo unitario es 45 USD.
¿Cuál es el COGS?
Ver respuesta
COGS = 12 × 45 = 540 USD
Ejercicio B — Ingresos
Vendiste 3 productos:
• 2 unidades a 80 USD
• 1 unidad a 120 USD
¿Cuál es el total de ingresos?
Ver respuesta
Ingresos = (2 × 80) + (1 × 120) = 160 + 120 = 280 USD
Ejercicio C — Margen Bruto
Ingresos: 5,000 USD
COGS: 3,000 USD
¿Cuál es la utilidad bruta y el margen bruto (%)?
Ver respuesta
Utilidad Bruta = 5,000 − 3,000 = 2,000 USD
Margen Bruto (%) = (2,000 ÷ 5,000) × 100 = 40%
Ejercicio D — ROI
Inversión en campaña: 2,000 USD
Ingresos obtenidos: 5,500 USD
¿Cuál es el ROI (%)?
Ver respuesta
Ganancia de la inversión = 5,500 − 2,000 = 3,500 USD
ROI = 3,500 ÷ 2,000 = 1.75175 %
(Por cada 1 USD invertido se ganaron 1.75 USD adicionales).

3. Verdadero / Falso

Intenta responder primero y luego despliega las soluciones.

Enunciado Tu respuesta Solución
COGS incluye costos de marketing. V / F
Ver Falso. El marketing es un gasto operativo, no un costo directo del producto.
El margen bruto es un indicador de rentabilidad del producto. V / F
Ver Verdadero. Mide cuánta ganancia deja el producto después de cubrir los costos directos (COGS).
El ROI mide cuánto ganó una empresa respecto a una inversión puntual. V / F
Ver Verdadero. El ROI se centra en una inversión específica (campaña, proyecto, etc.).
Los costos fijos cambian según cuántas unidades se produzcan. V / F
Ver Falso. Los costos fijos no dependen directamente del volumen producido (ej. alquiler, sueldos administrativos).
El análisis YoY compara periodos del mismo mes o trimestre entre años. V / F
Ver Verdadero. YoY compara el mismo periodo en años diferentes para evaluar crecimiento real.

4. Completa los espacios

  1. Ingresos se calculan como: ________ × ________.
    Ver respuesta
    Ingresos = Precio de venta × Cantidad vendida
  2. COGS representa los costos __________ asociados a producir lo vendido.
    Ver respuesta
    directos. Costos directos de producción o adquisición del producto.
  3. Margen Bruto = (Ingresos − ________) ÷ ________ × 100.
    Ver respuesta
    Margen Bruto (%) = (Ingresos − COGS) ÷ Ingresos × 100
  4. El ROI compara la ganancia obtenida con el monto _________.
    Ver respuesta
    invertido.
  5. Un análisis YoY compara el mismo periodo entre __________ distintos.
    Ver respuesta
    años.

5. Mini reto final

Una empresa vende 4 productos distintos durante un mes. Resumen del mes:

• Ingresos totales: 18,400 USD
• COGS total: 11,200 USD
• Inversión en campaña: 2,500 USD

Pregunta 1: ¿Cuál es el margen bruto (%)?
Pregunta 2: ¿Cuál es el ROI de la campaña?

Tip: primero calcula la utilidad bruta, luego aplica las fórmulas.

Ver respuestas
1) Margen Bruto
Utilidad Bruta = 18,400 − 11,200 = 7,200 USD
Margen Bruto (%) = (7,200 ÷ 18,400) × 100 ≈ 39.1 %

2) ROI de la campaña
Si usamos la definición básica de ROI vista en clase (ingresos asociados − inversión) / inversión:
Ganancia de la inversión = 18,400 − 2,500 = 15,900 USD
ROI ≈ 15,900 ÷ 2,500 = 6.36 → 636 %

Nota: en un análisis más avanzado podríamos usar la utilidad bruta en lugar de los ingresos para un ROI “más conservador”. En ese caso:
Ganancia = 7,200 − 2,500 = 4,700 → ROI ≈ 4,700 ÷ 2,500 = 188 %.

Mini Base de Datos Relacional para Practicar KPIs de Negocio

Esta base de datos está creda para practicar consultas SQL enfocadas en KPIs clave como Ingresos, Costos, Margen Bruto, ROI y Análisis de Tendencias.

  • Consultas con JOIN, GROUP BY, filtros y agregaciones.
  • Cálculo de Ingresos, Costos, Margen Bruto.
  • Cálculo de ROI por campaña o proyecto.
  • Análisis de tendencias mensuales y comparaciones año a año (YoY).

1. Esquema general (tablas y relaciones)

Tablas principales:
  • customers – información de clientes.
  • products – catálogo de productos, con precio y costo unitario.
  • orders – encabezado de pedidos (fecha, cliente, campaña).
  • order_items – detalle de productos vendidos en cada pedido.
  • campaigns – campañas de marketing e inversión asociada (para ROI).

Relaciones clave (modelo estrella sencillo):

customers (1) ──── (N) orders (1) ──── (N) order_items (N) ──── (1) products

campaigns (1) ──── (N) orders

2. Tablas y campos

2.1 Tabla customers

Información básica de clientes para segmentar y analizar por país o tipo de cliente.

Campo Tipo Descripción
customer_id INT (PK) Identificador único del cliente.
customer_name VARCHAR Nombre del cliente.
country VARCHAR País de residencia.
segment VARCHAR Segmento (e.g. Retail, Enterprise, Online).

2.2 Tabla products

Define productos, precios de venta y costos unitarios para calcular margen bruto.

Campo Tipo Descripción
product_id INT (PK) Identificador único del producto.
product_name VARCHAR Nombre del producto.
category VARCHAR Categoría (e.g. Software, Hardware, Servicio).
unit_price DECIMAL Precio de venta por unidad.
unit_cost DECIMAL Costo de producción por unidad (para COGS).

2.3 Tabla campaigns

Campañas de marketing o proyectos de inversión para calcular ROI.

Campo Tipo Descripción
campaign_id INT (PK) Identificador único de la campaña.
campaign_name VARCHAR Nombre de la campaña.
start_date DATE Fecha de inicio.
end_date DATE Fecha de cierre.
channel VARCHAR Canal (Email, Ads, Social Media, etc.).
investment_amount DECIMAL Monto invertido en la campaña (para ROI).

2.4 Tabla orders

Encabezado de cada pedido / orden de venta.

Campo Tipo Descripción
order_id INT (PK) Identificador único del pedido.
order_date DATE Fecha del pedido (sirve para tendencias y YoY).
customer_id INT (FK → customers) Cliente que realizó la compra.
campaign_id INT (FK → campaigns, NULL) Campaña que originó la venta (si aplica).

2.5 Tabla order_items

Detalle de líneas de cada pedido (productos, cantidades, precios). Aquí se calculan ingresos y costos.

Campo Tipo Descripción
order_item_id INT (PK) Identificador único de la línea del pedido.
order_id INT (FK → orders) Pedido al que pertenece la línea.
product_id INT (FK → products) Producto vendido.
quantity INT Cantidad vendida.
unit_price DECIMAL Precio de venta aplicado (permite simular descuentos).

Ejercicios Demostrativos JOIN SQL - KPIs de Negocio#

# 1. Ingresos totales de la empresa

# Hacemos la conexión a la Database
conn = sqlite3.connect("Marketing_salesDb.db")

query = '''
SELECT
    SUM(unit_price * quantity) AS total_revenue
FROM order_items;
'''
pd.read_sql(query, con=conn)
total_revenue
0 472440
# 2. COGS total (costo directo total)
query = '''
SELECT
    SUM(p.unit_cost * oi.quantity) AS total_cogs
FROM order_items AS oi
JOIN products AS p
    ON oi.product_id = p.product_id;
'''
pd.read_sql(query, con=conn)
total_cogs
0 190380
# 3. Margen bruto total (%)
query = '''
SELECT
    SUM(oi.unit_price * oi.quantity) AS total_revenue,
    SUM(p.unit_cost * oi.quantity)   AS total_cogs,
    SUM(oi.unit_price * oi.quantity)
      - SUM(p.unit_cost * oi.quantity) AS gross_profit,
    (
      (SUM(oi.unit_price * oi.quantity)
        - SUM(p.unit_cost * oi.quantity))
      / SUM(oi.unit_price * oi.quantity) * 100
    ) AS gross_margin_pct
FROM order_items AS oi
JOIN products AS p
    ON oi.product_id = p.product_id;
'''
pd.read_sql(query, con=conn)
total_revenue total_cogs gross_profit gross_margin_pct
0 472440 190380 282060 0
# 4. Ingresos por mes (año + mes)
query = '''
SELECT
    strftime('%Y', o.order_date)  AS year,
    strftime('%m', o.order_date) AS month,
    SUM(oi.unit_price * oi.quantity) AS monthly_revenue
FROM orders AS o
JOIN order_items AS oi
    ON o.order_id = oi.order_id
GROUP BY
    strftime('%Y', o.order_date),
    strftime('%m', o.order_date)
ORDER BY
    strftime('%Y', o.order_date),
    strftime('%m', o.order_date);
'''
pd.read_sql(query, con=conn)
year month monthly_revenue
0 2024 01 32605
1 2024 02 6640
2 2024 03 32300
3 2024 04 38400
4 2024 05 3900
5 2024 06 24125
6 2024 07 34880
7 2024 08 63400
8 2024 09 30150
9 2024 10 2810
10 2024 11 6500
11 2024 12 32610
12 2025 01 38300
13 2025 02 3850
14 2025 03 40400
15 2025 04 18620
16 2025 05 62950

Sesión práctica: SQL + JOINS + KPIs de Negocio - Ingresos, COGS, Margen Bruto, ROI y Análisis de Tendencias#

# 6. Top 5 clientes por ingresos
query = '''
SELECT
    c.customer_id,
    c.customer_name,
    c.country,
    SUM(oi.unit_price * oi.quantity) AS total_revenue
FROM customers AS c
JOIN orders AS o
    ON c.customer_id = o.customer_id
JOIN order_items AS oi
    ON o.order_id = oi.order_id
GROUP BY
    c.customer_id,
    c.customer_name,
    c.country
ORDER BY
    total_revenue DESC
LIMIT 5;
'''
pd.read_sql(query, con=conn)
customer_id customer_name country total_revenue
0 5 Pedro Ruiz Argentina 101350
1 10 Gabriela Ortiz España 63400
2 4 Ana Torres España 50920
3 15 Daniel Cuevas Colombia 38300
4 1 Juan Pérez Colombia 34005
# 7. Margen bruto por cliente (%)
query = '''
SELECT
    c.customer_id,
    c.customer_name,
    c.country,
    SUM(oi.unit_price * oi.quantity) AS revenue,
    SUM(p.unit_cost * oi.quantity)   AS cogs,
    SUM(oi.unit_price * oi.quantity)
      - SUM(p.unit_cost * oi.quantity) AS gross_profit,
    (
      (SUM(oi.unit_price * oi.quantity)
        - SUM(p.unit_cost * oi.quantity))
      / SUM(oi.unit_price * oi.quantity) * 100
    ) AS gross_margin_pct
FROM customers AS c
JOIN orders AS o
    ON c.customer_id = o.customer_id
JOIN order_items AS oi
    ON o.order_id = oi.order_id
JOIN products AS p
    ON oi.product_id = p.product_id
GROUP BY
    c.customer_id,
    c.customer_name,
    c.country
ORDER BY
    gross_margin_pct DESC;
'''
pd.read_sql(query, con=conn)
customer_id customer_name country revenue cogs gross_profit gross_margin_pct
0 1 Juan Pérez Colombia 34005 21605 12400 0
1 2 María Gómez México 31540 19625 11915 0
2 3 Luis Hernández USA 17950 4400 13550 0
3 4 Ana Torres España 50920 30385 20535 0
4 5 Pedro Ruiz Argentina 101350 19650 81700 0
5 6 Sofía Delgado Chile 3900 1560 2340 0
6 7 Carlos Silva Brasil 24125 15350 8775 0
7 8 Julia Castro Colombia 16400 3840 12560 0
8 9 Diego López USA 18480 10270 8210 0
9 10 Gabriela Ortiz España 63400 12650 50750 0
10 11 Ricardo Molina México 30150 20060 10090 0
11 12 Laura Castillo Chile 2810 1500 1310 0
12 13 Samuel Méndez Argentina 6500 2725 3775 0
13 14 Paula Salinas Brasil 32610 20370 12240 0
14 15 Daniel Cuevas Colombia 38300 6390 31910 0
# 8. Ingresos por país (top 5)
query = '''
SELECT
    c.country,
    SUM(oi.unit_price * oi.quantity) AS revenue_by_country
FROM customers AS c
JOIN orders AS o
    ON c.customer_id = o.customer_id
JOIN order_items AS oi
    ON o.order_id = oi.order_id
GROUP BY
    c.country
ORDER BY
    revenue_by_country DESC
LIMIT 5;
'''
pd.read_sql(query, con=conn)
country revenue_by_country
0 España 114320
1 Argentina 107850
2 Colombia 88705
3 México 61690
4 Brasil 56735
# 9. Margen bruto por categoría de producto
query = '''
SELECT
    p.category,
    SUM(oi.unit_price * oi.quantity) AS revenue,
    SUM(p.unit_cost * oi.quantity)   AS cogs,
    (SUM(oi.unit_price * oi.quantity) - SUM(p.unit_cost * oi.quantity)) 
        AS gross_profit,
    ROUND(
      (SUM(oi.unit_price * oi.quantity) - SUM(p.unit_cost * oi.quantity))
      * 1.0 / NULLIF(SUM(oi.unit_price * oi.quantity), 0) * 100
    , 2) AS gross_margin_pct
FROM products AS p
JOIN order_items AS oi
    ON p.product_id = oi.product_id
JOIN orders AS o
    ON oi.order_id = o.order_id
GROUP BY
    p.category
ORDER BY
    gross_margin_pct DESC;

'''
pd.read_sql(query, con=conn)
category revenue cogs gross_profit gross_margin_pct
0 Software 204300 38300 166000 81.25
1 Servicios 30400 6080 24320 80.00
2 Accesorios 23080 9700 13380 57.97
3 Hardware 214660 136300 78360 36.50
# 10. Ingresos atribuibles a cada campaña
query = '''
SELECT
    p.category,
    SUM(oi.unit_price * oi.quantity) AS revenue,
    SUM(p.unit_cost * oi.quantity)   AS cogs,
    (SUM(oi.unit_price * oi.quantity) - SUM(p.unit_cost * oi.quantity)) 
        AS gross_profit,
    ROUND(
      (SUM(oi.unit_price * oi.quantity) - SUM(p.unit_cost * oi.quantity))
      * 1.0 / NULLIF(SUM(oi.unit_price * oi.quantity), 0) * 100
    , 2) AS gross_margin_pct
FROM products AS p
JOIN order_items AS oi
    ON p.product_id = oi.product_id
JOIN orders AS o
    ON oi.order_id = o.order_id
GROUP BY
    p.category
ORDER BY
    gross_margin_pct DESC;

'''
pd.read_sql(query, con=conn)
category revenue cogs gross_profit gross_margin_pct
0 Software 204300 38300 166000 81.25
1 Servicios 30400 6080 24320 80.00
2 Accesorios 23080 9700 13380 57.97
3 Hardware 214660 136300 78360 36.50
# 11. ROI por campaña
query = '''
SELECT
    c.campaign_id,
    c.campaign_name,
    c.channel,
    c.investment_amount,
    SUM(oi.unit_price * oi.quantity) AS revenue_from_campaign,
    (SUM(oi.unit_price * oi.quantity) - c.investment_amount) AS profit_from_campaign,
    ((SUM(oi.unit_price * oi.quantity) - c.investment_amount)
        / c.investment_amount * 100) AS roi_pct
FROM campaigns AS c
LEFT JOIN orders AS o
    ON c.campaign_id = o.campaign_id
LEFT JOIN order_items AS oi
    ON o.order_id = oi.order_id
GROUP BY
    c.campaign_id,
    c.campaign_name,
    c.channel,
    c.investment_amount
ORDER BY
    roi_pct DESC;
'''
pd.read_sql(query, con=conn)
campaign_id campaign_name channel investment_amount revenue_from_campaign profit_from_campaign roi_pct
0 3 Campaña Back to School Social Media 5000 98280 93280 1800
1 2 Campaña Navidad Email 7000 70910 63910 900
2 4 Campaña Primavera Ads 6000 40400 34400 500
3 5 Campaña Verano SEO 4000 24125 20125 500
4 1 Campaña Black Friday Ads 10000 9310 -690 0
# 12. Comparación de ingresos por año (2024 vs 2025)
query = '''
SELECT
    strftime('%Y', o.order_date) AS year,
    SUM(oi.unit_price * oi.quantity) AS revenue
FROM orders AS o
JOIN order_items AS oi
    ON o.order_id = oi.order_id
GROUP BY
    strftime('%Y', o.order_date)
ORDER BY
    year;
'''
pd.read_sql(query, con=conn)
year revenue
0 2024 308320
1 2025 164120
# 13b. Ticket promedio por cliente
query = '''
SELECT
    c.customer_id,
    c.customer_name,
    SUM(oi.unit_price * oi.quantity) AS total_revenue,
    COUNT(DISTINCT o.order_id)       AS num_orders,
    SUM(oi.unit_price * oi.quantity) 
        / COUNT(DISTINCT o.order_id) AS avg_order_value_per_customer
FROM customers AS c
JOIN orders AS o
    ON c.customer_id = o.customer_id
JOIN order_items AS oi
    ON o.order_id = oi.order_id
GROUP BY
    c.customer_id,
    c.customer_name
ORDER BY
    avg_order_value_per_customer DESC;
'''
pd.read_sql(query, con=conn)
customer_id customer_name total_revenue num_orders avg_order_value_per_customer
0 10 Gabriela Ortiz 63400 1 63400
1 5 Pedro Ruiz 101350 2 50675
2 15 Daniel Cuevas 38300 1 38300
3 14 Paula Salinas 32610 1 32610
4 11 Ricardo Molina 30150 1 30150
5 4 Ana Torres 50920 2 25460
6 7 Carlos Silva 24125 1 24125
7 9 Diego López 18480 1 18480
8 1 Juan Pérez 34005 2 17002
9 8 Julia Castro 16400 1 16400
10 2 María Gómez 31540 2 15770
11 3 Luis Hernández 17950 2 8975
12 13 Samuel Méndez 6500 1 6500
13 6 Sofía Delgado 3900 1 3900
14 12 Laura Castillo 2810 1 2810
# 14. Top 10 productos más vendidos (por cantidad)
query = '''
SELECT
    p.product_id,
    p.product_name,
    p.category,
    SUM(oi.quantity) AS total_quantity_sold
FROM products AS p
JOIN order_items AS oi
    ON p.product_id = oi.product_id
JOIN orders AS o
    ON oi.order_id = o.order_id
GROUP BY
    p.product_id,
    p.product_name,
    p.category
ORDER BY
    total_quantity_sold DESC
LIMIT 10;
'''
pd.read_sql(query, con=conn)
product_id product_name category total_quantity_sold
0 5 Software CRM Pro Software 256
1 10 Licencia Analytics Software 255
2 9 Router WiFi 6 Hardware 205
3 4 Monitor 27" Hardware 203
4 3 Teclado Mecánico Accesorios 156
5 8 Servicio de Soporte Servicios 152
6 2 Mouse Inalámbrico Accesorios 112
7 7 Tablet 10" Hardware 104
8 1 Laptop Pro 15 Hardware 55
9 6 Audífonos Pro Accesorios 52
# 15. Número de clientes por campaña
query = '''
SELECT
    c.campaign_id,
    c.campaign_name,
    c.channel,
    COUNT(DISTINCT o.customer_id) AS num_customers_acquired
FROM campaigns AS c
LEFT JOIN orders AS o
    ON c.campaign_id = o.campaign_id
GROUP BY
    c.campaign_id,
    c.campaign_name,
    c.channel
ORDER BY
    num_customers_acquired DESC;
'''
pd.read_sql(query, con=conn)
campaign_id campaign_name channel num_customers_acquired
0 3 Campaña Back to School Social Media 3
1 1 Campaña Black Friday Ads 2
2 2 Campaña Navidad Email 2
3 4 Campaña Primavera Ads 2
4 5 Campaña Verano SEO 1

Cierre#

  • Exporta una tabla final , genera una visualización sencilla (gráfico de barras o línea) y compártela por el chat de la sesión.

Siguientes Pasos#

  • Próxima sesión: Practica con tus compañeros - revisión requisitos para el proyecto del sprint 3.

  • Participación continua: asistir a Co-Learning y a Sprint Focus, y usar los canales de Discord para hacer preguntas.

  • Recordatorios: la grabación y recursos utilizados, se comparten al finalizar la sesión; en caso de necesitar apoyo adicional, agenda un 1:1.