Programación — 1º Trimestre
Herramientas de este trimestre
- Entorno de código: Pickcode — navegador, sin instalación, soporta
turtle,pygameyPillowen el mismo sitio. - Copiloto de IA: chat de IA disponible. Se introduce a partir del bloque 6.
- Filosofía: no se evalúa escribir código de memoria. Se evalúa descomponer problemas, leer y entender código (propio o generado por IA), depurarlo y documentar el proceso.
1. Pensamiento computacional
¿Qué es programar, en realidad?
Programar no es "aprenderse comandos de memoria". Es describir la solución a un problema con tanta precisión que hasta una máquina sin sentido común pueda ejecutarla. Un ordenador no interpreta, no intuye, no rellena huecos: hace exactamente lo que le dices, ni una cosa más ni una menos. Esa es la diferencia fundamental entre hablar con una persona y "hablar" con un ordenador: una persona sobreentiende contexto; un ordenador no sobreentiende nada.
A la descripción de pasos ordenados y precisos para resolver un problema se le llama algoritmo. Usamos algoritmos constantemente sin darnos cuenta:
- Una receta de cocina (pasos ordenados, cantidades precisas).
- Las instrucciones de montaje de un mueble (numeradas, con dibujos).
- El semáforo de un cruce (secuencia fija: verde → ámbar → rojo → verde...).
- La ruta que calcula un GPS (de todos los caminos posibles, elige uno según un criterio).
- El programa de un lavavajillas (prelavado → lavado → aclarado → secado).
Todos comparten estructura: una entrada (los datos de partida — los ingredientes, el destino en el GPS), un proceso (los pasos que se aplican a esa entrada) y una salida (el resultado — el plato cocinado, la ruta calculada). A este esquema se le llama entrada → proceso → salida, y es el patrón que vas a reconocer en todo programa que escribas este curso, por complicado que parezca.
Descomposición: dividir para resolver
Cuando un problema es grande, la estrategia es descomponerlo: dividirlo en piezas más pequeñas y manejables, resolver cada pieza por separado y encajarlas al final. Por ejemplo, "organizar una fiesta de cumpleaños" se descompone en subtareas independientes: decidir la fecha, hacer la lista de invitados, elegir el sitio, comprar comida, preparar la decoración. Cada subtarea es, a su vez, una secuencia de pasos más pequeños.
Esta es la habilidad central del pensamiento computacional, y es la misma habilidad que necesitarás para especificarle un problema a una IA más adelante (bloque 6) — la máquina que ejecuta tus instrucciones ha cambiado, pero la necesidad de pensar con precisión y por partes no.
Pseudocódigo y diagramas de flujo
Antes de escribir código real, es útil expresar el algoritmo de dos formas intermedias:
- Pseudocódigo: los pasos escritos en lenguaje natural, pero ordenados y numerados, como si fueran instrucciones de una receta. No sigue reglas estrictas de sintaxis — su objetivo es pensar claro, no ejecutarse.
- Diagrama de flujo: la misma idea, pero dibujada con símbolos estándar:
| Símbolo | Forma | Significado |
|---|---|---|
| Óvalo | ⬭ | Inicio o fin del algoritmo |
| Rectángulo | ▭ | Un paso o proceso (una acción) |
| Rombo | ◇ | Una decisión (sí/no, verdadero/falso) — de aquí salen dos caminos distintos |
| Flecha | → | El orden en que se conectan los pasos |
El rombo de decisión es importante: es la primera pista visual de lo que en el bloque 4 se convertirá en un if/else real en código.
El experimento del "robot muy literal"
En parejas: un alumno escribe en un papel, paso a paso, las instrucciones para preparar un bocadillo. El otro alumno hace de robot y solo puede hacer exactamente lo que dice el papel — ni un movimiento más, ni interpretación de por medio.
Casi siempre pasa lo mismo: "coger el pan" no dice con qué mano, ni de dónde, ni cuánto pan. "Untar la mantequilla" no dice cuánta, ni con qué. El robot se queda parado, o hace algo absurdo pero técnicamente correcto según lo escrito.
Eso es exactamente lo que le pasa a un ordenador con código ambiguo: no falla por "tonto", falla porque nosotros no fuimos suficientemente precisos.
Vocabulario clave
| Término | Significado |
|---|---|
| Algoritmo | Secuencia ordenada y precisa de pasos para resolver un problema |
| Pseudocódigo | Algoritmo escrito en lenguaje natural, ordenado, sin sintaxis estricta |
| Diagrama de flujo | Representación gráfica de un algoritmo con símbolos estándar |
| Descomposición | Dividir un problema grande en partes más pequeñas y manejables |
| Entrada / Proceso / Salida | Esquema básico de todo algoritmo: datos de partida, transformación, resultado |
Errores típicos al empezar
- Dar por hecho contexto que la máquina no tiene ("coger el pan" sin decir de dónde).
- Saltarse pasos que parecen obvios para una persona pero no lo son para una máquina.
- Cambiar el orden: muchos algoritmos fallan no por faltar un paso, sino por estar en el orden equivocado (freír el huevo antes de encender el fuego).
Ejercicio propuesto — nivel básico
Traduce a pseudocódigo (pasos numerados, uno por línea, en lenguaje natural pero preciso) el proceso de "dibujar una escalera de 5 escalones". No uses código todavía — el objetivo es pensar en pasos, no en sintaxis.
Ejercicio propuesto — nivel intermedio
Dibuja un diagrama de flujo (a mano o con una herramienta online tipo draw.io) para "decidir si puedes cruzar un semáforo peatonal". Debe incluir al menos un rombo de decisión.
Ejercicio propuesto — reto
Descompón "organizar una fiesta de cumpleaños" en al menos 5 subtareas independientes, y para una de ellas, detalla sus propios pasos internos (descomposición de segundo nivel).
Preguntas de repaso
- ¿Por qué un ordenador "falla" con instrucciones ambiguas, si la instrucción parecía clara para una persona?
- ¿Qué diferencia hay entre pseudocódigo y diagrama de flujo? ¿Cuándo usarías uno u otro?
- Pon un ejemplo propio (no visto en clase) de algoritmo que uses en tu día a día.
Resumen del bloque
- Programar es describir soluciones con precisión total, no memorizar sintaxis.
- Todo algoritmo sigue el esquema entrada → proceso → salida.
- La descomposición permite abordar problemas grandes por partes.
- Pseudocódigo y diagramas de flujo son pasos previos útiles antes de escribir código real.
Producto / evidencia
Pseudocódigo del ejercicio de la escalera + diagrama de flujo del semáforo.
2. Primer contacto con Python y lectura de código
El entorno y la idea de "librería"
Python es un lenguaje de programación pensado para leerse casi como inglés, y es interpretado: cada línea se ejecuta en orden, de arriba a abajo, en el momento en que se lee (a diferencia de otros lenguajes que se "compilan" enteros antes de ejecutarse). No vamos a memorizar su sintaxis de golpe: vamos a leer código que ya funciona, predecir qué hace, ejecutarlo y comprobar si acertábamos. Es la misma lógica que aprender un idioma leyendo antes de escribir redacciones.
Usaremos la librería (también llamada módulo) turtle: un conjunto de herramientas ya programadas por otras personas que nos permiten controlar una "tortuga" que dibuja líneas mientras se mueve por la pantalla, como si llevara un lápiz atado. No hace falta programar el dibujo de una línea desde cero — alguien ya lo hizo y lo compartió; nosotros lo reutilizamos con import turtle. Este es uno de los superpoderes reales de programar: casi nunca partes de cero, siempre hay librerías que resuelven problemas comunes.
Un detalle importante de Python: la indentación
A diferencia de otros lenguajes, en Python los espacios al principio de una línea (la indentación) no son estéticos: indican qué código pertenece a qué bloque. Todo lo que está indentado bajo un for pertenece a ese bucle; en cuanto una línea vuelve al margen izquierdo, ya no forma parte de él. Pickcode (como la mayoría de editores) indenta automáticamente al pulsar Intro después de una línea que termina en : — pero si copias y pegas código de sitios distintos, es la causa nº1 de errores para quien empieza.
Código de ejemplo, explicado línea a línea
import turtle
t = turtle.Turtle()
t.speed(3)
t.color("blue")
lados = 4
for i in range(lados):
t.forward(100)
t.right(360 / lados)
turtle.done()
import turtle— carga la librería de dibujo.t = turtle.Turtle()— crea una tortuga y la guarda en la variabletpara poder darle órdenes.Turtle()es lo que se llama un constructor: crea un objeto nuevo listo para usar.t.speed(3)yt.color("blue")— son métodos: acciones que le pedimos al objetot.speedajusta la velocidad de dibujo (0 = instantáneo, 10 = muy rápido);colorcambia el color del trazo.lados = 4— guardamos el número de lados que queremos dibujar en una variable.for i in range(lados):— repite lo que viene indentado debajo, tantas veces como indiquelados(aquí, 4 veces). La variableiva tomando los valores 0, 1, 2, 3 en cada repetición, aunque en este caso no la usemos.t.forward(100)— avanza 100 píxeles dibujando una línea (argumento: el número entre paréntesis que le pasamos al método).t.right(360 / lados)— gira a la derecha. Girar360 / ladosgrados en cada vuelta es lo que hace que la figura se cierre exactamente (un giro completo son 360°, y queremos repartirlo en partes iguales entre los lados).turtle.done()— mantiene la ventana abierta al terminar; sin esta línea, la ventana podría cerrarse inmediatamente.
Segundo ejemplo — un triángulo y un hexágono
import turtle
t = turtle.Turtle()
# Triángulo
for i in range(3):
t.forward(100)
t.right(120)
t.penup() # levanta el "lápiz": moverse sin dibujar
t.goto(150, 0) # se desplaza a otra posición
t.pendown() # vuelve a bajar el lápiz para dibujar
# Hexágono
for i in range(6):
t.forward(60)
t.right(60)
turtle.done()
Aquí aparecen dos métodos nuevos: penup()/pendown() (para moverse sin dejar rastro) y goto(x, y) (para saltar directamente a una posición de la pantalla). Fíjate en el patrón: el ángulo de giro siempre es 360 / número_de_lados — 120° para 3 lados, 60° para 6 lados.
Tabla de referencia rápida — comandos turtle más usados
| Comando | Qué hace |
|---|---|
t.forward(n) / t.fd(n) |
Avanza n píxeles dibujando |
t.backward(n) / t.bk(n) |
Retrocede n píxeles dibujando |
t.right(grados) / t.rt(grados) |
Gira a la derecha |
t.left(grados) / t.lt(grados) |
Gira a la izquierda |
t.penup() |
Levanta el lápiz (moverse sin dibujar) |
t.pendown() |
Baja el lápiz (volver a dibujar) |
t.goto(x, y) |
Salta a una coordenada concreta |
t.color("nombre") |
Cambia el color del trazo |
t.circle(radio) |
Dibuja un círculo del radio indicado |
t.speed(n) |
Ajusta la velocidad (0 = instantáneo, 1-10 = lento a rápido) |
Errores comunes
- Olvidar
turtle.done()al final. - Indentación inconsistente (mezclar espacios y tabulaciones, o desalinear una línea).
- Olvidar los paréntesis al llamar a un método (
t.forwardsin(100)no hace nada). - Poner el ángulo de giro equivocado y no entender por qué la figura "no cierra" — repasa la relación
360 / lados.
Ejercicio propuesto — predicción
Antes de ejecutar el código del primer ejemplo, predice en papel qué figura dibujará. Ejecútalo y comprueba si acertaste.
Ejercicio propuesto — modificación
Cambia lados a 3, 5, 6 y 8 en el primer ejemplo, y anota qué figura obtienes en cada caso. ¿Se cumple siempre la relación 360°/lados?
Ejercicio propuesto — reto (estrella)
Una estrella de 5 puntas se dibuja con range(5), avanzando y girando 144° en vez de 72°. Pruébalo y explica con tus palabras por qué girar "de más" (en vez del ángulo que cerraría un pentágono normal) es lo que crea el efecto de estrella.
Preguntas de repaso
- ¿Qué diferencia hay entre un método (como
t.forward()) y una variable (comolados)? - ¿Por qué la indentación importa en Python y no en otros lenguajes?
- Si quisieras dibujar un octógono (8 lados) sin usar bucle, ¿cuántas líneas de código necesitarías? ¿Por qué el bucle es mejor?
Resumen del bloque
importcarga librerías ya hechas por otros;turtlees una de dibujo.- Un objeto (como la tortuga
t) tiene métodos: acciones que se le piden conobjeto.metodo(argumentos). - La indentación en Python define qué código pertenece a qué bloque.
- El ángulo
360 / ladoses la clave para que cualquier polígono regular cierre correctamente.
Producto / evidencia
Cuestionario corto de predicción de código + figuras generadas (triángulo, hexágono, estrella).
3. Variables y tipos de datos
Variables: cajas con nombre
Una variable es como una caja con una etiqueta: guarda un valor que puedes usar y cambiar más adelante en el programa, referenciándolo por su nombre. En Python no hace falta declarar de qué tipo es la caja — Python lo deduce automáticamente del valor que le metes. Esto se llama tipado dinámico.
Tipos de datos básicos
| Tipo | Nombre en Python | Ejemplo | Uso típico |
|---|---|---|---|
| Número entero | int |
tamano = 120 |
Contar, medir en píxeles, número de lados |
| Número decimal | float |
velocidad = 3.5 |
Medidas con precisión, cálculos |
| Texto (cadena) | str |
color = "purple" |
Nombres, colores, mensajes |
| Verdadero/falso | bool |
activo = True |
Resultado de comparaciones (bloque 4) |
Un texto siempre va entre comillas (simples '...' o dobles "...", da igual cuáles mientras seas consistente). Puedes comprobar el tipo de cualquier valor con type(variable) — útil para depurar cuando algo no funciona como esperabas.
Operaciones básicas
Con números: + (suma), - (resta), * (multiplicación), / (división, siempre da decimal), // (división entera, sin decimales), % (módulo, el resto de una división — clave en el bloque 4).
Con texto: + concatena (une) cadenas: "Hola " + "Ana" da "Hola Ana". Para mezclar texto y números al mostrar mensajes, se usan las f-strings, muy útiles para depurar:
La f antes de las comillas activa que lo que va entre {} se sustituya por el valor de la variable. print() es una función que muestra texto en la consola — no dibuja nada, pero es la herramienta más usada para comprobar qué valor tiene una variable en un momento dado, especialmente al depurar.
Código de ejemplo
import turtle
t = turtle.Turtle()
color = "purple"
tamano = 120
lados = 6
print(f"Voy a dibujar un polígono de {lados} lados en color {color}")
t.color(color)
for i in range(lados):
t.forward(tamano)
t.right(360 / lados)
turtle.done()
Aquí color, tamano y lados son las tres variables que controlan el resultado. Cambiar una sola línea (por ejemplo lados = 6 → lados = 3) cambia toda la figura, sin tocar el resto del código — esa es la idea de parametrizar un programa: separar los "datos" (qué figura quiero) del "proceso" (cómo se dibuja cualquier figura).
Nombres de variable: una cuestión de claridad
t.forward(x) funciona igual que t.forward(tamano), pero tamano le dice a cualquiera que lea el código qué representa ese número, sin tener que ejecutarlo para averiguarlo. En Python la convención es escribir nombres de variable en minúsculas y con guiones bajos para separar palabras (numero_lados, no NumeroLados ni numerolados) — se llama snake_case.
Errores comunes
- Mezclar tipos incompatibles:
"lados: " + 6da error (no se puede concatenar texto con un número directamente; hay que convertirlo constr(6)o usar f-strings). - Nombres de variable poco descriptivos (
x,a,cosa1) que hacen el código difícil de leer más adelante. - Confundir
/(siempre decimal) con//(entera):7 / 2da3.5,7 // 2da3.
Ejercicio propuesto — básico
Modifica color, tamano y lados para crear tres figuras distintas. Añade un print() con f-string antes de cada dibujo, indicando qué vas a dibujar.
Ejercicio propuesto — intermedio
Añade una cuarta variable propia (por ejemplo grosor) y úsala con t.pensize(grosor) para controlar el grosor del trazo.
Ejercicio propuesto — reto
Crea variables ancho y alto que definan un rectángulo, y usa un bucle (o cuatro llamadas a forward/right con esas variables) para dibujarlo. Pista: un rectángulo no es un polígono regular, así que no vale la fórmula 360/lados.
Preguntas de repaso
- ¿Qué significa que Python tenga "tipado dinámico"?
- ¿Por qué
"lados: " + 6da error, perof"lados: {6}"no? - ¿Qué diferencia hay entre
/y//?
Resumen del bloque
- Las variables guardan valores con nombre; Python deduce su tipo automáticamente.
- Los tipos básicos son
int,float,strybool. - Las f-strings (
f"texto {variable}") son la forma más cómoda de mostrar valores mezclados con texto. - Parametrizar (usar variables en vez de valores fijos) hace el código reutilizable y fácil de modificar.
Producto / evidencia
Figura personalizada a partir del código base, con al menos una variable añadida por el alumno.
4. Condicionales y depuración
Tomar decisiones: if / else
Hasta ahora nuestros programas siempre hacían lo mismo, en el mismo orden. Un condicional permite que el programa decida entre varios caminos según se cumpla o no una condición — es la versión en código del rombo de decisión que vimos en el bloque 1:
if condicion:
# se ejecuta si la condición es verdadera
elif otra_condicion:
# se ejecuta si la primera era falsa pero esta es verdadera
else:
# se ejecuta si ninguna de las anteriores lo era
El elif (contracción de "else if") permite encadenar más de dos caminos; puedes poner tantos como necesites, y el else final es opcional.
Operadores de comparación y lógicos
| Operador | Significado | Ejemplo |
|---|---|---|
== |
Igual a | numero == 10 |
!= |
Distinto de | color != "rojo" |
< / > |
Menor / mayor que | edad < 18 |
<= / >= |
Menor o igual / mayor o igual | nota >= 5 |
and |
Ambas condiciones verdaderas | edad >= 12 and edad <= 17 |
or |
Al menos una verdadera | color == "rojo" or color == "azul" |
not |
Invierte el valor | not activo |
Cuidado: == compara (pregunta "¿son iguales?"), mientras que = asigna (dice "guarda este valor aquí"). Confundirlos es uno de los errores más comunes al empezar.
El operador % (módulo) da el resto de una división — por eso numero % 2 == 0 es la forma estándar de comprobar si un número es par (si el resto de dividir entre 2 es 0, es par).
Código de ejemplo — dos caminos
import turtle
import random
t = turtle.Turtle()
numero = random.randint(1, 10)
if numero % 2 == 0:
t.color("blue")
else:
t.color("orange")
t.circle(60)
turtle.done()
random.randint(1, 10) genera un número entero al azar entre 1 y 10 (ambos incluidos) — viene de la librería random, que también hay que importar. Según sea par o impar, el círculo se dibuja de un color u otro — el programa "decide" en tiempo de ejecución, y por eso cada vez que lo ejecutes puede salir distinto.
Código de ejemplo — tres o más caminos con elif
import turtle
t = turtle.Turtle()
angulo = 95 # cámbialo para probar los distintos casos
if angulo < 90:
tipo = "agudo"
t.color("green")
elif angulo == 90:
tipo = "recto"
t.color("blue")
else:
tipo = "obtuso"
t.color("red")
print(f"El ángulo de {angulo}° es {tipo}")
t.circle(50)
turtle.done()
Aquí el programa clasifica un ángulo en tres categorías. Fíjate en que solo se ejecuta una de las tres ramas — en cuanto se cumple una condición, Python ignora el resto.
Depurar: leer el error en vez de temerlo
Todo el mundo comete errores de sintaxis al escribir código — lo importante no es evitarlos siempre, sino saber leer lo que Python te dice cuando falla. El mensaje de error (traceback) casi siempre indica la línea exacta y el tipo de problema.
| Tipo de error | Causa típica | Ejemplo |
|---|---|---|
SyntaxError |
Falta algo en la estructura (dos puntos, paréntesis, comillas) | for i in range(5) (sin :) |
IndentationError |
La indentación no coincide con lo esperado | Una línea del bloque sin indentar |
NameError |
Usas una variable que no existe o está mal escrita | print(lado) cuando la variable se llama lados |
ZeroDivisionError |
Divides entre 0 | 360 / lados con lados = 0 |
TypeError |
Mezclas tipos incompatibles | "lados: " + 6 |
Aprender a leer el traceback (qué línea, qué tipo de error) es más rentable que intentar memorizar sintaxis para no equivocarse nunca.
Ejercicio propuesto — depuración 1
El siguiente código tiene un error y no se ejecuta. Encuéntralo y corrígelo usando el mensaje de error como pista:
Ejercicio propuesto — depuración 2
Este código también falla. ¿Qué tipo de error da y por qué?
Ejercicio propuesto — reto
Escribe un programa que pida (usando una variable, no input() todavía) una nota numérica y clasifique con if/elif/else en "suspenso" (<5), "aprobado" (5-6), "notable" (7-8) o "sobresaliente" (9-10).
Preguntas de repaso
- ¿Qué diferencia hay entre
=y==? - ¿Por qué solo se ejecuta una rama de un
if/elif/else, aunque varias condiciones sean técnicamente verdaderas? - Si un programa da
NameError, ¿qué tienes que revisar primero?
Resumen del bloque
if/elif/elsepermite que el programa tome decisiones y siga caminos distintos.- Los operadores de comparación (
==,<,>...) y lógicos (and,or,not) construyen las condiciones. - El mensaje de error de Python (tipo + línea) es la mejor pista para depurar, no algo que temer.
Producto / evidencia
Registro escrito: "qué error encontré, qué mensaje daba Python, y cómo lo arreglé", para cada uno de los dos ejercicios de depuración.
5. Bucles anidados y patrones
Un bucle dentro de otro
Un bucle anidado es simplemente un for dentro de otro for. El bucle exterior decide cuántas veces se repite todo el bloque interior, y el interior dibuja una figura completa cada vez. Es la misma lógica que "repite 6 veces: dibuja un cuadrado, y luego gira un poco" — el resultado es un patrón tipo rosetón.
range() con más detalle
Hasta ahora hemos usado range(n), que genera números desde 0 hasta n-1. range() admite hasta tres argumentos: range(inicio, fin, paso).
| Llamada | Genera |
|---|---|
range(5) |
0, 1, 2, 3, 4 |
range(2, 8) |
2, 3, 4, 5, 6, 7 |
range(0, 10, 2) |
0, 2, 4, 6, 8 |
range(10, 0, -1) |
10, 9, 8, ..., 1 |
El tercer argumento (paso) es lo que nos permite, por ejemplo, aumentar el tamaño de una figura en cada repetición de un bucle.
Código de ejemplo — rosetón
import turtle
t = turtle.Turtle()
t.speed(0)
for i in range(6):
for lado in range(4):
t.forward(50)
t.right(90)
t.right(60)
turtle.done()
El bucle interior (for lado in range(4)) dibuja un cuadrado completo (4 lados, giro de 90° en cada uno). El bucle exterior (for i in range(6)) repite ese cuadrado 6 veces, girando 60° entre cada uno — por eso aparecen 6 cuadrados repartidos alrededor de un punto central, como los pétalos de una flor.
Fíjate en la relación: 6 × 60° = 360°, un giro completo. Si cambias el número de repeticiones, tienes que ajustar el ángulo para que la figura "cierre" correctamente alrededor del centro.
Código de ejemplo — espiral creciente
import turtle
t = turtle.Turtle()
t.speed(0)
tamano = 5
for i in range(50):
t.forward(tamano)
t.right(91)
tamano += 3 # equivale a: tamano = tamano + 3
turtle.done()
Aquí no hay bucle anidado, pero sí una variable (tamano) que cambia en cada vuelta del bucle, en lugar de quedarse fija. tamano += 3 es una forma abreviada de tamano = tamano + 3. Usar un ángulo de 91° en vez de 90° exactos es lo que evita que la espiral se cierre sobre sí misma y produce el efecto de espiral abierta.
Errores comunes
- Desalinear la indentación entre el bucle exterior y el interior (Python interpretará el bloque incorrectamente).
- Olvidar que la variable de control de un bucle (
i,lado) no hace falta usarla dentro si no la necesitas, pero sí hay que declarar elfor ... in range(...)completo. - No mantener la relación repeticiones × ángulo = 360° cuando se busca una figura cerrada (aunque, como en la espiral, a veces buscamos justo lo contrario).
Ejercicio propuesto — básico
Cambia el range(6) exterior y el ángulo t.right(60) del rosetón para crear al menos dos rosetones distintos, manteniendo la relación (repeticiones × ángulo = 360°). ¿Qué pasa si no la mantienes?
Ejercicio propuesto — intermedio
Modifica la espiral para que, en vez de crecer, vaya cambiando de color cada 10 vueltas (pista: usa el módulo % del bloque 4 junto con un if dentro del bucle).
Ejercicio propuesto — reto
Combina ambas ideas: un bucle exterior que repita una espiral pequeña varias veces, girando cada vez, para crear un patrón tipo "flor de espirales".
Preguntas de repaso
- ¿Qué controla el bucle exterior y qué controla el interior en el ejemplo del rosetón?
- ¿Por qué la espiral usa 91° en vez de 90°?
- ¿Qué hace
range(10, 0, -1)y en qué se diferencia derange(0, 10)?
Resumen del bloque
- Un bucle anidado repite un bloque completo (que a su vez repite otro bloque) — útil para patrones compuestos.
range(inicio, fin, paso)controla con precisión cuántas veces y con qué incremento se repite un bucle.- Una variable puede cambiar de valor en cada iteración del bucle, permitiendo efectos progresivos (crecimiento, cambio de color...).
Producto / evidencia
Patrón geométrico propio (rosetón modificado + espiral con variación), captura o archivo .py.
6. La IA como copiloto
Programar en la era de la IA generativa
Hoy cualquiera puede pedirle código a una IA. Eso no elimina la necesidad de saber programar: elimina la necesidad de memorizar sintaxis, pero hace todavía más importante saber especificar bien un problema, leer el resultado con sentido crítico, probarlo y corregirlo. Eso es justo lo que vamos a entrenar en este bloque, y lo que seguirá apareciendo el resto del curso.
Qué hace bueno o malo un prompt
Un buen prompt (instrucción a la IA) es tan específico como el pseudocódigo del bloque 1: dice qué se quiere, con qué restricciones, y con qué detalle. Un prompt vago produce un resultado vago — o algo que "funciona" pero no es lo que querías.
Checklist de un buen prompt para pedir código:
- [ ] ¿Dice qué quieres conseguir (el objetivo final)?
- [ ] ¿Da detalles concretos (número de elementos, colores, tamaños)?
- [ ] ¿Indica con qué herramienta (turtle, pygame, Pillow) debe hacerse?
- [ ] ¿Menciona restricciones (nivel principiante, sin librerías raras, comentado)?
Ejercicio propuesto — comparar prompts
Pide a la IA el mismo objetivo con tres prompts distintos:
- "Hazme un dibujo bonito con turtle" (vago)
- "Código turtle que dibuje una espiral de cuadrados" (específico pero sin detalles)
- "Código Python con turtle que dibuje una espiral de 20 cuadrados, cada uno un poco más grande que el anterior, con colores que cambien progresivamente" (preciso)
Ejecuta los tres resultados y compara. ¿Cuál se ajusta mejor a lo que querías? ¿Por qué?
Después, coge el mejor resultado y personalízalo (colores, número de repeticiones), anotando en comentarios # qué cambiaste y por qué.
El ciclo de trabajo con IA
- Pedir con un prompt específico.
- Ejecutar el código que devuelve — nunca copiar sin probar.
- Comprobar si hace exactamente lo que pediste (no "algo parecido").
- Si no, ajustar el prompt primero (antes que tocar el código a mano) y repetir.
- Cuando el resultado es correcto, personalizarlo tú mismo y documentar qué cambiaste.
Un aviso importante: las IA se equivocan con seguridad
Una IA generativa puede producir código que parece correcto — bien formateado, con nombres de variable sensatos — pero que no funciona, o que hace algo distinto a lo pedido. A esto se le llama a veces "alucinación": la IA no sabe que se equivoca, simplemente genera lo que estadísticamente parece una buena respuesta. Por eso el paso 2 (ejecutar y comprobar) del ciclo anterior nunca es opcional — copiar código sin probarlo es la forma más rápida de arrastrar un error sin saberlo.
Transparencia: documentar el uso de IA
En este trimestre (y en el resto del curso) no está mal usar IA — está mal no saber explicar lo que tu código hace. Por eso cada proyecto donde uses IA como copiloto debe ir acompañado de una breve nota indicando qué pediste y qué tuviste que corregir tú. Es una buena costumbre que también se espera en el mundo profesional.
Ejercicio propuesto — reto
Pide a la IA un código que, a propósito, sea más complejo de lo que necesitas (por ejemplo, pídele "el código más eficiente posible" para dibujar un patrón). Léelo con calma: ¿entiendes cada línea? Simplifícalo tú mismo si hay partes que no comprendes, aunque el resultado visual sea idéntico.
Preguntas de repaso
- ¿Por qué "hazme un dibujo bonito" es un mal prompt, aunque técnicamente pida algo?
- ¿Qué significa que una IA "alucine" código?
- ¿Por qué documentar el proceso de trabajo con IA es parte de la evaluación, y no solo el resultado final?
Resumen del bloque
- Un buen prompt es específico: objetivo, detalles, herramienta y restricciones.
- El ciclo de trabajo con IA es pedir → ejecutar → comprobar → ajustar → personalizar.
- El código generado por IA puede parecer correcto y no serlo — siempre hay que probarlo.
- Documentar el proceso (qué se pidió, qué se corrigió) es parte del trabajo, no un extra.
Producto / evidencia
Patrón generado con IA + bitácora de prompts (las 3 versiones y qué aprendiste de cada una).
7. Imágenes y filtros con Pillow
Una imagen es una matriz de píxeles
Una imagen digital es, por dentro, una cuadrícula (matriz) de puntos de color llamados píxeles. Cada píxel guarda tres valores entre 0 y 255: cuánto de rojo, verde y azul tiene (el modelo RGB, de Red-Green-Blue). Una imagen de 200×200 píxeles tiene 40.000 de esos puntos, cada uno con su propio color, y se organizan en filas y columnas igual que las celdas de una hoja de cálculo — el píxel (0, 0) está en la esquina superior izquierda, y x crece hacia la derecha mientras y crece hacia abajo (el mismo sistema de coordenadas que usaremos en pygame, bloque 8).
Tabla de referencia — colores RGB comunes
| Color | Valores (R, G, B) |
|---|---|
| Blanco | (255, 255, 255) |
| Negro | (0, 0, 0) |
| Rojo puro | (255, 0, 0) |
| Verde puro | (0, 255, 0) |
| Azul puro | (0, 0, 255) |
| Gris medio | (128, 128, 128) |
Un "filtro" no es magia: es simplemente recorrer todos los píxeles y aplicar la misma transformación matemática a cada uno.
Código de ejemplo — crear una imagen y convertirla a blanco y negro
from PIL import Image, ImageDraw
# Imagen de prueba generada en código (o usa Image.open("foto.jpg") con una imagen propia)
img = Image.new("RGB", (200, 200), color=(255, 255, 255))
draw = ImageDraw.Draw(img)
draw.ellipse((40, 40, 160, 160), fill=(0, 150, 255))
draw.rectangle((70, 70, 130, 130), fill=(255, 100, 0))
img.save("original.png")
# Filtro blanco y negro
bn = img.convert("L")
bn.save("blanco_y_negro.png")
Image.new(...) crea un lienzo en blanco de 200×200 con fondo blanco; ImageDraw nos deja dibujar formas encima, indicando en cada caso las coordenadas de la "caja" que contiene la forma. El método .convert("L") transforma cada píxel de color a un único valor de luminosidad (escala de grises, la L es de Luminance) — Pillow ya trae esa transformación hecha y optimizada.
Código de ejemplo — invertir colores
def invertir_colores(imagen):
rgb = imagen.convert("RGB")
pixeles = rgb.load()
ancho, alto = rgb.size
for x in range(ancho):
for y in range(alto):
r, g, b = pixeles[x, y]
pixeles[x, y] = (255 - r, 255 - g, 255 - b)
return rgb
Invertir un color es tan sencillo como restarle cada componente a 255: el rojo puro (255, 0, 0) se convierte en cian (0, 255, 255). Este código recorre la imagen con dos bucles anidados (x e y, como en el bloque 5, pero sobre píxeles en vez de sobre figuras).
Código de ejemplo — un filtro sepia escrito a mano, píxel a píxel
def aplicar_sepia(imagen):
gris = imagen.convert("L")
sepia = Image.new("RGB", gris.size)
pixeles_gris = gris.load()
pixeles_sepia = sepia.load()
ancho, alto = gris.size
for x in range(ancho):
for y in range(alto):
valor = pixeles_gris[x, y]
r = min(255, int(valor * 1.07))
g = min(255, int(valor * 0.74))
b = min(255, int(valor * 0.43))
pixeles_sepia[x, y] = (r, g, b)
return sepia
Para cada píxel, tomamos su valor de gris y lo multiplicamos por tres factores distintos para el rojo, verde y azul — eso es lo que da el tono cálido característico del sepia (más rojo, algo menos verde, bastante menos azul). Es el mismo patrón "recorrer y transformar" que usa .convert("L") por dentro, solo que aquí lo vemos explícito. La función min(255, ...) evita que el cálculo se pase del máximo permitido (255).
¿Y el efecto pixelado?
El pixelado funciona de forma distinta: en vez de transformar cada píxel de forma independiente, se agrupan los píxeles en bloques (por ejemplo, de 10×10) y se sustituye cada bloque entero por su color promedio. Es un buen ejercicio para pedir a la IA una vez entendido el patrón general de "recorrer y transformar".
Una nota sobre rendimiento
Recorrer una imagen píxel a píxel con dos bucles for en Python, como hacemos aquí, es instructivo pero relativamente lento para imágenes grandes. Por eso Pillow trae funciones ya optimizadas (como .convert("L")) para las operaciones más comunes — nosotros escribimos el bucle manual solo para entender qué ocurre "por dentro".
Errores comunes
- Olvidar
.save("nombre.png")al final — sin eso, la imagen se genera pero no se guarda en ningún archivo. - Acceder a coordenadas fuera del tamaño de la imagen (
pixeles[300, 300]en una imagen de 200×200) — da un error de índice. - Confundir
imagen.size(una tupla(ancho, alto)) conimagen.size()(no es un método, no lleva paréntesis).
Ejercicio propuesto — básico
Ejecuta los tres filtros (blanco y negro, invertir, sepia) sobre la misma imagen base y compara los resultados.
Ejercicio propuesto — intermedio
Pide a la IA un filtro pixelado, basándote en la explicación de "agrupar en bloques y promediar". Ejecútalo e identifica en el código dónde se hace ese agrupamiento.
Ejercicio propuesto — reto
Inventa tu propio filtro (por ejemplo, resaltar solo el canal rojo, o aplicar un degradado que oscurezca la imagen de arriba a abajo), especificando primero en pseudocódigo qué transformación quieres, y pide a la IA que lo implemente.
Preguntas de repaso
- ¿Por qué invertir un color es simplemente
255 - valoren cada componente? - ¿Qué hace
.load()sobre una imagen de Pillow? - ¿Por qué el pixelado necesita agrupar píxeles en bloques, en vez de transformar cada uno por separado como el sepia?
Resumen del bloque
- Una imagen es una matriz de píxeles, cada uno con valores RGB entre 0 y 255.
- Un filtro es "recorrer píxeles + aplicar una transformación matemática" — el mismo patrón para blanco y negro, invertir o sepia.
- Pillow trae operaciones comunes ya optimizadas; escribir el bucle a mano ayuda a entender qué hacen por dentro.
Producto / evidencia
Filtro de imagen propio + explicación línea a línea (oral o escrita) de cómo funciona.
8. Videojuego simple con pygame
La estructura de todo videojuego: el bucle de juego
Todo juego, por complejo que sea, repite el mismo ciclo muchas veces por segundo:
- Procesar eventos — ¿ha pulsado el jugador una tecla? ¿ha cerrado la ventana?
- Actualizar el estado — mover objetos, comprobar colisiones, sumar puntos.
- Dibujar — pintar el fotograma actual en pantalla.
- Esperar un poco para mantener una velocidad constante (los frames por segundo).
Ese ciclo se llama game loop, y es la base conceptual de cualquier juego, desde un Pong hasta un triple A. Nótese que este bucle usa while, no for: while repite mientras se cumpla una condición (aquí, mientras el jugador no cierre la ventana), sin saber de antemano cuántas veces se va a ejecutar — al contrario que for, donde el número de repeticiones se conoce desde el principio.
Código de ejemplo, explicado
import pygame
pygame.init()
ANCHO, ALTO = 400, 300
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pygame.display.set_caption("Pelota rebotando")
reloj = pygame.time.Clock()
x, y = 200, 150
vx, vy = 4, 3
radio = 20
ejecutando = True
while ejecutando:
for evento in pygame.event.get():
if evento.type == pygame.QUIT:
ejecutando = False
x += vx
y += vy
if x - radio <= 0 or x + radio >= ANCHO:
vx = -vx
if y - radio <= 0 or y + radio >= ALTO:
vy = -vy
pantalla.fill((20, 20, 30))
pygame.draw.circle(pantalla, (0, 200, 255), (x, y), radio)
pygame.display.flip()
reloj.tick(60)
pygame.quit()
pygame.init()prepara la librería para usarse.x, yes la posición de la pelota;vx, vyes cuánto se mueve en cada fotograma (su "velocidad").- El
while ejecutando:es el bucle de juego: se repite sin parar hasta queejecutandopasa aFalse. for evento in pygame.event.get():recoge lo que ha pasado desde el último fotograma (aquí solo comprobamos si se cerró la ventana, conpygame.QUIT).x += vxyy += vyactualizan la posición: es la parte de "actualizar estado".- Los dos
ifcomprueban si la pelota ha tocado un borde; si es así, invierten la velocidad (vx = -vx) — así rebota. pantalla.fill(...)borra el fotograma anterior (si no lo hiciéramos, la pelota dejaría un rastro);pygame.draw.circle(...)dibuja la pelota en su nueva posición;pygame.display.flip()muestra ese fotograma completo en pantalla de golpe.reloj.tick(60)limita el bucle a 60 fotogramas por segundo, para que la velocidad no dependa de lo rápido que sea el ordenador que lo ejecuta.
Nota importante: en pygame, el origen (0, 0) está en la esquina superior izquierda, y el eje y crece hacia abajo — al revés que en las gráficas matemáticas habituales, pero igual que en las imágenes del bloque 7.
Código de ejemplo — control con teclado
import pygame
pygame.init()
pantalla = pygame.display.set_mode((400, 300))
reloj = pygame.time.Clock()
x, y = 190, 140
velocidad = 5
ejecutando = True
while ejecutando:
for evento in pygame.event.get():
if evento.type == pygame.QUIT:
ejecutando = False
teclas = pygame.key.get_pressed()
if teclas[pygame.K_LEFT]:
x -= velocidad
if teclas[pygame.K_RIGHT]:
x += velocidad
if teclas[pygame.K_UP]:
y -= velocidad
if teclas[pygame.K_DOWN]:
y += velocidad
pantalla.fill((20, 20, 30))
pygame.draw.rect(pantalla, (0, 200, 255), (x, y, 20, 20))
pygame.display.flip()
reloj.tick(60)
pygame.quit()
pygame.key.get_pressed() devuelve el estado de todas las teclas en ese instante; comprobamos con if si las flechas están pulsadas y movemos el cuadrado en consecuencia. Este es el bloque base para controlar una pala de Pong o un personaje.
Detección de colisiones (adelanto)
Cuando tengas dos objetos rectangulares en pantalla, pygame ofrece rect1.colliderect(rect2) para saber si se solapan — muy útil para detectar si la pelota choca con una pala, o un personaje con un obstáculo. No hace falta que lo domines ahora: es lo primero que pedirás a la IA cuando tu mini-juego lo necesite.
Errores comunes
- Olvidar el bucle
for evento in pygame.event.get():— sin él, la ventana deja de responder y el sistema operativo la marca como "no responde". - Olvidar
pygame.display.flip()— el estado se actualiza pero nada se ve en pantalla. - Olvidar
pantalla.fill(...)al principio de cada vuelta — los objetos dejan un rastro en vez de moverse limpiamente. - Olvidar
pygame.quit()al final.
Ejercicio propuesto — básico
Cambia el color, el radio y la velocidad inicial de la pelota rebotando. Prueba qué pasa si vx y vy son iguales, o si uno es mucho mayor que el otro.
Ejercicio propuesto — intermedio
Pide a la IA que convierta el ejemplo de la pelota rebotando en un mini-Pong (añadiendo una pala controlada con las flechas, basándote en el segundo ejemplo de esta sección). Pide las mejoras una a una (primero la pala, luego el control, luego el marcador) y prueba cada cambio antes de pedir el siguiente.
Ejercicio propuesto — reto
Añade una segunda pelota independiente (con su propia posición y velocidad) al ejemplo original. Al final, intercambia tu juego con un compañero para hacer playtesting y anota los fallos que te reporte.
Preguntas de repaso
- ¿Por qué el bucle de juego usa
whiley nofor? - ¿Qué pasaría si quitas la línea
pantalla.fill(...)del ejemplo de la pelota? - ¿Para qué sirve
reloj.tick(60)?
Resumen del bloque
- Todo juego se basa en el bucle: procesar eventos → actualizar → dibujar → esperar.
pygame.event.get()recoge lo que ha pasado (teclado, cierre de ventana);pygame.key.get_pressed()consulta el estado actual de las teclas.- El origen de coordenadas está arriba a la izquierda, con
ycreciendo hacia abajo. colliderect()es la herramienta estándar para detectar colisiones entre rectángulos.
Producto / evidencia
Mini-juego jugable + registro de iteraciones (qué pediste en cada paso y por qué).
9. Sonido generativo
El sonido como onda
Un sonido es una onda: vibra con una frecuencia (cuántas veces por segundo, medida en Hz — determina si es un tono agudo o grave) y una amplitud (cuánta energía tiene — determina el volumen). El audio digital captura esa onda tomando miles de "fotos" (muestras) por segundo; el estándar de calidad CD es 44.100 muestras por segundo (44.1 kHz).
Tabla de referencia — frecuencias de notas musicales (octava central)
| Nota | Frecuencia (Hz) |
|---|---|
| Do | 261.63 |
| Re | 293.66 |
| Mi | 329.63 |
| Fa | 349.23 |
| Sol | 392.00 |
| La | 440.00 |
| Si | 493.88 |
El La a 440 Hz es el estándar de afinación usado por orquestas de todo el mundo.
Código de ejemplo — un tono
import numpy as np
import pygame
pygame.mixer.init()
def generar_tono(frecuencia=440, duracion=1.0, volumen=0.3):
muestras = 44100
t = np.linspace(0, duracion, int(muestras * duracion), False)
onda = np.sin(frecuencia * t * 2 * np.pi)
audio = (onda * volumen * 32767).astype(np.int16)
estereo = np.column_stack((audio, audio))
return pygame.sndarray.make_sound(estereo)
sonido = generar_tono(440, 1)
sonido.play()
pygame.time.wait(1000)
np.linspace(...) genera los instantes de tiempo en los que vamos a "muestrear" la onda. np.sin(frecuencia * t * 2 * np.pi) calcula una onda senoidal pura a esa frecuencia. El resto convierte esos valores matemáticos (entre -1 y 1) a un formato de audio de 16 bits que pygame puede reproducir, y np.column_stack duplica el canal para que suene en estéreo (izquierda y derecha igual).
Código de ejemplo — una melodía corta
notas = {
"Do": 261.63, "Re": 293.66, "Mi": 329.63,
"Fa": 349.23, "Sol": 392.00, "La": 440.00, "Si": 493.88,
}
melodia = ["Do", "Re", "Mi", "Do"]
for nota in melodia:
frecuencia = notas[nota]
sonido = generar_tono(frecuencia, duracion=0.4)
sonido.play()
pygame.time.wait(400)
Un diccionario (notas = {clave: valor, ...}) asocia cada nombre de nota con su frecuencia — es como una tabla de consulta. El bucle recorre la lista melodia y, para cada nota, busca su frecuencia en el diccionario y genera el tono correspondiente.
Nota
Si numpy no está disponible en Pickcode, alternativa más simple: cargar un archivo .wav corto con pygame.mixer.Sound("archivo.wav") y reproducirlo en respuesta a un evento del juego (por ejemplo, al rebotar la pelota).
Errores comunes
- Olvidar
pygame.mixer.init()antes de generar o reproducir sonido. - Volumen fuera de rango (debe estar entre 0 y 1, no en porcentaje ni en decibelios).
- Frecuencias muy altas o muy bajas pueden no ser audibles o sonar desagradables — el oído humano capta aproximadamente entre 20 Hz y 20.000 Hz.
Ejercicio propuesto — básico
Cambia la frecuencia de generar_tono y comprueba cómo cambia el tono percibido. Prueba también a cambiar el volumen.
Ejercicio propuesto — intermedio
Usando el diccionario de notas, compón una melodía corta propia (al menos 6 notas) y reprodúcela.
Ejercicio propuesto — reto
Añade un sonido al mini-juego del bloque 8: que suene algo distinto cuando la pelota rebota y cuando se marca un punto (dos tonos de frecuencia distinta).
Preguntas de repaso
- ¿Qué diferencia hay entre frecuencia y amplitud de una onda?
- ¿Qué es un diccionario en Python y para qué se usó en el ejemplo de la melodía?
- ¿Por qué 44.100 muestras por segundo es un número que aparece constantemente en audio digital?
Resumen del bloque
- El sonido es una onda caracterizada por frecuencia (tono) y amplitud (volumen).
- El audio digital muestrea esa onda miles de veces por segundo.
- Un diccionario permite asociar nombres (notas) con valores (frecuencias) para consultarlos fácilmente.
Producto / evidencia
Proyecto del bloque 8 con sonido incorporado, o melodía corta compuesta con el diccionario de notas.
10. Proyecto integrador
Objetivo
Sintetizar todo lo aprendido en el trimestre en una pieza propia, y ser capaz de defenderla oralmente — explicar qué hace tu código y por qué, aunque parte se haya generado con ayuda de IA.
Ideas de proyecto (elige una o combina varias)
- Una escena visual generativa que combine patrones geométricos (bloque 5) con una paleta de colores propia.
- Un filtro de imagen "de autor" aplicado a una foto real, con su propio nombre y estilo.
- Un mini-juego con mecánica propia (no tiene por qué ser Pong o Snake) que incluya sonido.
- Una pieza audiovisual: patrón visual generativo sincronizado con una melodía.
- Cualquier combinación de las anteriores que tenga sentido como proyecto único.
Metodología de trabajo
- Planificación — boceto o pseudocódigo de qué se quiere construir.
- Desarrollo con IA como copiloto — aplicando el ciclo pedir → ejecutar → comprobar → ajustar del bloque 6, documentando el proceso en una bitácora.
- Defensa oral — el alumno explica, parte por parte, qué hace su código y qué decisiones tomó (por qué ese filtro, por qué esa mecánica de juego, qué problema tuvo que depurar).
Rúbrica orientativa
| Criterio | Insuficiente | Suficiente | Notable | Excelente |
|---|---|---|---|---|
| Funcionamiento | No ejecuta o falla constantemente | Ejecuta con algún fallo menor | Ejecuta correctamente | Ejecuta correctamente y pulido |
| Comprensión del código | No sabe explicar su propio código | Explica lo básico | Explica la mayoría con soltura | Explica cualquier parte, incluida la generada por IA |
| Uso de la IA | Copia sin entender ni probar | Usa IA pero apenas itera | Itera con prompts específicos | Itera, documenta y personaliza con criterio propio |
| Documentación (bitácora) | No existe | Incompleta | Completa | Completa y refleja aprendizaje real |
| Originalidad | Copia directa de un ejemplo visto en clase | Variación mínima | Aporta ideas propias | Combina bloques de forma creativa y coherente |
Preparación para la defensa oral
Antes de la presentación, asegúrate de poder responder a:
- ¿Qué hace tu programa, explicado en una frase?
- ¿Qué parte del código escribiste tú y cuál generó la IA?
- ¿Qué problema tuviste que depurar, y cómo lo resolviste?
- Si tuvieras que añadir una función más, ¿cuál sería y cómo la abordarías?
Producto / evidencia
Portafolio del trimestre (todos los productos de los bloques anteriores) + proyecto integrador + defensa oral.
Criterio de evaluación transversal
En ningún bloque se evalúa "escribir código de memoria". Los cuestionarios piden leer código y predecir o encontrar errores; los proyectos evalúan el proceso (prompts, depuración, documentación) tanto como el resultado final.