Capítulo 3 de 23 12 secciones 18 min

Compartir

SELECT, WHERE y ORDER BY

Pedir, filtrar y ordenar, con la primera diferencia gorda entre los cuatro motores.

SELECT elige las columnas, FROM la tabla, WHERE filtra las filas y ORDER BY las ordena. Para traer solo unas cuantas, PostgreSQL, MySQL y SQLite usan LIMIT, y SQL Server usa TOP, que además va antes de las columnas. Y lo que a mí me ordenó la cabeza fue entender que el orden en que escribes una consulta no es el orden en que el motor la ejecuta. Casi todos los errores del principio salen de ahí.

Hola! Aquí empieza lo bueno

Con lo de este capítulo ya puedes responder el 60% de lo que te van a pedir en un trabajo. En serio 💜

Así que párate un segundo y piensa: ¿cuál es la pregunta que te piden todas las semanas en tu trabajo? Esa misma, con lo de este capítulo, la vas a poder contestar tú sin pedírsela a nadie 💪

El orden en que se escribe y el orden en que corre

Esto va primero porque explica casi todos los errores del principio.

Se escribe así:

SELECT   columnas
FROM     tabla
WHERE    filtro
ORDER BY orden
LIMIT    cuántas

Pero el motor lo ejecuta en otro orden: primero FROM (de dónde), después WHERE (qué filas), después SELECT (qué columnas), después ORDER BY y al final LIMIT.

De ahí sale una consecuencia práctica: en el WHERE no deberías usar un alias que creaste en el SELECT, porque cuando el WHERE corre, el SELECT todavía no pasó.

Y aquí tenemos el primer caso de este libro donde SQLite te miente por ser amable. Mira:

SELECT id, monto * 1.18 AS con_igv
FROM pedidos
WHERE con_igv > 1000
LIMIT 3;
id  con_igv
--  ------------------
1   1052.6308
12  1001.5957999999998
14  1344.8814

Funcionó. Y esa es exactamente la trampa 😬

SQLite es permisivo y te deja usar el alias. Los otros tres no, porque siguen el orden de ejecución al pie de la letra: en PostgreSQL, MySQL y SQL Server esa consulta te dice que la columna con_igv no existe.

Así que una consulta que aquí corre perfecto revienta en cuanto la lleves al trabajo. Es justo el tipo de cosa por la que este libro marca los cuatro motores 💜

Se arregla repitiendo el cálculo en el WHERE:

La forma que viaja a los cuatro es repetir el cálculo en el WHERE:

SELECT id, monto, monto * 1.18 AS con_igv
FROM pedidos
WHERE monto * 1.18 > 1000
ORDER BY con_igv DESC
LIMIT 3;
id   monto    con_igv
---  -------  ------------------
25   1399.98  1651.9764
674  1372.34  1619.3611999999998
373  1341.15  1582.557

Y en el ORDER BY sí funciona el alias en los cuatro motores, porque el ORDER BY corre después del SELECT. Ahí no hay discusión 🙂

Limitar filas: la diferencia que más muerde

Traer solo las 5 primeras

PostgreSQLSELECT * FROM pedidos ORDER BY monto DESC LIMIT 5;
MySQLSELECT * FROM pedidos ORDER BY monto DESC LIMIT 5;
SQL ServerSELECT TOP 5 * FROM pedidos ORDER BY monto DESC;
SQLiteSELECT * FROM pedidos ORDER BY monto DESC LIMIT 5;

El TOP de SQL Server va pegado al SELECT, antes de las columnas. Si vienes de MySQL y escribes LIMIT, el error que sale no te ayuda nada.

Saltarse las 10 primeras y traer las 5 siguientes (paginar)

PostgreSQLSELECT * FROM pedidos ORDER BY id LIMIT 5 OFFSET 10;
MySQLSELECT * FROM pedidos ORDER BY id LIMIT 10, 5;
SQL ServerSELECT * FROM pedidos ORDER BY id OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY;
SQLiteSELECT * FROM pedidos ORDER BY id LIMIT 5 OFFSET 10;

MySQL acepta las dos formas, pero en su atajo el orden se invierte: primero cuántas saltas y después cuántas traes. Es un clásico para equivocarse.

Y una regla que vale para los cuatro: limitar sin ordenar no garantiza nada. Sin ORDER BY, "las 5 primeras" son las 5 que al motor le dé la gana devolver, y puede cambiar entre ejecuciones.

Árbol para elegir el filtro en SQL según contra qué comparas: un valor con igual o distinto, una lista con IN, un rango con BETWEEN, un trozo de texto con LIKE, y lo que puede faltar con IS NULL, nunca con igual a NULL.
Los cuatro primeros se eligen por comodidad y dan el mismo resultado. El último no: comparar un NULL con igual no devuelve falso, devuelve desconocido, y esa fila desaparece del reporte.

Filtrar con WHERE

SELECT id, canal, monto
FROM pedidos
WHERE monto > 1500
ORDER BY monto DESC
LIMIT 5;
id  canal  monto
--  -----  -----

Los operadores son los que esperas y funcionan igual en los cuatro: =, <> o != para distinto, >, <, >=, <=.

AND, OR y los paréntesis que cambian el resultado

SELECT COUNT(*) AS sin_parentesis
FROM pedidos
WHERE canal = 'Web' OR canal = 'Tienda' AND monto > 1000;
sin_parentesis
--------------
246
SELECT COUNT(*) AS con_parentesis
FROM pedidos
WHERE (canal = 'Web' OR canal = 'Tienda') AND monto > 1000;
con_parentesis
--------------
31

Números distintos, y la consulta no dio error en ninguno de los dos casos 😳

El motivo: AND se evalúa antes que OR, como la multiplicación antes que la suma. Así que el primero significa "todo lo de Web, más lo de Tienda que pase de 1000".

La regla que uso siempre: si mezclas AND con OR, pon paréntesis aunque creas que no hacen falta. Cuestan nada y evitan un reporte equivocado que nadie va a notar.

IN, BETWEEN y LIKE

SELECT COUNT(*) AS pedidos
FROM pedidos
WHERE canal IN ('Web', 'WhatsApp');
pedidos
-------
465
SELECT COUNT(*) AS del_primer_trimestre
FROM pedidos
WHERE fecha BETWEEN '2025-01-01' AND '2025-03-31';
del_primer_trimestre
--------------------
155

BETWEEN incluye los dos extremos, y ahí está su trampa: con fechas que llevan hora, BETWEEN '2025-01-01' AND '2025-03-31' se come todo el 31 de marzo salvo las horas posteriores a medianoche. Por eso en producción se escribe >= inicio AND < día_siguiente.

SELECT nombre FROM clientes
WHERE nombre LIKE 'Bodega%'
LIMIT 4;
nombre
---------------------
Bodega La Esquina 005
Bodega San Martin 030
Bodega San Martin 034
Bodega La Esquina 036

El % es "lo que sea, incluido nada", y el _ es exactamente un carácter.

Buscar texto sin importar mayúsculas

PostgreSQLWHERE nombre ILIKE '%bodega%'
MySQLWHERE nombre LIKE '%bodega%' -- ya ignora mayúsculas por defecto
SQL ServerWHERE nombre LIKE '%bodega%' -- depende del collation de la base
SQLiteWHERE LOWER(nombre) LIKE '%bodega%'

Esta es de las peores, porque el mismo LIKE se comporta distinto en cada motor y ninguno te avisa. Si escribes LOWER() en los dos lados, funciona igual en los cuatro y no dependes de la configuración.

Los nulos, que no se comparan con igual

SELECT COUNT(*) AS con_igual
FROM direcciones
WHERE es_principal = NULL;
con_igual
---------
0

Cero. Y no porque no haya nulos, sino porque nada es igual a NULL, ni siquiera otro NULL. NULL significa "no se sabe", y dos cosas que no se saben no se puede decir que sean iguales.

SELECT COUNT(*) AS con_is_null
FROM direcciones
WHERE es_principal IS NULL;
con_is_null
-----------
0

Se usa IS NULL y IS NOT NULL, y eso es igual en los cuatro motores. El capítulo 4 va entero sobre esto porque da para mucho más.

Ordenar

SELECT nombre, ciudad
FROM clientes
ORDER BY ciudad ASC, nombre DESC
LIMIT 5;
nombre                      ciudad
--------------------------  --------
Restaurante Miraflores 056  Arequipa
Minimarket El Sol 109       Arequipa
Minimarket Aurora 041       Arequipa
Market Central 104          Arequipa
Distribuidora Paz 095       Arequipa

ASC es ascendente y es el que se usa si no dices nada; DESC es al revés. Puedes ordenar por varias columnas, y se aplican en orden.

Dónde quedan los nulos al ordenar

PostgreSQLORDER BY monto DESC NULLS LAST -- controlable
MySQLORDER BY monto DESC -- los nulos van al final
SQL ServerORDER BY monto DESC -- los nulos van al final
SQLiteORDER BY monto DESC -- los nulos van al final

Ordenando de mayor a menor, PostgreSQL pone los nulos primero y los otros tres al final. Si tu top 10 empieza con filas vacías, es esto.

Un caso completo

SELECT c.nombre, c.ciudad, p.fecha, p.canal, p.monto
FROM pedidos p
JOIN clientes c ON c.id = p.id_cliente
WHERE p.monto > 1200
  AND p.canal IN ('Web', 'Marketplace')
  AND c.ciudad = 'Lima'
ORDER BY p.monto DESC
LIMIT 5;
nombre                      ciudad  fecha       canal  monto
--------------------------  ------  ----------  -----  -------
Restaurante Miraflores 009  Lima    2026-01-19  Web    1210.11

Ese JOIN lo vemos entero en el capítulo 7; aquí solo quería que vieras cómo se lee una consulta de trabajo real. Y el alias de tabla (p, c) es lo que la hace legible cuando hay dos o más 🌟

El paréntesis que vale 215 pedidos

Este es el error más caro de todo el capítulo, y no da error 😖

Quieres los pedidos grandes de dos canales concretos. Lo escribes como se dice en voz alta:

SELECT COUNT(*)
FROM pedidos
WHERE canal = 'Web' OR canal = 'Tienda' AND monto > 1000;
COUNT(*)
--------
246

246 pedidos. Suena razonable, lo pones en el informe y te vas a comer 🥗

Ahora con dos paréntesis:

SELECT COUNT(*)
FROM pedidos
WHERE (canal = 'Web' OR canal = 'Tienda') AND monto > 1000;
COUNT(*)
--------
31

31. Ocho veces menos, con las mismas palabras y sin una sola queja del motor 😱

Lo que pasa es que AND se evalúa antes que OR, igual que la multiplicación va antes que la suma. Así que la primera consulta dice de verdad:

Todos los de Web, más los de Tienda que pasen de mil.

Y tú querías decir:

Los de Web o Tienda, siempre que pasen de mil.

Los 246 son los 234 de Web enteros más 12 de Tienda. El filtro del monto solo se aplicó a la mitad de la consulta 🫠

La regla, y no tiene excepciones: si en un WHERE hay un OR y un AND juntos, van paréntesis. Aunque sepas la precedencia. No es por ti, es por quien lea la consulta dentro de seis meses, que puede ser tú.

Tres filtros que ahorran mucho paréntesis

Justo por lo de arriba, cuanto menos OR escribas, mejor. Estos tres lo evitan casi siempre 🎯

IN, para una lista de valores

SELECT COUNT(*) AS solo_esos_dos
FROM pedidos
WHERE canal IN ('Web', 'Tienda');
solo_esos_dos
-------------
440

440, que es exactamente lo que daba el OR de arriba sin el filtro del monto. Y ahora añadir el monto ya no tiene trampa:

SELECT COUNT(*) AS los_grandes_de_esos_dos
FROM pedidos
WHERE canal IN ('Web', 'Tienda')
  AND monto > 1000;
los_grandes_de_esos_dos
-----------------------
31

31, los mismos que con paréntesis 🙌

Esa es la mejor razón para usar IN: no es que sea más corto, es que no se puede escribir mal. El paréntesis viene incluido.

BETWEEN, para un rango

SELECT COUNT(*) AS con_between
FROM pedidos
WHERE monto BETWEEN 500 AND 1000;
con_between
-----------
485
SELECT COUNT(*) AS a_mano
FROM pedidos
WHERE monto >= 500 AND monto <= 1000;
a_mano
------
485

El mismo número, y ahí está el detalle que hay que retener: BETWEEN incluye los dos extremos 📏

Con dinero da igual, pero con fechas muerde. BETWEEN '2026-01-01' AND '2026-01-31' deja fuera todo lo que pasó el 31 después de medianoche si la columna guarda también la hora. Por eso con fechas y hora prefiero >= el primero AND < el primero del mes siguiente, que no tiene ese borde.

LIKE, para buscar dentro del texto

SELECT COUNT(*) AS empiezan_con_a
FROM clientes
WHERE nombre LIKE 'A%';
empiezan_con_a
--------------
15

El % significa "lo que sea, incluso nada". Va al final para "empieza por", al principio para "termina en", y a los dos lados para "contiene en algún sitio" 🔎

Y el aviso que no se dice nunca: LIKE '%algo%', con el comodín delante, no puede usar un índice. Con cien filas ni te enteras; con diez millones, esa consulta es la que tiene al equipo esperando. Lo desarrollo en el capítulo 14.

Ejercicios

1. Los pedidos grandes de un canal

Trae los 5 pedidos de WhatsApp con mayor monto.

SELECT id, fecha, monto
FROM pedidos
WHERE canal = 'WhatsApp'
ORDER BY monto DESC
LIMIT 5;
id   fecha       monto
---  ----------  -------
25   2025-01-25  1399.98
440  2025-11-02  1322.73
780  2025-03-25  1310.96
878  2026-04-17  1209.79
53   2026-05-21  1144.55
2. Escríbelo en SQL Server

La misma consulta anterior, en T-SQL.

SELECT TOP 5 id, fecha, monto
FROM pedidos
WHERE canal = 'WhatsApp'
ORDER BY monto DESC;

El TOP va pegado al SELECT y desaparece el LIMIT. Todo lo demás, idéntico.

Y ahora corre eso mismo contra tienda.db, que es SQLite, para que veas de qué hablamos cuando decimos que hay cuatro SQL.

SELECT TOP 5 id, fecha, monto
FROM pedidos
ORDER BY monto DESC;
OperationalError: near "5": syntax error

Ese syntax error no significa que escribiste mal: significa que escribiste bien, pero en otro idioma. Es el error número uno de quien aprende con un motor y llega a una empresa que usa otro 💛

3. Los paréntesis que faltaban

Cuenta los pedidos de Web o Tienda que además pasen de S/800, escrito bien.

SELECT COUNT(*) AS pedidos
FROM pedidos
WHERE (canal = 'Web' OR canal = 'Tienda')
  AND monto > 800;
pedidos
-------
97

Sin los paréntesis el resultado sale distinto y no hay error. Es el tipo de fallo que llega a una presentación.

4. Un rango de fechas bien escrito

Cuenta los pedidos de enero de 2026, sin usar BETWEEN.

SELECT COUNT(*) AS pedidos_enero
FROM pedidos
WHERE fecha >= '2026-01-01' AND fecha < '2026-02-01';
pedidos_enero
-------------
51

Mayor o igual al primero, menor al primero del mes siguiente. Así funciona igual lleve hora o no, y no tienes que acordarte de cuántos días tiene el mes.

5. Buscar por texto

Encuentra los clientes cuyo nombre contenga "Norte".

SELECT nombre, ciudad
FROM clientes
WHERE nombre LIKE '%Norte%'
LIMIT 5;
nombre                      ciudad
--------------------------  --------
Autoservicio Norte 007      Chiclayo
Autoservicio Norte 008      Chiclayo
Autoservicio Norte 022      Piura
Autoservicio Norte 028      Trujillo
  AUTOSERVICIO NORTE 032    Piura
6. El alias en el WHERE

Usa un alias del SELECT dentro del WHERE. Va a funcionar, y esa es la trampa. Escribe también la versión que sí viaja a los otros tres.

SELECT id, monto * 0.18 AS igv
FROM pedidos
WHERE igv > 200
LIMIT 3;
id  igv
--  --------
14  205.1514
21  200.502
25  251.9964

En SQLite corre. En PostgreSQL, MySQL y SQL Server sale un error diciendo que la columna igv no existe. La versión que viaja repite el cálculo:

SELECT id, monto * 0.18 AS igv
FROM pedidos
WHERE monto * 0.18 > 200
LIMIT 3;
id  igv
--  --------
14  205.1514
21  200.502
25  251.9964

Misma salida, y esta sí corre en los cuatro. En el capítulo 8 verás la forma elegante de no repetir el cálculo, que es un CTE.

7. Los nulos, bien preguntados

Cuenta las direcciones que NO tienen marcado si son principales.

SELECT COUNT(*) AS sin_marcar
FROM direcciones
WHERE es_principal IS NULL;
sin_marcar
----------
0
8. Paginar

Trae la segunda página de clientes, de 10 en 10, ordenados por nombre.

SELECT nombre, ciudad
FROM clientes
ORDER BY nombre
LIMIT 10 OFFSET 10;
nombre                          ciudad
------------------------------  --------
  MARKET CENTRAL 069            Trujillo
  MARKET CENTRAL 078            Chiclayo
  MAYORISTA PERU 074            Arequipa
  MINIMARKET AURORA 068         Arequipa
  MINIMARKET AURORA 071         Cusco
  MINIMARKET EL SOL 026         Arequipa
  RESTAURANTE MIRAFLORES 051    Arequipa
  RESTAURANTE MIRAFLORES 070    Arequipa
Almacenes Vega 010              Lima
Almacenes Vega 014              Piura

En MySQL también vale LIMIT 10, 10 y en SQL Server es OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY. El ORDER BY no es opcional al paginar: sin él, la página 2 puede repetir filas de la 1.

9. De dónde salían los 246

Demuestra que la consulta sin paréntesis contestaba otra pregunta. Cuenta las dos mitades por separado.

SELECT COUNT(*) AS todos_los_de_web
FROM pedidos
WHERE canal = 'Web';
todos_los_de_web
----------------
234
SELECT COUNT(*) AS los_grandes_de_tienda
FROM pedidos
WHERE canal = 'Tienda' AND monto > 1000;
los_grandes_de_tienda
---------------------
12

234 más 12 son 246, clavados 🎯

Eso es lo que el motor estaba calculando: todos los de Web, sin mirarles el monto, más los de Tienda que sí pasaban de mil. Ni se equivocó ni fue ambiguo: hizo exactamente lo que dice la precedencia.

Y esta es la forma de depurar cualquier WHERE que te dé un número raro: pártelo en pedazos y cuenta cada uno. Cuando las piezas suman el total, ya sabes cómo se agrupó de verdad.

10. Tres ciudades sin escribir tres OR

Cuenta los clientes de Lima, Arequipa y Cusco.

SELECT COUNT(*) AS de_las_tres
FROM clientes
WHERE ciudad IN ('Lima', 'Arequipa', 'Cusco');
de_las_tres
-----------
54

54 de 120 clientes 🏙️

Con OR serían tres comparaciones y el riesgo del paréntesis en cuanto añadas cualquier otra condición. Con IN son cinco palabras y no hay forma de que se agrupe mal.

El aviso que va con esto: NOT IN no es tan inocente. Si la lista viene de una subconsulta y trae un solo nulo, el resultado se queda vacío. Es una de las trampas del capítulo 8.

11. El BETWEEN de fechas, aquí y en la vida real

Cuenta los pedidos del primer trimestre de 2026 de las dos formas y compara.

SELECT COUNT(*) AS con_between
FROM pedidos
WHERE fecha BETWEEN '2026-01-01' AND '2026-03-31';
con_between
-----------
139
SELECT COUNT(*) AS con_mayor_y_menor
FROM pedidos
WHERE fecha >= '2026-01-01' AND fecha < '2026-04-01';
con_mayor_y_menor
-----------------
139

139 y 139. Aquí dan lo mismo, y conviene saber por qué 🤔

Porque en esta base fecha guarda solo el día, sin hora. Los dos filtros incluyen el 31 de marzo entero y no hay nada más que incluir.

En cuanto la columna guarde también la hora, el primero se rompe: un pedido del 31 de marzo a las 14:20 es mayor que '2026-03-31' a secas, que para el motor es la medianoche. Se te cae un día entero de ventas y el número sigue pareciendo razonable.

Por eso escribo la segunda forma siempre, aunque en esta base no haga falta: funciona en los dos casos, y no tengo que acordarme de si esta columna concreta llevaba hora o no 🕐

12. Las tres posiciones del comodín

Busca clientes por el nombre de tres maneras y mira qué cambia.

SELECT COUNT(*) AS empiezan_por_distribuidora
FROM clientes
WHERE nombre LIKE 'Distribuidora%';
empiezan_por_distribuidora
--------------------------
8
SELECT COUNT(*) AS contienen_market
FROM clientes
WHERE nombre LIKE '%Market%';
contienen_market
----------------
33

8 y 33 🔤

El primero es el barato: el motor sabe que el texto empieza así, y si hay un índice sobre nombre lo puede usar para saltar directo.

El segundo es el caro. Con el comodín delante, hay que mirar los ciento veinte nombres uno por uno, porque "Market" puede estar en cualquier posición. Aquí son 120 filas y no se nota; en una tabla de clientes de verdad, esa consulta es la que deja la pantalla cargando.

Y una tercera cosa que casi nadie sabe: en SQLite y en MySQL, LIKE ignora mayúsculas por defecto; en PostgreSQL no. Buscar '%market%' en minúscula devuelve 33 aquí y 0 en Postgres, donde hay que usar ILIKE. Es de las diferencias que más desconciertan al cambiar de motor 🙃

Los dos grupos que no suman el total

Y ahora una que no da error, no da un número raro, y aun así deja fuera un trozo de tus datos sin que te enteres 🕳️

La trampa

Partes los pedidos en dos grupos para el reporte: los que pasan de 500 soles y los que no. Entre los dos tienen que estar todos.

SELECT COUNT(*) FROM pedidos WHERE monto > 500;
-- 558

SELECT COUNT(*) FROM pedidos WHERE monto <= 500;
-- 315

SELECT COUNT(*) FROM pedidos;
-- 900
Qué está mal

558 más 315 son 873, y los pedidos son 900. Faltan 27 y no están en ninguno de los dos grupos 🕳️

Son los pedidos que tienen el monto en NULL. Un NULL no es mayor que 500 y tampoco es menor o igual que 500: comparar contra NULL no da ni verdadero ni falso, da desconocido, y el WHERE solo deja pasar lo verdadero.

Lo peligroso es que cada consulta por separado se ve perfecta. El agujero solo aparece si sumas los dos grupos y los comparas con el total, que es justo lo que casi nadie hace.

La costumbre que te salva: cada vez que partas algo en grupos, comprueba que los grupos suman el total. Y si hay nulos, decide qué hacer con ellos a propósito, con IS NULL, en vez de dejar que desaparezcan solos.

Comprueba que lo tienes

Escribes SELECT monto * 1.18 AS con_igv FROM ventas WHERE con_igv > 1000 y te dice que con_igv no existe. Pero si lo acabas de crear arriba.

  • El WHERE corre antes que el SELECT, así que el alias todavía no existe
  • Falta poner el alias entre comillas
  • Hay que repetir el cálculo en el WHERE porque SQL no guarda variables
  • El alias solo vale si la columna es de la tabla

Lo que te llevas

  • 🔄 Se escribe SELECT primero y se ejecuta casi al final. Por eso el alias no sirve en el WHERE y sí en el ORDER BY.
  • ✂️ LIMIT en tres motores, TOP en SQL Server, y siempre con ORDER BY.
  • 🧮 Con AND y OR mezclados, paréntesis siempre.
  • 📅 Rangos de fecha con >= y <, no con BETWEEN.
  • 🕳️ Los nulos se preguntan con IS NULL, nunca con =.
  • 🔠 Para buscar texto sin importar mayúsculas, LOWER() en los dos lados y funciona igual en los cuatro.

Y si de todo el capítulo te llevas una sola frase, que sea esta:

El orden en que escribes una consulta no es el orden en que se ejecuta.

Y esto mismo, filtrar y ordenar, en Python se escribe distinto y significa lo mismo. Si quieres verlo desde el otro lado está en el libro de Python 🐍

En el capítulo 4 vamos a los tipos de dato y a los nulos en serio, que es donde se producen los errores más caros y más silenciosos.

Que tengas un hermoso día! 🌟

Practica este capítulo 📓

Todo el código de arriba en un cuaderno que corre de principio a fin, y los ejercicios con una celda vacía para que los hagas tú. Se abre en Google Colab de un clic y no hay que instalar nada. Donde veas %%revisa, escribe tu respuesta y el cuaderno te dice si te salió.

¿Prefieres trabajar en tu máquina? Bájate el cuaderno de práctica o el de soluciones. Todos están también en github.com/soymissyera/MissYeraEjercicios.

¿Le sirve a alguien que conoces?

Pásale el libro. Es gratis, está entero y no pide registro 🐣

Instagram y TikTok no dejan compartir enlaces desde la web: esos dos copian la URL para que la pegues en tu historia.

¿Tienes alguna duda o consulta?