Este es el primer curso de Informática Universitaria: fundamentos de programación
, enseñado con Python 3. No se asume conocimiento previo de programación. Cada programa en
este material está completo y ejecutable — escríbelo, ejecútalo, cambia una línea y predice
qué cambios antes de volver a ejecutarlo. Ese ciclo de predecir-ajustar-ejecutar constituye toda la habilidad.
El curso es una escalera:
Las unidades de 1.1 a 1.3 construyen las mecánicas: valores y tipos, selección e iteración.
La unidad 1.4 encapsula el trabajo en funciones.
Las unidades 1.5 y 1.6 añaden las colecciones estándar y los archivos con manejo de errores.
La unidad 1.7 establece la disciplina superior: descomposición de problemas, pruebas y depuración.
Cómo leer cada unidad: una breve historia explica su importancia; una imagen o una regla general
aparecen antes de cualquier sintaxis; luego las reglas precisas; después ejemplos resueltos que aumentan en dificultad,
cada uno con un trazado que puedes verificar ejecutando el código; luego los errores reales que cometen las personas;
luego un resumen de tres líneas. Cada ejemplo aquí puede verificarse de otra manera —
ejecutándolo. Leer código sin ejecutarlo es como leer partituras musicales sin escucharlas.
escuchándolo.
1.1
Valores, tipos y entrada/salida
Una aplicación de compras muestra "4.20" — ¿es texto o un número? Si es texto, sumar dos
precios da "4.203.50"; si es un número, da 8.7. Cuál ocurre depende de
el tipo, y todo en la unidad 1.1 aprende a identificar tipos antes de que
ejecutes cualquier cosa.
Un valor tiene un tipo, y el tipo determina qué puedes hacer con él: int (números
enteros), float (decimales), str (texto), bool (True/False). El type(...)
función nombra el tipo de cualquier valor — pregúntalo libremente mientras aprendes.
age = 21 # int
price = 4.20 # float
name = "Ada" # str
open_ = True # bool
print(type(age)) # <class 'int'>
Variables son nombres vinculados a valores; la asignación age = 22 revincula el nombre —
Los nombres son etiquetas en cajas; las expresiones leen cajas y crean nuevas cajas
Los nombres son etiquetas en cajas; las expresiones leen cajas y crean nuevas cajas
Leer una expresión es "abrir las cajas, calcular, cerrar una nueva caja": count * 3.50
con count = 6 abre la caja int 6 y la caja float 3.50, y la regla de mezcla
de Python promueve el int — el resultado es la caja float21.0, nunca la caja int 21.
La conversión es explícita: int("21") es 21, pero "4" + 2 es un TypeError — Python
se niega a adivinar si querías decir "42" o 6. Salida con print(...); entrada con
input(...), que siempre devuelve un string, así que los números necesitan conversión:
Ejemplo resuelto 1 (la caja registradora del café).count = int(input("Coffees? ")), luego
print("Total:", count * 3.50). En 6 cafés esto imprime Total: 21.0 — la caja .0
marca un float, porque int * float promueve a float. Observar qué tipo sale
de una expresión es el hábito completo de la unidad 1.1. Rastrea el estado así: input devuelve
la cadena "6"; int(...) abre una nueva caja que contiene la int 6; la multiplicación
hace una nueva caja float21.0; print lo muestra.
Ejemplo resuelto 2 (el accidente clásico, reparado). Un impresor de banderas une un conteo
a un mensaje:
Ejecútalo y Python eleva TypeError: can only concatenate str (not "int") to str —
la regla se niega a adivinar. La reparación nombra la conversión explícitamente:
print("Sold " + str(count) + " today"), o el más limpio print("Vendido", count, "hoy"), which inserts the spaces for you. The lesson to carry: + significa suma
para números, concatenación para cadenas, y nunca una mezcla silenciosa.
Errores comunes. Olvidar que input() siempre devuelve una cadena, por lo tanto
input() * 2 repite texto en lugar de duplicar un número. Escribir 4.20 + 6 y
esperar una respuesta de int: cualquier punto flotante en la expresión produce uno. Confiar
0.1 + 0.2 == 0.3 (es False; redondeo binario — la unidad 1.2 muestra la corrección).
Resumen: cuatro tipos básicos determinan cada operación; los nombres son etiquetas, la asignación reasigna;
las expresiones mezclan tipos mediante promoción con puntos flotantes; las conversiones son explícitas y
input() necesita una.
Un torniquete de parque temático debe cobrar diferente a niños, adultos y ancianos — un
programa, varias rutas. Selección es cómo elige un programa: if, elif,
else ejecuta un bloque solo cuando una condición se cumple.
Edad 70, estudiante: la primera rama verdadera decide el precio
La condición es cualquier expresión booleana; las comparaciones (==, !=, <, >=) y
la lógica (and, or, not) la construyen. Dos reglas hacen que toda decisión sea legible:
Las ramas se evalúan de arriba hacia abajo; la primera rama verdadera gana y el resto
omitido.
La indentación no es decoración: ES el bloque. Un espacio adicional cambia a qué
líneas pertenece la rama.
temp = 37.5
if temp >= 38:
print("Fever")
elif temp >= 36 and temp < 38:
print("Normal")
else:
print("Low")
Ejemplo resuelto 1 (precio de cine). Bajo 12 o 65+ se paga 5, los estudiantes pagan 7,
el resto de las personas 10:
age = int(input("Age? "))
student = input("Student? y/n ") == "y"
if age < 12 or age >= 65:
price = 5
elif student:
price = 7
else:
price = 10
print("Pay", price)
El orden importa: la primera condición verdadera gana, por eso la verificación de edad precede a la
comprobación del estudiante — un estudiante de 70 años debe pagar 5, y este orden lo indica. Ejecuta
con 70 y y para ver la rama más a la izquierda del diagrama de flujo tomada.
donde cada 2 solo necesita una comparación, porque las pruebas anteriores ya han
donde cada elif solo necesita una comparación, porque las pruebas anteriores ya han
fallado:
score = int(input("Score? "))
if score >= 80:
band = "A"
elif score >= 70:
band = "B"
elif score >= 60:
band = "C"
else:
band = "F"
print("Band:", band)
aviso: el 79 no necesita también Band: B; llegar a esa
note: la elif score >= 70 no necesita también and score < 80 — llegar a esa
línea ya demuestra que la puntuación está por debajo de 80. Confiar en la caída como esto es
lo que hace cortas las cadenas; reevaluar todo es la versión más larga y errónea del principiante.
versión.
Floating-point comparisons need care: 0.1 + 0.2 == 0.3 is False (binary
rounding), so compare against a tolerance: abs(x - y) < 1e-9.
Errores comunes. Escribir = donde se pretendía == — el error clásico, y Python
lo rechaza en condiciones (SyntaxError) precisamente porque es tan común. Probar
el caso especial después del general, por lo que el general siempre prevalece. Usando
elif con una condición que nunca se alcanza porque un ramo anterior ya
la capturó.
Resumen: las condiciones eligen rutas, gana el primero verdadero, la indentación define el bloque; ordenar
pruebas de específico a general; los floats se comparan por tolerancia, nunca por ==.
1.3
Iteración: repetirlo nuevamente
Pagar facturas de 5,000 a mano es una carrera; con un bucle es un programa. Iteración
repite trabajo: un bucle for recorre una secuencia conocida; un bucle while se ejecuta mientras una
condición se cumple. La pregunta de diseño siempre es: ¿sabes cuántas veces se repetirá, o decide
una condición?
total = 0
for i in range(1, 101): # 1..100 — range stops BEFORE its end
total += i
print(total) # 5050
Usa for cuando el número de repeticiones sea conocido; usa while cuando una condición decida. Cada
bucle while necesita tres cosas visibles en el código: el valor inicial de la variable, la condición,
y algo dentro que avance hacia la salida — de lo contrario es un bucle infinito.
bucle.
Tabla de trazado para el bucle factorial
n = 5
factorial = 1
while n > 1:
factorial *= n
n -= 1 # the step toward exiting
print(factorial) # 120
La tabla de trazado es el hábito de verificación del curso para los bucles: sigue las
variables ronda por ronda, primero en papel. Para el bucle factorial:
(n=5, f=1) → (4, 5) → (3, 20) → (2, 60) → (1, 120). Construye la tabla a mano, luego
agrega un print(n, factorial) dentro del bucle y deja que Python te califique.
Ejemplo resuelto 2 (una búsqueda con break, escalada). Encuentra el primer múltiplo de
7 que es mayor que 100 — el conteo de viajes es desconocido, la condición decide:
n = 100
while True:
if n % 7 == 0:
break # found it — leave the loop now
n += 1
print(n) # 105
La traza es corta: 100, 101, …, 104 fallan todos la prueba; 105 pasa y break
sale. break sale del bucle temprano; continue salta a la siguiente ronda. Ambos son
herramientas de legibilidad — un bucle que debe escanear hasta encontrar algo, luego detenerse, se lee
mejor con break. La escalada adicional, duplicar el dinero: a 5% al año, ¿cuánto tiempo
tarda 1000 en alcanzar 2000?
balance = 1000.0
years = 0
while balance < 2000.0: # the condition itself is the goal
balance *= 1.05
years += 1
print(years, round(balance, 2)) # 15 2078.93
Quince años, y el bucle se detiene en el momento exacto en que cambia la condición — la tabla de trazado
mostraría el saldo cruzando la línea en 2078.93, no aterrizando exactamente en 2000.
Errores comunes. Editar la lista (unidad 1.5) o la variable del bucle que estás
for-recorriendo. range(1, 100) cuando se pretendía 100 — el final está excluido.
Un while cuyo paso de salida está ausente o comentado: la condición nunca cambia,
y el programa se congela.
Resumen: for para conteos de viaje conocidos, while para condiciones; todo bucle muestra inicio,
condición y paso; traza en papel, luego impresión de nivel examen; break y continue para
salidas tempranas limpias.
El mismo cálculo de precio aparece en cinco lugares; la regla de impuestos cambia; cuatro de los
cinco se actualizan. Una función empaqueta un trabajo detrás de un nombre para que se escriba
una sola vez y confiable en todas partes: parámetros de entrada, un valor de retorno de salida. def
define; llamar ejecuta.
def total_price(count, unit=3.50):
"""Return the total for count items at unit price."""
return count * unit
print(total_price(6)) # 21.0
print(total_price(6, unit=4.0)) # 24.0
La definición proporciona un parámetro (count, unit); la llamada suministra un
argumento (6). Un valor por defecto (unit=3.50) hace que el argumento sea opcional.
return devuelve un valor y termina la llamada; imprimir no es devolver — una
función sin return retorna None, y el invocador que intenta realizar operaciones aritméticas sobre él
recibe un TypeError.
Llamadas anidadas: el invocador se pausa hasta que el valor regresa
Cada llamada tiene su propio ámbito local: los nombres dentro son invisibles
fuera, y desaparecen cuando finaliza la llamada. Una traza de llamadas anidadas muestra:
invocador se pausa → callee se ejecuta → el valor de retorno cae en el invocador.
Ejemplo resuelto 1 (el pedido con tasa incluida).
def add_fee(amount):
"""Return amount plus a fixed 3 pence fee."""
return amount + 3
def order_total(unit_pence, quantity):
"""Return the order total: subtotal plus the fixed fee."""
return add_fee(unit_pence * quantity)
print(order_total(250, 4)) # 1003
El contrato en el docstring es importante: enteros de pence entran, enteros de pence salen. Al rastrear
order_total(250, 4): subtotal 1000, add_fee retorna 1003 — y el diagrama de marco
de arriba muestra la pausa y la devolución.
Ejemplo resuelto 2 (un algoritmo real dentro de una función pequeña, escalado).is_prime(n) retorna True cuando nada desde 2 hasta $\sqrt{n}$ divide n:
def is_prime(n):
if n < 2:
return False
d = 2
while d * d <= n: # only test up to the square root
if n % d == 0:
return False # found a divisor — answer known, stop
d += 1
return True
print([p for p in range(2, 30) if is_prime(p)])
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Dos hábitos de función se encuentran aquí: una temprana return termina la llamada en el momento en que la respuesta
es conocida (no se necesita variable de bandera), y el límite del bucle d * d <= n proviene de la
derivación — si $n = a \times b$ con $a \le b$, entonces $a \le \sqrt{n}$, por lo que probar más allá de $\sqrt{n}$
vuelve a verificar divisores ya vistos. Ejecútalo: la lista son exactamente los primos menores que 30.
Errores comunes. Imprimir dentro de una función en lugar de devolver, por lo que el resultado
no se puede reutilizar. Olvidar el return en una rama — ese camino silenciosamente produce
None. Acceder a las variables locales de una función desde fuera; desaparecen cuando la llamada
termina.
Resumen: def una vez, llámalo en cualquier lugar; parámetros entran, un valor sale mediante return;
las devoluciones tempranas terminan la llamada; las variables locales nacen y mueren con la llamada; los docstrings definen el
contrato.
La caja registradora de un café necesita los pedidos del día en orden, el menú por nombre, una ubicación que
no debe variar y las bebidas únicas de hoy. Cuatro tareas, cuatro contenedores — elegir
entre ellos es la decisión de diseño de la unidad 1.5.
Una lista es una secuencia ordenada y mutable: marks = [72, 85, 61], indexada
desde 0, cortada con marks[1:3]. Un diccionario mapea claves a valores:
price = {"flat white": 3.5, "tea": 2.8}, leído y escrito por clave. Una tupla
es una secuencia fija (53.1, -1.5) — adecuada para coordenadas que no deben cambiar. Un
conjunto contiene elementos únicos sin orden: {"a", "b"}.
marks = [72, 85, 61]
marks.append(90) # [72, 85, 61, 90]
best = max(marks) # 90
average = sum(marks) / len(marks) # 77.0
Aliasing: dos nombres, una lista
La asignación nunca copia: a = [1]; b = a; b.append(2) hace que a sea [1, 2] — la lista
se comparte, los nombres no. Este aliasing son nuevamente las cajas de la unidad 1.1, ahora
con dos etiquetas en una sola caja, y también explica la siguiente regla: mutar una lista mientras
se recorre omite elementos; recorra una copia (for m in marks[:]) o construya una
nueva lista.
El diccionario .get(key, 0) proporciona 0 para un primer avistamiento — la idiomática forma que
hace del conteo una línea de código. Verifique manualmente: "tea" aparece tres veces en orders,
y el recuento impreso así lo indica.
Ejemplo resuelto 2 (el artículo más vendido del día, escalado). Basándose en el recuento,
informe el elemento principal y su cuenta:
best = max(tally, key=tally.get)
print(best, tally[best]) # tea 3
El argumento key=tally.get le pide a max que compare los elementos por sus cuentas en lugar de
alfabéticamente — el mismo truco con key= clasifica cualquier colección según cualquier métrica. La
escalación es importante: los programas reales encadenan estas herramientas (contar, luego clasificar) en lugar de
escanear orders dos veces.
Elegir la colección es la decisión de diseño: orden + duplicados → lista; búsqueda por
nombre → diccionario; agrupación fija → tupla; unicidad → conjunto.
Errores comunes.tally[item] += 1 en una clave que aún no está en el diccionario
(KeyError) — .get es la solución. Esperar que un conjunto o diccionario recuerde el orden.
Asumir que b = a copia una lista; en realidad crea un alias.
Resumen: cuatro contenedores para cuatro tareas; la asignación comparte referencias, nunca copia; .get
hace el conteo seguro; key= convierte el ranking en una sola línea.
Se cierra el café, se corta la electricidad —y el conteo de la mañana sobrevive solo si el
programa lo escribió en disco. Un archivo perdura más que el programa. with open(path) as f: opens, closes on exit, and reads text; open(path, "w") escribe
(y primero lo vacía —el accidente clásico).
with open("log.txt", "w") as f:
f.write("opened\n")
with open("log.txt") as f:
for line in f:
print(line.rstrip()) # rstrip removes the trailing newline
El bloque with es la barandilla de seguridad: suceda lo que suceda dentro, el archivo se cierra.
La lectura devuelve líneas con su salto de línea, por lo que .rstrip() pertenece a cada bucle.
Los errores son valores que Python lanza como excepciones: FileNotFoundError,
ValueError, KeyError, TypeError. try/except captura un tipo nombrado — nunca un
except: desnuda que oculta fallos reales.
Ejemplo resuelto 1 (el archivo de configuración ausente). Leer un tamaño de texto desde un archivo de
configuración que puede estar ausente, o que no contiene un número entero:
try:
with open("settings.txt") as f:
size = int(f.read().strip())
except FileNotFoundError:
size = 12 # no file yet: a sensible default
except ValueError:
size = 12 # the file holds "big", not a number
print("Text size", size)
Ejecútelo tres veces: con 16 en el archivo, sin archivo, y con big en el
archivo; imprime un tamaño utilizable cada vez, procedente de tres rutas distintas. Esa es la estructura del código robusto: una decisión sensata
varios caminos hacia él. Maneja el error que esperas, donde puedas responder a
él; deja que el resto falle ruidosamente.
Ejemplo resuelto 2 (un bucle de entrada robusto, escalado). Pide hasta que el usuario escriba un
número entero:
Pásale x luego 4: el primer intento falla, es capturado, imprime el mensaje, y el
while True vuelve a preguntar; el segundo intento de int(...) tiene éxito y return sale
el bucle y la función en un solo paso. Tres unidades se combinan aquí: el bucle (1.3),
el contrato de la función (1.4) y la excepción (1.6) — que es el objetivo de la
escalada.
Common mistakes.open(path, "w") on the wrong path: the file is created or
emptied before you notice. Catching Exception broadly and continuing with broken
Olvidar .rstrip(), por lo que la salida muestra líneas con doble espacio.
Resumen: with open(...) tanto para lectura como para escritura; "w" vacía primero;
las excepciones son valores: captura el tipo nombrado que esperas, recurre a una solución razonable y deja que el resto falle.
the rest crash.
diferencia no es la velocidad de escritura — es que el código del equipo rápido está construido a partir de piezas pequeñas que pueden demostrar su corrección individualmente, mientras que el equipo lento depura toda la aplicación de una vez.
la diferencia no es la velocidad de escritura, sino que el equipo rápido construye su código a partir de pequeños
componentes que pueden demostrar individualmente que son correctos, mientras el equipo lento depura toda la aplicación de una sola vez.
Un1.7 que nunca se activa es una prueba que pasa. Escribe las pruebas antes de escribir el
suficiente para probarse por separado. Pruebas comparan la salida real de una función con la
esperada, en los límites: cero, uno, vacío, negativo, enorme.
se espera, en los límites: cero, uno, vacío, negativo, enorme.
El bucle guiado por pruebas: rojo, verde, refactorización
Un assert que nunca se dispara es una prueba aprobada. Escriba las pruebas antes de escribir el
cuerpo cuando sea posible — son el contrato hecho ejecutable, y el bucle anterior
(rojo → verde → refacturar) es que ese hábito se ejecute continuamente.
Ejemplo resuelto 1 (ampliar las pruebas con la función). Amplíe order_total con un
descuento de 10% sobre £10: la prueba se escribe primero, en el nuevo límite:
Verifique manualmente la prueba intermedia: subtotal 1003, diez por ciento de descuento da 902.7, y
int(...) se redondea hacia abajo a 902. Observe también la primera afirmación: en 200 peniques y cuatro
tazas, el total permanece en 803: las pruebas de límite demuestran tanto que el descuento se activa
como que no se activa prematuramente.
Ejemplo resuelto 2 (bisecar un error con impresiones, escalonado). El recuento de 1.5
imprime {'tea': 3}, pero la línea del producto más vendido imprime la bebida incorrecta. El método:
(1) reproducir con la entrada fallida más pequeña; (2) imprimir los valores en el límite
entre la parte confiable y la sospechosa: aquí, el recuento antes de la max;
(3) corregir una cosa; (4) ejecutar las pruebas nuevamente. Mover la impresión a mitad del código
en cada ejecución reduce a la mitad la búsqueda: cuatro impresiones localizan un error en un programa de dieciséis líneas, sin importar
cuán bien esté oculto.
Depuración es encontrar la diferencia entre lo que usted cree que hace el código
y lo que realmente hace: la impresión en el límite de confianza/sospecha es el instrumento.
Errores comunes. Probar solo el camino feliz: los límites (cero, uno, vacío,
negativo) son donde los programas fallan. Escribir una semana de código antes de la primera prueba,
luego depurar una montaña. Corregir dos cosas a la vez, para que ningún resultado signifique
nada.
Resumen: descomponga hasta que cada pieza se pruebe por separado; establezca los límites en cuanto exista una función.
Escriba la prueba primero cuando pueda; depure mediante bisección con impresiones de confianza en los límites — el hábito sobre el que se basa toda la carrera.
en los límites de confianza — el hábito en el que se basa toda la titulación.
Lecciones interactivas sobre este tema
Trátalo paso a paso, con ejercicios de verificación instantánea.