Saltar a contenido

Programación — 1º Trimestre

Herramientas de este trimestre

  • Entorno de código: Pickcode — navegador, sin instalación, soporta turtle, pygame y Pillow en 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

  1. ¿Por qué un ordenador "falla" con instrucciones ambiguas, si la instrucción parecía clara para una persona?
  2. ¿Qué diferencia hay entre pseudocódigo y diagrama de flujo? ¿Cuándo usarías uno u otro?
  3. 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 variable t para poder darle órdenes. Turtle() es lo que se llama un constructor: crea un objeto nuevo listo para usar.
  • t.speed(3) y t.color("blue") — son métodos: acciones que le pedimos al objeto t. speed ajusta la velocidad de dibujo (0 = instantáneo, 10 = muy rápido); color cambia 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 indique lados (aquí, 4 veces). La variable i va 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. Girar 360 / lados grados 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.forward sin (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

  1. ¿Qué diferencia hay entre un método (como t.forward()) y una variable (como lados)?
  2. ¿Por qué la indentación importa en Python y no en otros lenguajes?
  3. 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

  • import carga librerías ya hechas por otros; turtle es una de dibujo.
  • Un objeto (como la tortuga t) tiene métodos: acciones que se le piden con objeto.metodo(argumentos).
  • La indentación en Python define qué código pertenece a qué bloque.
  • El ángulo 360 / lados es 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:

lados = 6
print(f"La figura tiene {lados} lados")

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: " + 6 da error (no se puede concatenar texto con un número directamente; hay que convertirlo con str(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 / 2 da 3.5, 7 // 2 da 3.

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

  1. ¿Qué significa que Python tenga "tipado dinámico"?
  2. ¿Por qué "lados: " + 6 da error, pero f"lados: {6}" no?
  3. ¿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, str y bool.
  • 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:

import turtle

t = turtle.Turtle()
lados = 5

for i in range(lados)
    t.forward(80)
    t.right(360 / lados)

turtle.done()

Ejercicio propuesto — depuración 2

Este código también falla. ¿Qué tipo de error da y por qué?

numero = 7
if numero % 2 = 0:
    print("par")
else:
    print("impar")

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

  1. ¿Qué diferencia hay entre = y ==?
  2. ¿Por qué solo se ejecuta una rama de un if/elif/else, aunque varias condiciones sean técnicamente verdaderas?
  3. Si un programa da NameError, ¿qué tienes que revisar primero?

Resumen del bloque

  • if/elif/else permite 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 el for ... 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

  1. ¿Qué controla el bucle exterior y qué controla el interior en el ejemplo del rosetón?
  2. ¿Por qué la espiral usa 91° en vez de 90°?
  3. ¿Qué hace range(10, 0, -1) y en qué se diferencia de range(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:

  1. "Hazme un dibujo bonito con turtle" (vago)
  2. "Código turtle que dibuje una espiral de cuadrados" (específico pero sin detalles)
  3. "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

  1. Pedir con un prompt específico.
  2. Ejecutar el código que devuelve — nunca copiar sin probar.
  3. Comprobar si hace exactamente lo que pediste (no "algo parecido").
  4. Si no, ajustar el prompt primero (antes que tocar el código a mano) y repetir.
  5. 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

  1. ¿Por qué "hazme un dibujo bonito" es un mal prompt, aunque técnicamente pida algo?
  2. ¿Qué significa que una IA "alucine" código?
  3. ¿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)) con imagen.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

  1. ¿Por qué invertir un color es simplemente 255 - valor en cada componente?
  2. ¿Qué hace .load() sobre una imagen de Pillow?
  3. ¿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:

  1. Procesar eventos — ¿ha pulsado el jugador una tecla? ¿ha cerrado la ventana?
  2. Actualizar el estado — mover objetos, comprobar colisiones, sumar puntos.
  3. Dibujar — pintar el fotograma actual en pantalla.
  4. 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, y es la posición de la pelota; vx, vy es cuánto se mueve en cada fotograma (su "velocidad").
  • El while ejecutando: es el bucle de juego: se repite sin parar hasta que ejecutando pasa a False.
  • for evento in pygame.event.get(): recoge lo que ha pasado desde el último fotograma (aquí solo comprobamos si se cerró la ventana, con pygame.QUIT).
  • x += vx y y += vy actualizan la posición: es la parte de "actualizar estado".
  • Los dos if comprueban 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

  1. ¿Por qué el bucle de juego usa while y no for?
  2. ¿Qué pasaría si quitas la línea pantalla.fill(...) del ejemplo de la pelota?
  3. ¿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 y creciendo 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

  1. ¿Qué diferencia hay entre frecuencia y amplitud de una onda?
  2. ¿Qué es un diccionario en Python y para qué se usó en el ejemplo de la melodía?
  3. ¿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

  1. Planificación — boceto o pseudocódigo de qué se quiere construir.
  2. Desarrollo con IA como copiloto — aplicando el ciclo pedir → ejecutar → comprobar → ajustar del bloque 6, documentando el proceso en una bitácora.
  3. 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.