Las tres funciones que escribimos en la lección anterior —mostrar_cabecera(), mostrar_menu() y pausar()— tienen una limitación evidente: hacen siempre exactamente lo mismo. mostrar_menu() pinta ese menú y no otro; pausar() muestra ese mensaje y no otro. Son útiles, pero rígidas. Y por eso mismo no nos han servido para el problema de fondo de TareaFácil: los cuatro bucles de validación repetidos, que se parecen entre sí pero no son idénticos —cada uno tiene su mensaje, sus valores aceptables y su tipo de dato.
Para extraer eso hacen falta las dos piezas que faltan. La primera, decirle cosas a la función cuando la llamas: eso son los parámetros, y convierten una función que hace una cosa en una función que hace una familia entera de cosas. La segunda, que la función te entregue un resultado: eso es return, y es lo que permite usar el valor calculado en cualquier sitio, guardarlo en una variable o pasarlo a otra función.
Con parámetros y return, una función deja de ser un atajo para escribir menos y se convierte en una pieza de construcción: algo con entradas y salidas bien definidas que puedes montar con otras piezas. Esta es la lección más importante del módulo.
Contenido
- Parámetros y argumentos: dos palabras para dos cosas
- Argumentos posicionales y argumentos por nombre
- Valores por defecto
return: devolver un valor y terminar- Varios
returny elreturntemprano - Devolver más de un valor
- ¿Imprimir dentro o devolver?
*argsy**kwargs, para reconocerlos- TareaFácil: las funciones de validación
- Errores comunes y consejos
- Ejercicios
- Conclusión
- Parámetros y argumentos: dos palabras para dos cosas
Un parámetro es el nombre que aparece entre los paréntesis de la definición; un argumento es el valor concreto que se entrega entre los paréntesis de la llamada.
def saludar(nombre): # 'nombre' es el PARAMETRO
print(f"Hola, {nombre}. Bienvenida a Estudio Alba.")
saludar("Marta") # "Marta" es el ARGUMENTO
saludar("Luis")Lo que ocurre en la llamada es exactamente esto: Python crea, dentro de la función, una variable llamada nombre y le asigna el valor "Marta" —una asignación normal y corriente, nombre = "Marta", hecha automáticamente por ti. Esa variable vive solo mientras la función se ejecuta y desaparece al terminar; el porqué exacto es el tema de Ámbito de variables.
| Parámetro | Argumento | |
|---|---|---|
| Dónde aparece | En la definición (def) |
En la llamada |
| Qué es | Un nombre, un hueco por rellenar | Un valor concreto |
| Cuántos hay | Fijos al escribir la función | Uno por cada parámetro, en cada llamada |
| Ejemplo | def saludar(nombre): |
saludar("Nuria") |
Distinguirlos importa porque los mensajes de error de Python hablan en estos términos: llamar a saludar() sin nada produce TypeError: saludar() missing 1 required positional argument: 'nombre', es decir, «falta 1 argumento obligatorio y se llama nombre». Si pasas de más, dirá takes 1 positional argument but 2 were given. En cuanto entiendes el vocabulario, estos errores dejan de dar miedo y pasan a ser instrucciones bastante precisas.
Una función puede tener varios parámetros, separados por comas, y entonces el orden manda:
def presentar_tarea(titulo, responsable, dias):
print(f"{titulo} -> {responsable} ({dias} dias)")
presentar_tarea("Cartel feria del libro", "Nuria", 4)
presentar_tarea("Nuria", 4, "Cartel feria del libro") # valido, pero absurdoLa segunda llamada no da error: Python asigna por posición sin juzgar si tiene sentido. Es un fallo silencioso, de los peores, y la sección siguiente ofrece la defensa.
- Argumentos posicionales y argumentos por nombre
Hasta ahora hemos pasado argumentos posicionales: el primer valor va al primer parámetro, el segundo al segundo, y así. Pero Python permite también nombrar el parámetro al que va cada valor. Se llaman argumentos por nombre o keyword arguments:
presentar_tarea(titulo="Menu de la Panaderia Sole", responsable="Luis", dias=3)
presentar_tarea(responsable="Luis", dias=3, titulo="Menu de la Panaderia Sole")Las dos llamadas hacen exactamente lo mismo: al indicar el nombre, el orden deja de importar. Y de paso, la llamada se lee sola. Compara crear_aviso("Cartel feria", True, False, 3) con crear_aviso("Cartel feria", urgente=True, silencioso=False, dias=3): sin los nombres, para entender la primera tendrías que ir a buscar la definición de crear_aviso; con ellos, la llamada se explica en el sitio donde está.
Se pueden mezclar los dos estilos, con una regla firme: los posicionales van siempre antes que los nombrados.
presentar_tarea("Cartel feria", responsable="Nuria", dias=4) # correcto
presentar_tarea(titulo="Cartel feria", "Nuria", 4) # SyntaxErrorCriterio práctico: usa argumentos por nombre cuando el valor sea un True/False suelto, un número sin unidades evidentes o cuando la función tenga más de dos o tres parámetros; un True desnudo en una llamada es una adivinanza para quien lea el código dentro de un mes.
- Valores por defecto
Un parámetro puede llevar un valor por defecto, que se usa si quien llama no proporciona ese argumento. Se escribe con un = en la definición:
def avisar(mensaje, veces=1):
"""Repite un aviso por pantalla el numero de veces indicado."""
for i in range(veces):
print(f"[AVISO] {mensaje}")
avisar("Revisar el encargo del cliente Vidal") # imprime 1 linea
avisar("Entrega manana", veces=3) # imprime 3 lineasveces se ha vuelto opcional: la primera llamada usa el valor por defecto, 1, y la segunda lo sobrescribe. Así se consigue que una función sea sencilla en el caso normal y flexible en el especial, que es exactamente lo que quieres. Hay una regla sintáctica que Python impone: los parámetros con valor por defecto van al final, después de todos los obligatorios. Es decir, def pedir_texto(mensaje, obligatorio=True): es correcto, pero def pedir_texto(obligatorio=True, mensaje): produce SyntaxError: parameter without a default follows parameter with a default. La razón es de sentido común: si los opcionales pudieran ir en medio, Python no sabría a qué parámetro corresponde el segundo valor de una llamada posicional.
Un aviso que conviene grabar desde hoy, aunque su explicación completa llegue más adelante: no uses como valor por defecto un objeto mutable (una lista, por ejemplo). El valor por defecto se evalúa una sola vez, al definir la función, no en cada llamada; si es mutable y se modifica dentro, esa modificación persiste entre llamadas y produce un comportamiento desconcertante. Con valores inmutables —números, cadenas, booleanos, None— no hay ningún problema, y es lo único que usaremos por ahora. Volveremos a ello cuando conozcas las listas.
return: devolver un valor y terminar
return: devolver un valor y terminarreturn hace dos cosas a la vez, y ambas importan:
- Entrega un valor a quien llamó a la función.
- Termina la función inmediatamente: ninguna línea posterior del cuerpo se ejecuta.
def calcular_coste(dias, tarifa_diaria):
"""Devuelve el coste total de una tarea, sin imprimir nada."""
return dias * tarifa_diaria
coste = calcular_coste(4, 120.0)
print(f"Coste estimado: {coste:.2f} EUR")
print(f"Con IVA: {coste * 1.21:.2f} EUR")Fíjate en lo que ha ganado el programa. calcular_coste no sabe ni le importa qué se hará con el número: lo entrega y se aparta. Quien llama lo guarda en coste, lo formatea y lo reutiliza para el IVA. Una llamada a una función que devuelve valor es una expresión, con todo lo que eso significa: puede ir dentro de un print, de un if, de una operación aritmética o dentro de otra llamada.
print(calcular_coste(2, 90.0)) # dentro de print
if calcular_coste(10, 120.0) > 1000: # dentro de una condicion
print("Presupuesto alto: requiere aprobacion de Marta.")
total = calcular_coste(3, 100.0) + calcular_coste(2, 80.0) # en una sumaY la segunda mitad de return, la de terminar, se ve así: si escribes return 42 y debajo un print("..."), ese print es código muerto y no se ejecuta jamás; los editores suelen marcarlo en gris. Si te aparece uno, o sobra o el return está en el sitio equivocado.
- Varios
return y el return temprano
return y el return tempranoUna función puede tener tantos return como haga falta. Se ejecuta el primero que se alcance, y ahí termina todo:
def etiqueta_de_carga(dias):
"""Devuelve una etiqueta de carga de trabajo segun los dias estimados."""
if dias <= 2:
return "ligera"
if dias <= 5:
return "media"
return "pesada"
print(etiqueta_de_carga(1)) # ligera
print(etiqueta_de_carga(4)) # media
print(etiqueta_de_carga(9)) # pesadaFíjate en que no hay ni elif ni else, y sin embargo funciona: como cada rama termina en return, si la primera condición se cumple la función ya ha terminado y nunca llega a la segunda. Esta es la cláusula de guarda de 03-01 llevada a las funciones: comprobar primero los casos particulares, salir en cuanto se resuelven y dejar el caso general al final, sin indentar. Se lee mucho mejor que un if anidado de tres niveles.
Un return sin valor —solo la palabra— también existe: termina la función y devuelve None. Es útil para abandonar pronto un procedimiento:
def mostrar_ficha(hay_tarea, titulo):
if not hay_tarea:
print("Todavia no hay ninguna tarea registrada.")
return # salimos: no hay nada que pintar
print(f"Ficha de: {titulo}")Cuidado con un despiste habitual: si una rama olvida su return, esa rama devuelve None en silencio, y la función parece correcta hasta que alguien la llama con el valor que cae justo ahí. Comprueba una por una que todas las ramas devuelven algo.
- Devolver más de un valor
Si separas varios valores con comas después de return, la función los devuelve todos a la vez:
def analizar_titulo(texto):
"""Devuelve el titulo normalizado y su numero de caracteres."""
limpio = texto.strip().capitalize()
return limpio, len(limpio)
titulo, longitud = analizar_titulo(" cartel feria del libro ")
print(titulo) # Cartel feria del libro
print(longitud) # 22Lo que ocurre por dentro es que Python empaqueta los dos valores en un único objeto llamado tupla, y la línea titulo, longitud = ... lo desempaqueta en dos variables. Puedes comprobarlo guardando el resultado en una sola variable: resultado = analizar_titulo("cartel feria") deja en resultado el valor ('Cartel feria', 12), cuyo type() es <class 'tuple'>. Las tuplas son el tema de Tuplas y estructuras anidadas; hoy basta con saber que el desempaquetado funciona y que el número de variables a la izquierda debe coincidir con el de valores devueltos, o tendrás un ValueError: too many values to unpack.
Un consejo de diseño: devolver dos valores está bien; devolver cinco es señal de que esa función hace demasiado, o de que esos cinco valores son en realidad un solo concepto que todavía no tiene nombre —justo lo que le pasa a TareaFácil con titulo, responsable, prioridad, dias y completada, y que se resolverá en los módulos 5 y 7.
- ¿Imprimir dentro o devolver?
Esta es la decisión de diseño más frecuente al escribir una función, y la respuesta correcta casi siempre es la misma. Compara una coste_impreso(dias, tarifa) cuyo cuerpo es print(f"Coste: {dias * tarifa:.2f} EUR") con una coste_devuelto(dias, tarifa) cuyo cuerpo es return dias * tarifa:
| Criterio | Imprimir dentro | Devolver el valor |
|---|---|---|
| ¿Se puede reutilizar el resultado? | No: se ha ido a la pantalla | Sí: es un valor como cualquier otro |
| ¿Sirve para calcular otra cosa? | No | Sí (total = a() + b()) |
| ¿Se puede probar sin mirar la pantalla? | No | Sí: se compara el valor devuelto |
| ¿Sirve para un informe, un fichero, otra pantalla? | No: el formato está fijado | Sí: decide quien llama |
| ¿Quién decide el formato? | La función, para siempre | Quien la usa, en cada caso |
Devolver es casi siempre mejor, y la razón de fondo es que una función que imprime ha tomado por ti una decisión que no le correspondía. coste_impreso solo sirve para mostrar euros con dos decimales en la pantalla; el día que quieras el coste para sumarlo, compararlo o guardarlo, no te vale. coste_devuelto sirve para todo eso y también para imprimir, porque siempre puedes hacer print(f"{coste_devuelto(4, 120):.2f} EUR"). La consecuencia práctica es una regla que retomaremos en Descomponer un programa en funciones: las funciones que calculan no imprimen; las que presentan no calculan. Y la que sí debe imprimir es precisamente la que dialoga con el usuario, como pedir_texto, que necesita mostrar el mensaje para poder preguntar.
*args y **kwargs, para reconocerlos
*args y **kwargs, para reconocerlosVerás estas dos formas por todas partes en el código ajeno, así que conviene reconocerlas aunque no las uses todavía.
Un parámetro precedido de un asterisco recoge todos los argumentos posicionales sobrantes, y uno precedido de dos asteriscos recoge todos los argumentos por nombre sobrantes:
def anunciar(*mensajes):
"""Imprime por pantalla cuantos mensajes se le pasen."""
for mensaje in mensajes:
print(f"[AVISO] {mensaje}")
anunciar("Reunion a las 10", "Entregar el cartel", "Llamar a Vidal")La versión con dos asteriscos se define igual —def registrar_evento(accion, **detalles):— y se llama con registrar_evento("alta_tarea", responsable="Nuria", dias=4); dentro, detalles asocia cada nombre con su valor.
Los nombres args y kwargs son pura convención (de arguments y keyword arguments); lo que hace el trabajo son los asteriscos. Por dentro, *args es una tupla y **kwargs un diccionario, dos estructuras del módulo 5: no necesitas dominarlas hoy, solo reconocer que def funcion(*args, **kwargs): acepta cualquier cosa. Úsalo con moderación: una función con parámetros explícitos se documenta y se entiende mucho mejor.
- TareaFácil: las funciones de validación
Llegó el momento de matar la repetición. Los cuatro bucles de validación de la v0.6 se reducen a tres funciones parametrizadas, más una que clasifica.
def pedir_texto(mensaje, obligatorio=True):
"""Pide un texto por teclado y lo devuelve ya limpio de espacios."""
valor = input(mensaje).strip()
while obligatorio and valor == "":
valor = input("No puede estar vacio. " + mensaje).strip()
return valor
def pedir_opcion(mensaje, opciones):
"""Pide un valor hasta que este dentro de las opciones admitidas."""
valor = input(mensaje).strip().lower()
while valor not in opciones:
valor = input(f"No valido. Admitidos: {', '.join(opciones)}. ").strip().lower()
return valor
def pedir_entero(mensaje, minimo, maximo):
"""Pide un entero y no lo devuelve hasta que este entre minimo y maximo."""
texto = input(mensaje).strip()
while not texto.isdigit() or not minimo <= int(texto) <= maximo:
texto = input(f"Debe ser un entero entre {minimo} y {maximo}. ").strip()
return int(texto)
def clasificar_urgencia(prioridad, dias):
"""Devuelve la etiqueta de urgencia que corresponde a la tarea."""
if prioridad == "alta" and dias <= 2:
return "CRITICA"
if prioridad == "alta":
return "Urgente"
if prioridad == "media" and dias <= 3:
return "Atencion"
return "Normal"Y así se usan desde el programa principal:
PRIORIDADES = ("alta", "media", "baja")
EQUIPO = ("marta", "luis", "nuria")
titulo = pedir_texto("Titulo : ")
responsable = pedir_opcion("Responsable : ", EQUIPO).capitalize()
prioridad = pedir_opcion("Prioridad : ", PRIORIDADES)
dias = pedir_entero("Dias (1-365) : ", 1, 365)
print(f"{titulo} [{clasificar_urgencia(prioridad, dias)}]")Repasa lo que ha pasado, porque es mucho:
- Cuatro bucles de validación repetidos han quedado en tres funciones, cada una escrita una sola vez. Si mañana quieres cambiar el mensaje de reproche, lo cambias en un sitio y cambia en todos.
- El programa principal se lee como una lista de intenciones. La mecánica del
whileha desaparecido de la vista sin desaparecer del programa. pedir_opcionsirve para el responsable, para la prioridad y para la opción del menú, tres cosas que en la v0.6 eran tres bloques distintos. Ese es el poder de un parámetro:opcionesconvierte una función en una familia de funciones. Yobligatorio=Truedeja abierta la puerta a pedir un dato opcional el día que haga falta, sin tocar ninguna llamada actual.clasificar_urgenciadevuelve, no imprime. Por eso podemos meterla dentro de una f-string, y mañana podríamos usarla también para decidir un color o para ordenar tareas.
Fíjate además en un detalle de reparto de responsabilidades: pedir_opcion normaliza a minúsculas, pero el .capitalize() del responsable se aplica fuera, en quien llama. La función se ocupa de validar; el formato de presentación es asunto del programa. Y observa que pedir_entero devuelve ya un int, no un texto: convierte una vez, en el único sitio donde consta que el dato es convertible, aplicando el marco «presencia, tipo, dominio» de 02-04 de principio a fin.
Errores Comunes y Consejos
Olvidar el return. La función calcula bien y devuelve None. Síntoma: TypeError: unsupported operand type(s) for +: 'NoneType' and 'int' un poco más adelante, en la línea que intenta usar el resultado.
Confundir print con return. Escribir print(dias * tarifa) dentro de la función y luego coste = calcular_coste(...) deja coste a None, aunque el número correcto haya salido por pantalla. Ver el valor en la consola no es lo mismo que tenerlo.
Número de argumentos incorrecto. TypeError: ... missing 1 required positional argument o takes 2 positional arguments but 3 were given. El mensaje dice cuántos esperaba y cuántos recibió: cuéntalos en la definición.
Poner un parámetro con defecto antes de uno obligatorio. SyntaxError inmediato: los opcionales van al final, siempre. Y no uses una lista como valor por defecto: def f(x, historial=[]) comparte la misma lista entre todas las llamadas. Aún no manejas listas; recuérdalo para cuando llegue 05-01.
Poner código después del return. No se ejecuta jamás. Si lo has escrito, es que querías otra cosa.
Consejo: escribe primero la llamada. Antes de implementar, escribe cómo te gustaría usarla: dias = pedir_entero("Dias: ", 1, 365). Si esa línea se lee bien, la firma es buena; si te obliga a explicarla, cámbiala antes de escribir el cuerpo.
Consejo: dos o tres parámetros es lo cómodo. A partir de cuatro, la llamada se vuelve un jeroglífico y conviene usar argumentos por nombre —o preguntarse si esa función no estará haciendo dos cosas.
Ejercicios
Ejercicio 1: Predecir y explicar
Di qué imprime cada bloque y por qué. Hay un fallo de diseño escondido.
def media_dias(a, b):
print((a + b) / 2)
def media_dias_2(a, b):
return (a + b) / 2
x = media_dias(4, 6)
y = media_dias_2(4, 6)
print(x)
print(y)
print(y * 2)Ejercicio 2: Presupuesto del estudio
Escribe tres funciones con docstring de una línea:
coste_tarea(dias, tarifa=110.0): devuelve el coste de una tarea; la tarifa por defecto es 110 euros al día.aplicar_descuento(coste, porcentaje=0): devuelve el coste con el descuento aplicado. Si el porcentaje no está entre 0 y 50, devuelve el coste sin tocar.resumen_presupuesto(titulo, dias, tarifa=110.0, descuento=0): usa las dos anteriores y devuelve dos valores: el texto de resumen y el importe final.
Después, calcula el presupuesto de «Cartel feria del libro», 4 días, tarifa por defecto y 10 % de descuento, e imprime el resumen desde el programa principal (no desde dentro de las funciones).
Ejercicio 3: Reparar la función
Esta función tiene tres defectos. Encuéntralos, explícalos y reescríbela.
def clasificar(dias, prioridad="alta", umbral):
if prioridad == "alta":
print("Urgente")
elif dias > 10:
return "Largo plazo"
return "Normal"
print("Clasificacion terminada")Soluciones
Solución 1.
media_dias(4, 6) imprime 5.0 desde dentro y devuelve None, así que x vale None. media_dias_2(4, 6) no imprime nada pero devuelve 5.0, que se guarda en y. Luego print(x) muestra None, print(y) muestra 5.0 y print(y * 2) muestra 10.0. El fallo de diseño es media_dias: al imprimir en lugar de devolver, su resultado no se puede reutilizar. Comprueba que x * 2 daría TypeError, mientras que y * 2 funciona sin problema.
Solución 2.
def coste_tarea(dias, tarifa=110.0):
"""Devuelve el coste bruto de una tarea de la duracion indicada."""
return dias * tarifa
def aplicar_descuento(coste, porcentaje=0):
"""Devuelve el coste con descuento; ignora porcentajes fuera de 0-50."""
if not 0 <= porcentaje <= 50:
return coste
return coste * (1 - porcentaje / 100)
def resumen_presupuesto(titulo, dias, tarifa=110.0, descuento=0):
"""Devuelve el texto de resumen y el importe final del presupuesto."""
bruto = coste_tarea(dias, tarifa)
final = aplicar_descuento(bruto, descuento)
texto = f"{titulo}: {dias} dias x {tarifa:.2f} = {bruto:.2f} EUR"
return texto, final
detalle, importe = resumen_presupuesto("Cartel feria del libro", 4, descuento=10)
print(detalle) # ... 4 dias x 110.00 = 440.00 EUR
print(f"Total con descuento: {importe:.2f} EUR") # Total con descuento: 396.00 EURTres detalles: descuento=10 se pasa por nombre para saltarse tarifa sin repetir su valor por defecto; ninguna de las tres funciones imprime, así que las tres son reutilizables; y la comparación encadenada 0 <= porcentaje <= 50 es la de 03-01, dentro de una guarda con return temprano.
Solución 3.
| Defecto | Explicación |
|---|---|
umbral sin defecto va detrás de prioridad="alta" |
SyntaxError al cargar el fichero |
| La primera rama imprime en vez de devolver | La función devuelve None cuando la prioridad es alta |
El print final está detrás de un return |
Código muerto: no se ejecuta nunca |
def clasificar(dias, umbral, prioridad="alta"):
"""Devuelve la clasificacion de una tarea segun prioridad y duracion."""
if prioridad == "alta":
return "Urgente"
if dias > umbral:
return "Largo plazo"
return "Normal"El parámetro obligatorio pasa delante, la primera rama devuelve como las demás, el código muerto desaparece y de paso umbral se usa de verdad, que en el original ni siquiera aparecía en el cuerpo.
Conclusión
Tus funciones ya dialogan. Los parámetros son los huecos que declaras en el def; los argumentos son los valores que entregas en la llamada, y pueden ir por posición o por nombre, siendo esta segunda forma la que hace legibles las llamadas con booleanos o con muchos datos. Un parámetro puede llevar valor por defecto, y entonces se vuelve opcional, con la regla de que los opcionales van al final y nunca son mutables. Del otro lado, return entrega un valor y termina la función en el acto; puede haber varios en distintas ramas, sirve como cláusula de guarda para salir pronto, y admite varios valores separados por comas que se desempaquetan al recibirlos. Y la regla que ordena todo lo demás: una función que calcula devuelve, no imprime, porque el valor sirve para todo y la pantalla solo para mirar.
TareaFácil ha dado el salto más grande de su historia. Los cuatro bucles de validación duplicados se han convertido en pedir_texto, pedir_opcion y pedir_entero, tres funciones que se escriben una vez y se usan desde cualquier sitio, y clasificar_urgencia calcula una etiqueta sin ensuciar la pantalla. El programa principal ha pasado de ser una maraña de while a una lista de intenciones legible. Pero al reunir todas esas funciones en un fichero aparecen preguntas nuevas y muy concretas. ¿Por qué pedir_opcion puede leer la constante PRIORIDADES sin que se la pasemos? ¿Qué pasaría si dentro de una función asignase titulo = "otro": cambiaría el titulo del programa principal? ¿Y por qué la variable valor de pedir_texto no existe fuera? Todo eso es una sola cuestión —dónde vive cada nombre— y tiene un nombre propio: el ámbito. Es la próxima lección: Ámbito de variables.
Fundamentos de la Programación
Módulo 1: Introducción a la Programación
- ¿Qué es la programación?
- Historia de la programación
- Lenguajes de programación
- Entornos de desarrollo
- Del problema al algoritmo
Módulo 2: Conceptos Básicos
- Variables y tipos de datos
- Operadores y expresiones
- Entrada y salida de datos
- Conversión de tipos y validación de datos
Módulo 3: Estructuras de Control
Módulo 4: Funciones y Procedimientos
- Definición y uso de funciones
- Parámetros y retorno de valores
- Ámbito de variables
- Descomponer un programa en funciones
- Funciones como valores: lambda y orden superior
Módulo 5: Estructuras de Datos
- Listas y arreglos
- Cadenas de caracteres
- Diccionarios y conjuntos
- Tuplas y estructuras anidadas
- Guardar datos en archivos: texto, CSV y JSON
Módulo 6: Algoritmos Básicos
Módulo 7: Objetos y Organización del Código
- De los datos a los objetos: clases e instancias
- Atributos, métodos y constructor
- Colecciones de objetos
- Módulos, paquetes e importaciones
Módulo 8: Buenas Prácticas y Herramientas
- Documentación y comentarios
- Depuración y manejo de errores
- Control de versiones
- Pruebas automatizadas
- Estilo, legibilidad y refactorización
