Las dos lecciones anteriores dejaron un problema muy concreto sobre la mesa. La lista resolvió el «muchas tareas», pero para guardar los campos de cada tarea nos obligó a mantener cinco listas paralelas sincronizadas a mano: un pop olvidado y el listado empieza a mentir. El motivo de fondo es que en una lista los datos se identifican por posición, y la posición no significa nada: tarea[1] no dice si el 1 es el responsable o la prioridad.

Esta lección presenta dos estructuras que resuelven problemas distintos y que casi siempre se enseñan juntas. El diccionario guarda cada dato con su nombre, de modo que tarea["responsable"] se explica solo. El conjunto guarda elementos únicos sin orden, y es la herramienta correcta para preguntas del tipo «¿está esto aquí?» o «¿qué etiquetas distintas usamos?». Al final, TareaFácil dará el salto a la v0.9 y una tarea pasará a ser, por fin, una sola cosa.

Contenido

  1. Clave y valor: el problema que resuelve el diccionario
  2. Crear diccionarios y acceder a sus valores
  3. Añadir, modificar y borrar
  4. Recorrer un diccionario
  5. update, setdefault y qué puede ser una clave
  6. Diccionario por comprensión y conteo de frecuencias
  7. Conjuntos: elementos únicos y sin orden
  8. Operaciones entre conjuntos
  9. Usos típicos de los conjuntos
  10. Lista, diccionario o conjunto: tabla de decisión
  11. TareaFácil v0.9: la tarea es un diccionario
  12. Errores comunes y consejos
  13. Ejercicios
  14. Conclusión

  1. Clave y valor: el problema que resuelve el diccionario

Un diccionario (tipo dict) es una colección de pares clave → valor. La clave sustituye al índice numérico: en vez de recordar que el responsable está en la posición 1, lo pides por su nombre.

tarea = {"titulo": "Cartel feria del libro", "responsable": "Marta",
         "prioridad": "alta", "dias": 3, "completada": False}
print(tarea["responsable"])     # Marta
print(len(tarea))               # 5  -> numero de pares

Compara las dos formas de guardar la misma tarea:

Lista Diccionario
Se accede por Posición: tarea[1] Nombre: tarea["responsable"]
Se lee solo No: hay que recordar el orden Sí
Añadir un campo nuevo Rompe todos los índices posteriores Solo añade una clave
Orden de los elementos Significativo Irrelevante para el acceso
Buen uso Muchas cosas del mismo tipo Los campos distintos de una cosa

Esa última fila es la regla de decisión: lista para «muchos», diccionario para «uno con varios campos». Los responsables del estudio son una lista (["Marta", "Luis", "Nuria"]); una tarea concreta es un diccionario. Y cuando quieras muchas tareas, será una lista de diccionarios, que es exactamente 05-04.

  1. Crear diccionarios y acceder a sus valores

Hay dos formas de crearlo, y un diccionario vacío se escribe {}:

tarea = {"titulo": "Menu Sole", "dias": 5}      # literal con llaves
otra = dict(titulo="Menu Sole", dias=5)         # con dict() y argumentos por nombre
vacio = {}
print(tarea == otra)                # True -> son equivalentes
print(tarea["titulo"])              # Menu Sole
print(tarea["responsable"])         # KeyError: 'responsable'
print(tarea.get("responsable"))     # None  -> no falla
print(tarea.get("responsable", "sin asignar"))      # sin asignar

Para leer un valor hay, por tanto, dos caminos, y elegir bien es importante:

Forma Si la clave existe Si no existe
d[clave] Devuelve el valor KeyError y el programa se detiene
d.get(clave) Devuelve el valor Devuelve None
d.get(clave, defecto) Devuelve el valor Devuelve defecto

La regla práctica: usa d[clave] cuando la clave tiene que estar (si no está, es un error de programación y quieres enterarte), y .get() cuando su ausencia sea una posibilidad normal. Para comprobarlo explícitamente está el operador in, que en un diccionario mira las claves y no los valores: "titulo" in tarea es True, pero "Menu Sole" in tarea es False porque eso es un valor.

  1. Añadir, modificar y borrar

Añadir y modificar se escriben igual, y esa es una de las trampas del tipo: si la clave no existe, se crea; si existe, se sobrescribe sin avisar.

tarea = {"titulo": "Menu Sole", "dias": 5}
tarea["responsable"] = "Luis"       # la clave no existe: se anade
tarea["dias"] = 6                   # la clave existe: se sobrescribe
del tarea["dias"]                   # borra; KeyError si no existe
valor = tarea.pop("responsable")    # borra y devuelve el valor
otro = tarea.pop("cliente", "ninguno")  # con defecto: no falla si no esta
tarea.clear()                       # deja el diccionario vacio

del es la forma directa de borrar; .pop(clave) es la que usas cuando además necesitas el valor que sale, igual que en las listas. Y como en las listas, el diccionario es mutable: si lo pasas a una función y ésta lo modifica, el cambio se ve fuera. Esto es lo que permitirá que cambiar_prioridad(tarea) funcione sin devolver nada.

  1. Recorrer un diccionario

Un for directo sobre un diccionario recorre las claves. Los tres métodos de vista permiten elegir qué quieres recorrer:

Método Produce en cada vuelta
.keys() Las claves (es lo mismo que el for directo)
.values() Los valores
.items() Pares (clave, valor)
tarea = {"titulo": "Cartel feria", "responsable": "Marta", "dias": 3}
for clave in tarea:                     # equivale a tarea.keys()
    print(clave, end=" ")               # titulo responsable dias
for valor in tarea.values():
    print(valor, end=" ")               # Cartel feria Marta 3
for clave, valor in tarea.items():
    print(f"{clave:<12}: {valor}")
for clave, valor in sorted(tarea.items()):      # recorrido alfabetico por clave
    print(clave, valor)

.items() es el que usarás el 90 % de las veces, porque casi siempre necesitas las dos cosas a la vez. Fíjate en que el desempaquetado for clave, valor in ... es el mismo mecanismo que ya viste con enumerate y zip en 05-01. Desde Python 3.7 el diccionario conserva el orden de inserción al recorrerlo, pero no te apoyes en ello para dar significado a las posiciones: si necesitas un orden concreto, pídelo explícitamente con sorted(), como en la última línea.

  1. update, setdefault y qué puede ser una clave

.update(otro) fusiona otro diccionario dentro del primero: añade las claves nuevas y sobrescribe las que coincidan. Es la forma limpia de aplicar varios cambios de golpe.

.setdefault(clave, valor) devuelve el valor de la clave si existe y, si no, la crea con el valor indicado: sirve para garantizar que una clave está presente sin machacar lo que ya hubiera. Y sobre las claves hay una regla estricta: una clave debe ser inmutable. Valen cadenas, números, booleanos y tuplas (05-04); no vale una lista ni otro diccionario.

tarea = {"titulo": "Cartel feria", "dias": 3}
tarea.update({"dias": 4, "prioridad": "alta"})      # fusiona: dias pasa a 4
tarea.setdefault("completada", False)   # no estaba: la crea en False
tarea.setdefault("dias", 99)            # ya estaba: no toca nada, devuelve 4

valido = {"marta": 3, 2026: "ano", (1, 2): "coordenada"}
invalido = {["marta"]: 3}       # TypeError: unhashable type: 'list'

El motivo, sin entrar en detalles: el diccionario coloca cada valor en un sitio calculado a partir de la clave, así que si la clave pudiera cambiar después, el valor quedaría perdido en un sitio equivocado. Los valores, en cambio, pueden ser lo que quieras: listas, otros diccionarios, funciones. Y las claves son únicas: escribir dos veces la misma en un literal deja solo la última.

  1. Diccionario por comprensión y conteo de frecuencias

Igual que las listas, los diccionarios admiten comprensión, con la misma sintaxis más el par clave: valor:

dias_por_tarea = {"Cartel feria": 3, "Menu Sole": 5, "Logotipo Vidal": 2}
en_horas = {t: d * 8 for t, d in dias_por_tarea.items()}
print(en_horas)     # {'Cartel feria': 24, 'Menu Sole': 40, 'Logotipo Vidal': 16}
largas = {t: d for t, d in dias_por_tarea.items() if d > 2}
print(largas)       # {'Cartel feria': 3, 'Menu Sole': 5}

Y ahora un patrón clásico que aparece en todos los programas del mundo: contar frecuencias. La idea es usar el diccionario como marcador, con el elemento de clave y el recuento de valor.

responsables = ["Marta", "Luis", "Marta", "Nuria", "Marta", "Luis"]
conteo = {}
for nombre in responsables:
    conteo[nombre] = conteo.get(nombre, 0) + 1
print(conteo)                       # {'Marta': 3, 'Luis': 2, 'Nuria': 1}
print(max(conteo, key=conteo.get))  # Marta -> quien tiene mas tareas
print(sorted(conteo.items(), key=lambda p: p[1], reverse=True))
# [('Marta', 3), ('Luis', 2), ('Nuria', 1)]

La línea decisiva es conteo[nombre] = conteo.get(nombre, 0) + 1, y merece leerse despacio. conteo.get(nombre, 0) devuelve el recuento actual o 0 si es la primera vez que aparece ese nombre; se le suma 1 y se guarda. Ese 0 por defecto es lo que evita el KeyError de la primera vuelta y ahorra un if nombre in conteo:. Después, max(conteo, key=conteo.get) recorre las claves y las compara por su valor —el key de 04-05 aplicado a un diccionario— y sorted(...) ordena los pares por el segundo elemento; cómo ordena por dentro sigue siendo materia de Algoritmos de ordenamiento.

  1. Conjuntos: elementos únicos y sin orden

Un conjunto (set) es una colección de elementos únicos y sin orden. No hay índices: conjunto[0] da error. A cambio, responde a la pregunta «¿está esto dentro?» de forma inmediata y elimina duplicados sin esfuerzo.

etiquetas = {"diseno", "imprenta", "cliente", "diseno"}
print(etiquetas)        # {'cliente', 'diseno', 'imprenta'}  -> el duplicado desaparece
print(len(etiquetas), "imprenta" in etiquetas)      # 3 True
vacio_dict = {}                                 # ojo: esto es un dict
vacio_set = set()                               # el conjunto vacio se escribe asi
desde_lista = set(["Marta", "Luis", "Marta"])   # {'Marta', 'Luis'}

El orden en que se imprime no es el de escritura y no debes contar con él. Y cuidado con la trampa de la creación que muestran las tres últimas líneas: {} no es un conjunto vacío, sino un diccionario vacío. Los métodos básicos son cuatro, y la diferencia entre los dos de borrado es la de siempre:

Método Qué hace Si el elemento no está
.add(x) Añade x (si ya estaba, no pasa nada) —
.discard(x) Lo quita No hace nada
.remove(x) Lo quita KeyError
.pop() Saca un elemento cualquiera KeyError si está vacío

Existe también frozenset, la versión inmutable del conjunto: una vez creado no admite add ni remove, y su utilidad principal es poder usarse como clave de diccionario, cosa que un set normal no puede.

  1. Operaciones entre conjuntos

Aquí es donde los conjuntos brillan: implementan directamente las operaciones de la teoría de conjuntos, con operadores de una sola letra.

Partimos de marta = {"cartel", "menu", "logotipo"} y luis = {"menu", "flyer"}.

Operación Operador Método Resultado Significa
Unión marta | luis .union() {'cartel','menu','logotipo','flyer'} En uno o en otro
Intersección marta & luis .intersection() {'menu'} En los dos
Diferencia marta - luis .difference() {'cartel','logotipo'} En el primero y no en el segundo
Diferencia simétrica marta ^ luis .symmetric_difference() {'cartel','logotipo','flyer'} En uno pero no en ambos
graph LR
    A["Solo MARTA<br/>cartel, logotipo"] --- C["COMUN<br/>menu"]
    C --- D["Solo LUIS<br/>flyer"]

El diagrama se lee así: menu está en la zona común (la intersección), cartel y logotipo son exclusivos de Marta (la diferencia marta - luis) y flyer lo es de Luis. La unión son los cuatro; la diferencia simétrica, los tres de fuera del centro. Existe además luis <= marta, que comprueba si un conjunto está contenido en otro (aquí, False), y >= para lo contrario: son útiles para validar cosas como «¿son todas las prioridades introducidas de las admitidas?».

  1. Usos típicos de los conjuntos

Dos usos cubren casi todos los casos reales. El primero, eliminar duplicados de una lista en una línea: con responsables = ["Marta", "Luis", "Marta", "Nuria", "Luis"], la expresión sorted(set(responsables)) devuelve ['Luis', 'Marta', 'Nuria']. Fíjate en el sorted(): convertir a conjunto pierde el orden original, así que si el orden importa hay que reordenar después (o usar otra técnica). El segundo uso es comprobar pertenencia en colecciones grandes: preguntar x in conjunto es muchísimo más rápido que x in lista cuando hay miles de elementos, porque el conjunto no recorre nada. El porqué exacto se estudia en Algoritmos de búsqueda y Eficiencia y notación Big-O; por ahora quédate con la consecuencia práctica: si tu programa hace muchas comprobaciones de pertenencia sobre una colección sin duplicados, esa colección debería ser un conjunto.

  1. Lista, diccionario o conjunto: tabla de decisión

Criterio Lista Diccionario Conjunto
Sintaxis [1, 2] {"a": 1} {1, 2}
Se accede por Índice Clave No se accede: se pregunta con in
¿Mantiene el orden? Sí, y significa algo Sí, de inserción No
¿Admite duplicados? Sí Claves no, valores sí No
Buscar un elemento Recorre Directo Directo
Caso típico Muchas cosas iguales Los campos de una cosa Etiquetas, pertenencia, deduplicar

Las preguntas que llevan a la respuesta correcta: ¿importa el orden o voy a recorrerlos en secuencia? Lista. ¿Cada dato tiene un nombre propio? Diccionario. ¿Solo me importa si algo está o no, y no quiero repetidos? Conjunto.

  1. TareaFácil v0.9: la tarea es un diccionario

En la v0.8 conseguimos muchas tareas a costa de cinco listas paralelas frágiles. Ahora vamos a arreglar la otra mitad del problema —la forma de una tarea— y para verlo con claridad volvemos temporalmente a gestionar una sola: en la v0.9 la tarea es un diccionario, y en 05-04 juntaremos las dos ideas en una lista de diccionarios.

# tareafacil.py - Estudio Alba / Version 0.9: cada tarea es un diccionario

ANCHO = 46
PRIORIDADES = ("alta", "media", "baja")
EQUIPO = ("marta", "luis", "nuria")
OPCIONES = ("1", "2", "3", "4", "5")
ETIQUETAS = {"diseno", "imprenta", "cliente"}

# --- Entrada y logica sin cambios: pedir_texto, pedir_opcion, pedir_entero,
#     confirmar, clasificar_urgencia, mostrar_menu, pausar ---

def registrar_tarea():
    """Pide los datos de una tarea y devuelve el diccionario completo."""
    return {
        "titulo": pedir_texto("Titulo        : "),
        "responsable": pedir_opcion("Responsable   : ", EQUIPO).capitalize(),
        "prioridad": pedir_opcion("Prioridad     : ", PRIORIDADES),
        "dias": pedir_entero("Dias (1-365)  : ", 1, 365),
        "etiqueta": pedir_opcion("Etiqueta      : ", sorted(ETIQUETAS)),
        "completada": False,
    }

def mostrar_ficha(tarea):
    """Pinta la ficha completa de la tarea recibida."""
    estado = "Completada" if tarea["completada"] else "Pendiente"
    dias_estado = str(tarea["dias"]) + " / " + estado
    urgencia = clasificar_urgencia(tarea["prioridad"], tarea["dias"])
    print("-" * ANCHO)
    for etiqueta, clave in (("Titulo", "titulo"), ("Responsable", "responsable"),
                            ("Prioridad", "prioridad"), ("Etiqueta", "etiqueta")):
        print(f"{etiqueta:<14}{tarea[clave]:>{ANCHO - 14}}")
    print(f"{'Dias / estado':<14}{dias_estado:>{ANCHO - 14}}")
    print(f"{'Urgencia':<14}{urgencia:>{ANCHO - 14}}")

def cambiar_prioridad(tarea):
    """Cambia la prioridad de la tarea recibida (la modifica en el sitio)."""
    anterior = tarea["prioridad"]
    tarea["prioridad"] = pedir_opcion("Nueva prioridad: ", PRIORIDADES)
    print(f"Prioridad de '{tarea['titulo']}': {anterior} -> {tarea['prioridad']}")

def marcar_completada(tarea):
    """Marca la tarea como completada tras confirmar."""
    if tarea["completada"]:
        print("La tarea ya estaba completada.")
    elif confirmar(f"Marcar '{tarea['titulo']}' como completada?"):
        tarea["completada"] = True
        print("Tarea completada. Buen trabajo.")

def main():
    """Ejecuta el bucle principal de la aplicacion."""
    tarea = None                    # None significa 'todavia no hay tarea'
    while True:
        mostrar_menu()
        opcion = pedir_opcion("Elige una opcion (1-5): ", OPCIONES)
        if opcion == "5":
            if confirmar("Seguro que quieres salir?"):
                break
        elif opcion == "1":
            if tarea is None or confirmar(f"Ya existe '{tarea['titulo']}'. Reemplazar?"):
                tarea = registrar_tarea()
                print("Tarea registrada correctamente.")
        elif tarea is None:
            print("Todavia no hay ninguna tarea registrada.")
        elif opcion == "2":
            mostrar_ficha(tarea)
        elif opcion == "3":
            cambiar_prioridad(tarea)
        elif opcion == "4":
            marcar_completada(tarea)
        pausar()

if __name__ == "__main__":
    main()

Lo que ha cambiado respecto a la v0.7, y por qué importa:

  • mostrar_ficha(tarea) tiene un parámetro en vez de cinco. Las seis variables sueltas que viajaban juntas de función en función eran, efectivamente, una sola cosa; ahora tienen nombre y forma.
  • Añadir un campo ya no obliga a tocar ninguna firma. Hemos incorporado "etiqueta" sin cambiar la cabecera de nada. Con la v0.7 habría supuesto un parámetro más en cuatro funciones.
  • Las funciones de acción modifican el diccionario y no devuelven nada. cambiar_prioridad(tarea) funciona porque el diccionario es mutable y se pasa por referencia, como las listas de 05-01. El nombre anuncia claramente que modifica.
  • ETIQUETAS es un conjunto, porque son valores únicos sin orden cuyo uso es comprobar pertenencia y ofrecer opciones; se pasa sorted(ETIQUETAS) a pedir_opcion para que el usuario las vea siempre igual. Y tarea is None sustituye a la bandera hay_tarea: un dato menos que mantener sincronizado.

Sigue sin resolverse lo importante: solo hay una tarea. Pero ahora que una tarea es un valor único, guardarlas todas será tan fácil como meterlas en una lista.

Errores Comunes y Consejos

KeyError al leer una clave que no existe. Comprueba con in o usa .get(clave, defecto). Y recuerda que las claves distinguen mayúsculas: "Titulo" y "titulo" son distintas; elige una convención (minúsculas) y respétala. Escribir mal una clave al asignar. tarea["responsble"] = "Luis" no da error: crea una clave nueva. El síntoma aparece después, cuando tarea["responsable"] devuelve el valor viejo. Es el error más traicionero del tipo.

Creer que {} es un conjunto vacío. Es un diccionario vacío; el conjunto vacío es set(). Y no esperes orden en un conjunto: si necesitas mostrarlo ordenado, sorted(conjunto).

Usar una lista como clave. TypeError: unhashable type. Si necesitas una clave compuesta, usa una tupla (05-04).

Modificar un diccionario mientras lo recorres. Añadir o borrar claves dentro del for lanza RuntimeError; recorre una copia (for c in list(d.keys()):) o construye un diccionario nuevo. Consejo: usa el diccionario para campos, no como base de datos improvisada. Si acabas con claves "tarea1", "tarea2", "tarea3", lo que quieres es una lista de diccionarios (05-04).

Ejercicios

Ejercicio 1: Predecir la salida

Di qué imprime cada línea y qué errores se producen.

tarea = {"titulo": "Cartel feria", "dias": 3}
print(tarea.get("responsable", "sin asignar"))
tarea["dias"] = tarea["dias"] + 2
tarea.setdefault("dias", 99)
tarea.setdefault("prioridad", "media")
print(tarea)
print("Cartel feria" in tarea)
print(len({"a", "b", "a", "b"}))
print({"a", "b"} & {"b", "c"}, {"a", "b"} - {"b", "c"})
print(tarea["completada"])

Ejercicio 2: Resumen del estudio

Dada la lista de responsables de las tareas abiertas, escribe un programa que imprima: cuántas tareas lleva cada persona (ordenadas de más a menos), quién lleva más, y qué miembros del equipo EQUIPO = ("Marta", "Luis", "Nuria") no tienen ninguna tarea asignada. Usa un diccionario para contar y un conjunto para la última pregunta.

asignaciones = ["Marta", "Luis", "Marta", "Marta", "Luis"]

Ejercicio 3: Ficha con diccionario

Escribe crear_tarea(titulo, responsable, prioridad, dias) que devuelva el diccionario de una tarea con completada en False, y resumen(tarea) que devuelva una cadena de una línea con el título, el responsable, la prioridad en mayúsculas, los días y [X] o [ ] según el estado. Añade aplicar_cambios(tarea, cambios) que reciba un diccionario de cambios y los aplique, avisando si alguna clave no existía en la tarea.

Soluciones

Solución 1.

sin asignar
{'titulo': 'Cartel feria', 'dias': 5, 'prioridad': 'media'}
False
2
{'b'} {'a'}
KeyError: 'completada'

.get con defecto no falla. tarea["dias"] + 2 deja 5, y setdefault("dias", 99) no lo toca porque la clave ya existe; setdefault("prioridad", ...) sí la crea. "Cartel feria" in tarea es False porque in mira claves, no valores. El conjunto {"a","b","a","b"} tiene 2 elementos. La intersección es {'b'} y la diferencia {'a'}. Y la última línea lanza KeyError porque nunca se creó esa clave: con tarea.get("completada", False) habría devuelto False.

Solución 2.

EQUIPO = ("Marta", "Luis", "Nuria")
asignaciones = ["Marta", "Luis", "Marta", "Marta", "Luis"]

conteo = {}
for nombre in asignaciones:
    conteo[nombre] = conteo.get(nombre, 0) + 1

for nombre, total in sorted(conteo.items(), key=lambda p: p[1], reverse=True):
    print(f"{nombre:<10}{total:>3} tareas")
print("Quien lleva mas:", max(conteo, key=conteo.get))

sin_tareas = set(EQUIPO) - set(asignaciones)
print("Sin tareas:", ", ".join(sorted(sin_tareas)) or "nadie")

Imprime «Marta 3 tareas», «Luis 2 tareas», «Quien lleva mas: Marta» y «Sin tareas: Nuria». La última parte es el ejemplo perfecto de para qué sirve un conjunto: set(EQUIPO) - set(asignaciones) responde «quién está en el equipo y no en las asignaciones» en una sola expresión, sin bucles ni banderas. Con listas habría hecho falta recorrer el equipo comprobando cada nombre.

Solución 3.

def crear_tarea(titulo, responsable, prioridad, dias):
    """Devuelve el diccionario de una tarea nueva, pendiente por defecto."""
    return {"titulo": titulo, "responsable": responsable,
            "prioridad": prioridad, "dias": dias, "completada": False}

def resumen(tarea):
    """Devuelve una linea con los datos principales de la tarea."""
    marca = "[X]" if tarea["completada"] else "[ ]"
    return (f"{marca} {tarea['titulo']:<24}{tarea['responsable']:>8}"
            f"{tarea['prioridad'].upper():>8}{tarea['dias']:>4}d")

def aplicar_cambios(tarea, cambios):
    """Aplica los cambios a la tarea y avisa de las claves desconocidas."""
    for clave, valor in cambios.items():
        if clave not in tarea:
            print(f"Aviso: '{clave}' no es un campo de la tarea; se ignora.")
            continue
        tarea[clave] = valor

t = crear_tarea("Cartel feria", "Marta", "alta", 3)
aplicar_cambios(t, {"dias": 4, "completada": True, "color": "rojo"})
print(resumen(t))       # [X] Cartel feria                Marta    ALTA   4d

Lo interesante de aplicar_cambios es la comprobación if clave not in tarea. Sin ella, un error tipográfico en el nombre del campo añadiría una clave nueva en silencio —el error traicionero de la sección de consejos— y nunca sabrías por qué el cambio «no ha funcionado». Fíjate también en que la función modifica el diccionario recibido y no devuelve nada: es legítimo porque el nombre lo anuncia.

Conclusión

El diccionario guarda pares clave → valor y sustituye la posición por el nombre: se crea con {} o dict(), se lee con d[clave] —que lanza KeyError si falta— o con .get(clave, defecto) —que no falla—, se amplía y se modifica con la misma asignación, y se recorre con .keys(), .values() y sobre todo .items(). .update() fusiona, .setdefault() garantiza sin machacar, las claves deben ser inmutables y únicas, y la comprensión de diccionario y el patrón de conteo de frecuencias con .get(x, 0) + 1 resuelven en tres líneas cosas que antes costaban veinte. El conjunto guarda elementos únicos sin orden, no se indexa, se crea con {...} o set() —nunca con {}, que es un diccionario—, y ofrece unión |, intersección &, diferencia - y diferencia simétrica ^, además de ser la estructura correcta para deduplicar y para comprobar pertenencia.

TareaFácil está en la v0.9 y una tarea es por fin una sola cosa: un diccionario con titulo, responsable, prioridad, dias, etiqueta y completada. mostrar_ficha(tarea) tiene un parámetro en lugar de cinco, añadir un campo ya no obliga a tocar ninguna firma, y las etiquetas del estudio viven en un conjunto. Queda un último ensamblaje: la v0.8 sabía llevar muchas tareas pero con listas paralelas frágiles, y la v0.9 sabe representar una tarea perfectamente pero solo lleva una. Juntar ambas ideas —una lista de diccionarios— es lo que hace Tuplas y estructuras anidadas, donde además conocerás la tupla, entenderás por fin qué devolvía return a, b desde 04-02 y aprenderás a leer expresiones como agenda[0]["responsable"] sin pestañear.

Fundamentos de la Programación

Módulo 1: Introducción a la Programación

Módulo 2: Conceptos Básicos

Módulo 3: Estructuras de Control

Módulo 4: Funciones y Procedimientos

Módulo 5: Estructuras de Datos

Módulo 6: Algoritmos Básicos

Módulo 7: Objetos y Organización del Código

Módulo 8: Buenas Prácticas y Herramientas

Módulo 9: Proyecto Final y Cierre del Curso

© Copyright 2026. Todos los derechos reservados