for con rangos

# =============================================================================
# BUCLES FOR CON RANGE() EN PYTHON
# =============================================================================
# Hasta ahora hemos usado el bucle "for" para recorrer listas y cadenas.
# En este programa aprenderemos a usarlo con range(), que nos permite repetir
# algo un número exacto de veces o recorrer rangos de números.
#
# range() genera una secuencia de números. Sus formas principales son:
#
#   range(n)          → genera: 0, 1, 2, ..., n-1        (n números desde 0)
#   range(a, b)       → genera: a, a+1, ..., b-1         (sin incluir b)
#   range(a, b, paso) → genera: a, a+paso, a+paso*2, ... (de a en a saltos)
#
# En este archivo veremos:
#   1. range() básico para contar y sumar
#   2. range() para recorrer cadenas
#   3. Funciones que usan range() para calcular sumas
#   4. Dibujar figuras con emojis usando bucles
#   5. Árboles de Navidad cada vez más elaborados
# =============================================================================

import random
# Importamos el módulo "random" que usaremos al final para generar números
# aleatorios. Un módulo es una colección de funciones ya escritas que podemos
# usar directamente. "random" viene incluido con Python, no hay que instalarlo.


# =============================================================================
# EJEMPLO 1: range() BÁSICO — CONTAR DEL 0 AL 6
# =============================================================================

for i in range(7):
    # range(7) genera los números: 0, 1, 2, 3, 4, 5, 6
    # OJO: genera 7 números pero el último es 6, no 7. Nunca incluye el límite.
    # En cada vuelta, "i" toma el siguiente valor de la secuencia.
    # "i" es el nombre más habitual para un contador en programación.

    print(i)
    # Imprime el valor actual de i.
    # Salida:
    #   0
    #   1
    #   2
    #   3
    #   4
    #   5
    #   6


# =============================================================================
# EJEMPLO 2: SUMAR TODOS LOS NÚMEROS DEL 0 AL 100
# =============================================================================

suma = 0
# Acumulador: empezamos en 0 y vamos añadiendo números.
# Siempre se inicializa a 0 ANTES del bucle.

for i in range(101):
    # range(101) genera: 0, 1, 2, 3, ..., 100
    # Necesitamos 101 (no 100) porque range() no incluye el número final.
    # PREGUNTA FRECUENTE: ¿por qué no range(100)?
    #   range(100) → llega hasta 99. range(101) → llega hasta 100. ✓

    suma = suma + i
    # Añadimos el valor actual de i a la suma acumulada.
    # ALTERNATIVA más corta: suma += i
    # Paso a paso: suma=0 → 0+0=0 → 0+1=1 → 1+2=3 → 3+3=6 → ... → 5050

print(suma)
# Imprime 5050. Es la suma de los números del 0 al 100.
# Existe una fórmula matemática directa: n*(n+1)/2 → 100*101/2 = 5050


# =============================================================================
# EJEMPLO 3: range() PARA RECORRER UNA CADENA POR POSICIÓN
# =============================================================================

cadena = "hola"

for i in range(len(cadena)):
    # len(cadena) → devuelve 4 (la cadena "hola" tiene 4 caracteres)
    # range(4)    → genera: 0, 1, 2, 3
    # Así recorremos los índices válidos de la cadena.

    print(i, cadena[i])
    # cadena[i] → accede a la letra en la posición i
    # cadena[0] → "h"
    # cadena[1] → "o"
    # cadena[2] → "l"
    # cadena[3] → "a"
    #
    # Salida:
    #   0 h
    #   1 o
    #   2 l
    #   3 a
    #
    # RECUERDA: esta es la "Manera 2" del tema anterior. La manera más pythónica
    # es con enumerate(), pero esta es útil cuando necesitas el índice para
    # acceder a posiciones vecinas (cadena[i-1], cadena[i+1]...).


# =============================================================================
# FUNCIÓN suma_hasta() — SUMAR DEL 0 HASTA UN LÍMITE
# =============================================================================
# Generalizamos el ejemplo 2: en lugar de sumar siempre hasta 100,
# dejamos que el usuario elija el límite.
#
# suma_hasta(10)  → 0+1+2+...+10  = 55
# suma_hasta(100) → 0+1+2+...+100 = 5050
# =============================================================================

def suma_hasta(limite):
    # Parámetro "limite": el número hasta el que queremos sumar.

    suma = 0
    # Acumulador LOCAL a la función. Se reinicia a 0 en cada llamada.

    for i in range(limite + 1):
        # limite + 1 porque range() no incluye el último número.
        # Si limite=10 → range(11) → genera 0, 1, 2, ..., 10 ✓
        # Sin el +1 → range(10) → llegaríamos solo hasta 9 ✗

        suma = suma + i
        # Acumulamos cada número en la suma.

    return suma
    # Devolvemos el resultado al lugar donde se llamó la función.

print(suma_hasta(10))   # → 55
print(suma_hasta(100))  # → 5050


# =============================================================================
# FUNCIÓN suma_entre() — SUMAR ENTRE DOS NÚMEROS
# =============================================================================
# Ahora también elegimos desde dónde empezamos a sumar.
# Usamos la forma range(inicio, final) que empieza en "inicio".
#
# suma_entre(10, 20) → 10+11+12+...+20 = 165
# suma_entre(2, 5)   → 2+3+4+5         = 14
# =============================================================================

def suma_entre(inicio, final):
    # Dos parámetros: "inicio" (desde dónde) y "final" (hasta dónde).

    suma = 0

    for i in range(inicio, final + 1):
        # range(inicio, final+1) genera: inicio, inicio+1, ..., final
        # El +1 sigue siendo necesario para incluir "final".
        # Ejemplo: range(10, 21) → 10, 11, 12, ..., 20 ✓
        # Sin +1: range(10, 20) → 10, 11, ..., 19 — nos falta el 20 ✗

        suma = suma + i

    return suma

print(suma_entre(10, 20))  # → 165
print(suma_entre(2, 5))    # → 14


# =============================================================================
# TRIÁNGULO DE EMOJIS
# =============================================================================
# Usamos range() para controlar cuántos emojis imprimimos en cada línea.
# En la línea 0 → 0 emojis, línea 1 → 1 emoji, ..., línea n → n emojis.
# =============================================================================

tamanyo = 6

for i in range(tamanyo + 1):
    # range(tamanyo+1) → range(7) → genera 0, 1, 2, 3, 4, 5, 6
    # Con tamanyo+1 incluimos la última fila completa.

    print("🟢" * i)
    # "🟢" * i repite el emoji i veces.
    # "🟢" * 0 → ""      (línea vacía)
    # "🟢" * 1 → "🟢"
    # "🟢" * 2 → "🟢🟢"
    # "🟢" * 6 → "🟢🟢🟢🟢🟢🟢"
    # Salida:
    #
    # 🟢
    # 🟢🟢
    # 🟢🟢🟢
    # 🟢🟢🟢🟢
    # 🟢🟢🟢🟢🟢
    # 🟢🟢🟢🟢🟢🟢


# =============================================================================
# FUNCIÓN arbolito() — TRIÁNGULO COMO FUNCIÓN REUTILIZABLE
# =============================================================================

def arbolito(tamanyo):
    # Exactamente el mismo código que arriba, pero dentro de una función.
    # Así podemos llamarla con cualquier tamaño sin reescribir el bucle.

    for i in range(tamanyo + 1):
        print("🟢" * i)

arbolito(10)   # Dibuja un triángulo de 10 filas
# RETO: ¿qué pasa si llamas a arbolito(0)? ¿Y a arbolito(1)?


# =============================================================================
# FUNCIÓN arbolito_puro() — DEVUELVE EL ÁRBOL COMO TEXTO (SIN IMPRIMIR)
# =============================================================================
# Diferencia clave respecto a arbolito():
#   arbolito()      → imprime directamente (no devuelve nada útil)
#   arbolito_puro() → construye el texto y lo devuelve con return
#
# ¿Por qué es mejor devolver el texto?
# Porque podemos guardarlo, modificarlo, enviarlo por email, guardarlo
# en un archivo... En cambio, lo que se imprime con print() se pierde.
# =============================================================================

def arbolito_puro(tamanyo):
    resultado = ""
    # Cadena vacía donde iremos construyendo el árbol línea a línea.

    for i in range(tamanyo + 1):
        resultado += "🟢" * i + "\n"
        # Añadimos los emojis de esta fila MÁS "\n" (salto de línea).
        # "\n" es el carácter especial que representa "pulsar Enter".
        # Sin "\n" todo saldría en una sola línea.
        # ALTERNATIVA: resultado = resultado + "🟢" * i + "\n"

    return resultado
    # Devolvemos todo el árbol como una sola cadena con saltos de línea.

print(arbolito_puro(4))
# Al hacer print() de una cadena con "\n", cada "\n" se convierte en
# un salto de línea visible en pantalla.
# Salida:
#
# 🟢
# 🟢🟢
# 🟢🟢🟢
# 🟢🟢🟢🟢


# =============================================================================
# FUNCIÓN arbol_guay() — ÁRBOL CENTRADO CON FORMA DE TRIÁNGULO INVERTIDO
# =============================================================================
# Ahora mejoramos el árbol:
#   - Empieza en 1 (no en 0) para que la primera fila tenga 1 bola
#   - La primera fila tiene una bola roja (la estrella de la cima)
#   - Cada fila tiene un número IMPAR de bolas: 1, 3, 5, 7...
#   - Añadimos espacios a la izquierda para que quede centrado
#
# Con tamanyo=4, fila por fila:
#   i=1: espacios=3, bolas=1  →  "      🔴"
#   i=2: espacios=2, bolas=3  →  "    🟢🟢🟢"
#   i=3: espacios=1, bolas=5  →  "  🟢🟢🟢🟢🟢"
#   i=4: espacios=0, bolas=7  →  "🟢🟢🟢🟢🟢🟢🟢"
# =============================================================================

def arbol_guay(tamanyo):

    for i in range(1, tamanyo + 1):
        # range(1, tamanyo+1) → empieza en 1, no en 0.
        # Con tamanyo=6 → genera: 1, 2, 3, 4, 5, 6
        # Empezamos en 1 para que la primera fila tenga al menos 1 bola.

        bola = "🟢"
        # Por defecto, todas las bolas son verdes.

        if i == 1:
            bola = "🔴"
            # La primera fila (i=1) es la cima del árbol → bola roja (estrella).

        espacios = tamanyo - i
        # Cuántos espacios dobles ponemos a la izquierda para centrar.
        # Cuanto más arriba (i pequeño), más espacios → la cima queda centrada.
        # i=1 → espacios=5 (muchos, está en la cima)
        # i=6 → espacios=0 (ninguno, es la base)

        bolas = 2 * i - 1
        # Fórmula para obtener números impares: 1, 3, 5, 7, 9, 11...
        # i=1 → 2*1-1=1   i=2 → 2*2-1=3   i=3 → 2*3-1=5
        # Los árboles de Navidad tienen filas con número impar de bolas
        # para que queden simétricas.

        print("  " * espacios + bola * bolas)
        # "  " * espacios → espacios dobles para el centrado (2 espacios cada uno)
        # bola * bolas    → repite la bola el número de veces calculado
        # Los concatenamos con + para formar la línea completa.


# =============================================================================
# FUNCIÓN arbol_decorado() — ÁRBOL CON DECORACIONES ALEATORIAS
# =============================================================================
# La versión más completa. Añade decoraciones aleatorias entre las bolas verdes.
# Novedad: usa un BUCLE ANIDADO (un for dentro de otro for) para pintar
# cada bola de la fila una a una, pudiendo cambiar el emoji en cada posición.
# =============================================================================

def arbol_decorado(tamanyo):
    decoracion = "🟠🟡🔵🎄🎅🤶"
    # Cadena con los emojis de decoración disponibles.
    # decoracion[0] → "🟠"
    # decoracion[1] → "🟡"
    # decoracion[5] → "🤶"
    # len(decoracion) → 6

    for i in range(1, tamanyo + 1):
        # Bucle EXTERIOR: recorre las filas del árbol (igual que arbol_guay).

        bola = "🟢"
        # Bola por defecto para esta fila.

        if i == 1:
            bola = "🔴"
            # La cima sigue siendo roja.

        espacios = tamanyo - i
        bolas = 2 * i - 1
        # Mismas fórmulas de centrado que en arbol_guay().

        print("  " * espacios, end="")
        # Imprimimos los espacios de centrado.
        # end="" evita el salto de línea automático del print().
        # Sin end="" cada print() saltaría de línea y el árbol quedaría roto.

        for j in range(bolas):
            # Bucle INTERIOR: recorre cada posición de bola en esta fila.
            # "j" va de 0 a bolas-1. En cada posición decidimos qué emoji poner.

            bola = "🟢"
            # Reiniciamos la bola a verde para cada posición.

            if random.randint(0, 3) == 0:
                # random.randint(0, 3) genera un número aleatorio: 0, 1, 2 o 3.
                # Solo si sale 0 (probabilidad 1 de cada 4 = 25%), ponemos decoración.
                # Así la mayoría de bolas son verdes y las decoraciones son escasas.

                bola = decoracion[random.randint(0, len(decoracion) - 1)]
                # Elegimos un emoji aleatorio de la cadena "decoracion".
                # random.randint(0, 5) → número entre 0 y 5 (los 6 índices válidos).
                # len(decoracion)-1 = 5, así nunca nos salimos de rango.
                # ALTERNATIVA más corta: bola = random.choice(decoracion)

            print(bola, end="")
            # Imprimimos la bola (verde o decoración) SIN salto de línea,
            # para que todas las bolas de la misma fila queden juntas.

        print()
        # print() sin argumentos imprime solo un salto de línea.
        # Lo necesitamos al final de cada fila para pasar a la siguiente.
        # Es el "Enter" que falta porque usamos end="" en los print anteriores.

arbol_guay(6)
# Llamamos a arbol_guay para probarlo con tamaño 6.
# RETO: llama también a arbol_decorado(10) y observa que cada ejecución
# produce un árbol diferente gracias a random.

Repaso iteración de listas

# =============================================================================
# RECORRIDO DE CADENAS EN PYTHON
# =============================================================================
# Una cadena (string) es una secuencia de caracteres: letras, espacios, comas...
# Python nos permite recorrer esa secuencia carácter a carácter de varias formas.
#
# En este programa veremos:
#   1. Tres maneras distintas de recorrer una cadena con su posición
#   2. Cómo usar esa posición para hacer cosas distintas según sea par o impar
#   3. Cómo meter todo eso en una función reutilizable
#   4. Cómo aplicar esa función a una lista de textos
#
# ¿Qué es la posición (índice)?
#   Cada carácter de una cadena tiene un número de posición que empieza en 0.
#
#   Cadena:    H  o  l  a     q  u  e ...
#   Posición:  0  1  2  3  4  5  6  7 ...
#
#   IMPORTANTE: en Python los índices empiezan en 0, no en 1.
# =============================================================================


cadena = "Hola que tal, Python es genial"
# Declaramos la cadena con la que vamos a trabajar en los tres primeros ejemplos.
# Podría ser cualquier texto: cadena = input("Escribe algo: ")

resultado = ""
# Variable donde iremos construyendo la cadena transformada.
# Empieza vacía y le iremos añadiendo letras una a una.
# NUNCA la inicialices con otro valor o aparecerá texto extra al inicio.


# =============================================================================
# MANERA 1: CONTADOR MANUAL
# =============================================================================
# La forma más intuitiva para un principiante.
# Usamos una variable "posicion" que aumentamos nosotros a mano en cada vuelta.
# =============================================================================

posicion = 1
# Empezamos en 1 porque queremos mostrar posiciones "humanas" (del 1 en adelante).
# Nota: internamente Python usa 0, pero aquí lo mostramos desde 1 para el usuario.

for letra in cadena:
    # El bucle for recorre la cadena carácter a carácter.
    # En cada vuelta, "letra" toma el valor del siguiente carácter.
    # Ejemplo: vuelta 1 → letra="H", vuelta 2 → letra="o", etc.

    print(posicion, letra)
    # Imprime la posición actual y la letra correspondiente.
    # Salida ejemplo:
    #   1 H
    #   2 o
    #   3 l
    #   4 a
    #   5   (el espacio también es un carácter)

    posicion = posicion + 1
    # Incrementamos el contador manualmente para que en la próxima vuelta
    # indique la siguiente posición.
    # ALTERNATIVA más corta: posicion += 1
    # SIN esta línea, "posicion" siempre valdría 1 → mostraría siempre "1"


# =============================================================================
# MANERA 2: CON range() Y len()
# =============================================================================
# En lugar de recorrer las letras directamente, recorremos los números
# de posición (0, 1, 2, ...) y accedemos a cada letra por su índice.
#
# len(cadena) → devuelve el número total de caracteres de la cadena
# range(n)    → genera los números 0, 1, 2, ..., n-1
# cadena[i]   → accede a la letra que está en la posición i
# =============================================================================

for posicion in range(len(cadena)):
    # range(len(cadena)) con nuestra cadena genera: 0, 1, 2, ..., 29
    # (porque "Hola que tal, Python es genial" tiene 30 caracteres)
    # En cada vuelta, "posicion" vale el índice actual (empieza en 0).

    print(posicion + 1, cadena[posicion])
    # cadena[posicion] → accede a la letra en esa posición.
    #   cadena[0]  →  "H"
    #   cadena[1]  →  "o"
    #   cadena[4]  →  " " (espacio)
    # Sumamos +1 al mostrar para que el usuario vea 1, 2, 3... en lugar de 0, 1, 2...
    #
    # VENTAJA de esta manera sobre la 1: con "posicion" podemos acceder a
    # la letra anterior (cadena[posicion-1]) o siguiente (cadena[posicion+1])


# =============================================================================
# MANERA 3: CON enumerate() ← LA MÁS USADA EN PYTHON
# =============================================================================
# enumerate() nos da automáticamente DOS cosas en cada vuelta:
#   - La posición (índice, empezando en 0)
#   - El carácter en esa posición
# Es la manera más "pythónica" (la preferida por los programadores Python).
# =============================================================================

for posicion, letra in enumerate(cadena):
    # enumerate() devuelve pares (posición, carácter) en cada vuelta.
    # Desglosamos ese par en dos variables: "posicion" y "letra".
    # Equivale a hacer las maneras 1 y 2 a la vez, sin código extra.
    # Ejemplo: vuelta 1 → posicion=0, letra="H"
    #          vuelta 2 → posicion=1, letra="o"

    print(posicion + 1, letra)
    # Mostramos posición (desde 1) y letra, igual que en las maneras anteriores.

    if posicion % 2 == 0:
        # El operador % calcula el RESTO de la división.
        # posicion % 2 → si el resto es 0, la posición es PAR; si es 1, es IMPAR.
        # Posiciones pares:   0, 2, 4, 6, 8, ...  (H, l, ' ', u, ' ', t, ...)
        # Posiciones impares: 1, 3, 5, 7, 9, ...  (o, a, q, e, a, ...)
        # RECUERDA: la posición 0 (la primera) es PAR en Python.

        resultado = resultado + letra.lower()
        # .lower() convierte la letra a minúscula.
        # Si ya era minúscula, no cambia nada.
        # La añadimos al final de "resultado".
        # ALTERNATIVA más corta: resultado += letra.lower()

    else:
        # Si la posición es impar (resto 1)

        resultado = resultado + letra.upper()
        # .upper() convierte la letra a mayúscula.
        # Los espacios y comas no cambian con upper() ni lower().

print(resultado)
# Muestra la cadena transformada al estilo "camello alternado":
# "Hola que tal, Python es genial"
#  ↓
# "hOlA QuE TaL, pYtHoN Es gEnIaL"
# (posiciones pares en minúscula, impares en mayúscula)


# =============================================================================
# FUNCIÓN texto_camello()
# =============================================================================
# Metemos todo el proceso en una función para poder reutilizarlo con
# cualquier texto sin tener que reescribir el código.
#
# Entrada:  una cadena de texto cualquiera
# Salida:   la misma cadena con letras alternadas mayúscula/minúscula
#
# Ejemplo: texto_camello("hola") → "hOlA"
# =============================================================================

def texto_camello(cadena):
    # "def" define la función. "cadena" es el parámetro:
    # el texto que recibirá la función cuando la llamemos.
    # Este "cadena" es LOCAL a la función, no tiene nada que ver
    # con la variable "cadena" que declaramos arriba.

    resultado = ""
    # IMPORTANTE: declaramos resultado DENTRO de la función.
    # Cada vez que llamemos a la función, resultado empieza vacío desde cero.
    # Si estuviera fuera, las llamadas anteriores acumularían texto sobrante.

    for posicion, letra in enumerate(cadena):
        # Recorremos la cadena que nos han pasado como parámetro,
        # obteniendo posición y letra en cada vuelta.

        print(posicion + 1, letra)
        # Muestra el progreso por pantalla mientras trabaja.
        # En un programa real probablemente quitaríamos este print,
        # ya que solo nos interesa el resultado final.

        if posicion % 2 == 0:
            resultado = resultado + letra.lower()
            # Posición par → minúscula
        else:
            resultado = resultado + letra.upper()
            # Posición impar → mayúscula

    return resultado
    # Devuelve la cadena transformada al lugar donde se llamó la función.
    # Sin "return" la función haría todo el trabajo pero no compartiría el resultado.


# =============================================================================
# LLAMADAS A LA FUNCIÓN
# =============================================================================

cadena = "Hola que tal, Python es genial"
# Reasignamos la variable cadena (la anterior también valía, pero así
# queda claro con qué texto trabajamos en esta sección).

print(texto_camello("hola que tal"))
# Llamada con texto directo (literal de cadena).
# La función recibe "hola que tal" como parámetro.
# Imprime: "hOlA QuE TaL"

print(texto_camello(cadena))
# Llamada pasando una variable como argumento.
# La función recibe el contenido de "cadena": "Hola que tal, Python es genial"
# Imprime: "hOlA QuE TaL, pYtHoN Es gEnIaL"

versos = ["Vi un gato muerto", "espanzurrado en la carretera", "una gaviota acechaba", "y la tormenta tronaba"]
# Lista de cadenas. Cada elemento es una línea de un poema.
# Podría ser cualquier colección de textos: nombres, frases, párrafos...

for verso in versos:
    # Recorremos la lista. En cada vuelta "verso" toma el valor de la siguiente línea.
    # Vuelta 1: verso = "Vi un gato muerto"
    # Vuelta 2: verso = "espanzurrado en la carretera"
    # ...

    print(texto_camello(verso))
    # Aplicamos la función a cada verso y mostramos el resultado.
    # Salida:
    #   "vI Un gAtO MuErTo"
    #   "eSpAnZuRrAdO En lA CaRrEtErA"
    #   "uNa gAvIoTa aCeChAbA"
    #   "y lA ToRmEnTa tRoNaBa"
    #
    # ALTERNATIVA con comprensión de listas:
    #   transformados = [texto_camello(v) for v in versos]
    #   print('\n'.join(transformados))

Más repaso while

# =============================================================================
# CALCULAR LA MEDIA DE UNA SERIE DE NÚMEROS
# =============================================================================
# Este programa pide números al usuario uno a uno y los va sumando.
# Cuando el usuario introduce un 0, el programa para y calcula la media.
#
# La media es la suma de todos los números dividida entre cuántos hay.
# Ejemplo: si introduces 4, 8 y 6 → media = (4+8+6) / 3 = 6.0
#
# Interacción esperada:
#   Digite un numero: 4
#   Digite un numero: 8
#   Digite un numero: 6
#   Digite un numero: 0   ← el 0 para el bucle, no se suma
#   6.0
# =============================================================================


def calcular_media():
    # Definimos la función. No recibe parámetros porque los datos
    # los pedirá ella misma al usuario con input().
    # Devolverá un número decimal (float) con la media calculada.

    numero = float(input("Digite un numero: "))
    # Pedimos el PRIMER número antes del bucle.
    # Necesitamos hacerlo aquí para poder comprobar la condición del while.
    # float() convierte el texto que escribe el usuario a número decimal.
    # Usamos float en lugar de int para aceptar números como 3.5 o 7.8.
    # PRUEBA: cambia float() por int() e intenta introducir 3.5 para ver el error.

    suma = 0
    # Variable acumuladora: irá sumando todos los números que introduzca el usuario.
    # Siempre se inicializa a 0 antes del bucle.
    # Ejemplo paso a paso con 4, 8, 6:
    #   Antes del bucle: suma = 0
    #   Tras el 4:       suma = 4
    #   Tras el 8:       suma = 12
    #   Tras el 6:       suma = 18

    contador = 0
    # Variable contadora: cuenta cuántos números ha introducido el usuario.
    # También se inicializa a 0. La necesitamos para dividir al calcular la media.
    # Ejemplo: con 4, 8 y 6 → contador llegará a 3.
    # ¡IMPORTANTE! El 0 final NO se cuenta porque para el bucle antes de sumarlo.

    while numero != 0:
        # El bucle se repite MIENTRAS el número introducido sea distinto de 0.
        # En cuanto el usuario escribe 0, el bucle termina sin sumar ni contar ese 0.
        # "!=" significa "distinto de".
        # ALTERNATIVA equivalente: while not numero == 0:

        # ↓ Aquí estaba el comentario "????" en el código original.
        # Lo que hace este bloque es acumular el número en la suma
        # y aumentar el contador en 1 por cada número válido introducido.

        suma = suma + numero
        # Añadimos el número actual a la suma acumulada.
        # Es lo mismo que escribir: suma += numero
        # Solo llegamos aquí si numero != 0, así que el 0 nunca se suma.

        contador = contador + 1
        # Contamos este número como válido.
        # Es lo mismo que escribir: contador += 1

        numero = float(input("Digite un numero: "))
        # Pedimos el SIGUIENTE número al final del bucle.
        # Esto sobreescribe el valor anterior de "numero".
        # En la próxima comprobación del while se usará este nuevo valor.
        # Si el usuario escribe 0 aquí, el while parará en su próxima vuelta.

    # Cuando llegamos aquí, el bucle ya ha terminado.
    # En este punto sabemos que:
    #   - "suma" tiene la suma de todos los números introducidos (sin el 0)
    #   - "contador" tiene cuántos números se introdujeron (sin el 0)

    return suma / contador
    # Calculamos y devolvemos la media: suma total dividida entre cuántos números hay.
    # CUIDADO: si el usuario introduce 0 como primer número, contador valdrá 0
    # y dividir entre 0 causará un error (ZeroDivisionError).
    # MEJORA para evitar ese error:
    #   if contador == 0:
    #       return 0
    #   return suma / contador


# =============================================================================
# PROGRAMA PRINCIPAL
# =============================================================================

media = calcular_media()
# Llamamos a la función. Esta ejecuta todo el proceso de pedir números
# y calcular la media. El resultado que devuelve "return" se guarda aquí.

print(media)
# Mostramos la media por pantalla.
# MEJORA para que quede más claro:
#   print(f"La media de los números introducidos es: {media:.2f}")
#   El :.2f muestra solo 2 decimales. Ejemplo: 6.333333 → 6.33

Repaso while

# =============================================================================
# FUNCIONES CON VALIDACIÓN Y LISTAS EN PYTHON
# =============================================================================
# En este programa vamos a ver cómo crear una función que:
#   1. Pide un dato al usuario
#   2. Comprueba que el dato sea correcto (validación)
#   3. Devuelve el dato para usarlo desde fuera
#
# Después veremos TRES formas distintas de usar esa función:
#   - Imprimir el resultado directamente
#   - Guardarlo en una variable
#   - Añadirlo a una lista
#
# Y por último, un ejemplo real: recoger las notas de un grupo de alumnos.
# =============================================================================


# =============================================================================
# DEFINICIÓN DE LA FUNCIÓN pedir_nota()
# =============================================================================
# Una función es un bloque de código con nombre que podemos reutilizar
# tantas veces como queramos sin tener que reescribirlo.
#
# Esta función:
#   - No recibe ningún parámetro (los paréntesis están vacíos)
#   - Se encarga de pedir una nota válida al usuario
#   - Devuelve la nota una vez validada
# =============================================================================

def pedir_nota():
    # "def" le dice a Python que vamos a definir una función.
    # "pedir_nota" es el nombre que le damos (debe ser descriptivo).
    # Los "():" al final son obligatorios aunque no haya parámetros.
    # Todo lo que esté indentado (con sangría) pertenece a la función.

    print("Introduce una nota entre 1 y 10")
    # Mensaje informativo para que el usuario sepa qué se espera de él.

    nota = int(input("Introduce la nota (1 y 10): "))
    # input() pide texto al usuario. int() lo convierte a número entero.
    # Si el usuario escribe "7", sin int() tendríamos la cadena "7", no el número.
    # PRUEBA: quita el int() y verás que la comparación nota < 1 da error.

    while nota < 1 or nota > 10:
        # Bucle de validación: se repite MIENTRAS la nota sea incorrecta.
        # "or" significa que basta con que UNA de las dos condiciones sea True
        # para que el bucle continúe.
        # Ejemplos de notas incorrectas: 0, -5, 11, 100
        # ALTERNATIVA equivalente: while not (1 <= nota <= 10):

        print("Nota incorrecta, mendrugo")
        # Mensaje de error. En un programa real usaríamos algo más amable,
        # como: print("Nota incorrecta. Debe estar entre 1 y 10.")

        nota = int(input("Introduce la nota (1 y 10): "))
        # Volvemos a pedir la nota para que el usuario pueda corregirla.
        # Esto SOBREESCRIBE el valor anterior de "nota".
        # En la siguiente vuelta del while, se comprobará este nuevo valor.

    return nota
    # "return" devuelve el valor de "nota" al lugar donde se llamó la función.
    # Solo llegamos aquí cuando la nota ES válida (el while ha terminado).
    # Sin "return", la función haría todo el proceso pero no nos daría el resultado.
    # Sería como una máquina expendedora que acepta el dinero pero no da el producto.


# =============================================================================
# CASOS DE USO: TRES FORMAS DE USAR LA FUNCIÓN
# =============================================================================
# Una función puede usarse de varias maneras según lo que necesitemos hacer
# con el valor que devuelve. Aquí vemos las tres más habituales.
# =============================================================================

# --- CASO 1: Imprimir el resultado directamente ---
print(pedir_nota())
# Python ejecuta pedir_nota(), obtiene la nota válida y la pasa a print().
# El valor devuelto por la función se usa al momento y no se guarda en ningún sitio.
# Útil cuando solo queremos mostrar el resultado una vez.

# --- CASO 2: Guardar el resultado en una variable ---
nota_alumno = pedir_nota()
# El valor que devuelve pedir_nota() se guarda en la variable "nota_alumno".
# A partir de aquí podemos usar "nota_alumno" tantas veces como queramos.
# VENTAJA respecto al caso 1: podemos usar el valor varias veces sin pedir
# la nota de nuevo. Por ejemplo: print(nota_alumno), calcular media, etc.

# --- CASO 3: Añadir el resultado a una lista ---
lista_notas = []
# Creamos una lista vacía con []. Aquí iremos acumulando notas.

lista_notas.append(nota_alumno)
# .append() añade un elemento AL FINAL de la lista.
# Aquí añadimos la nota que ya teníamos guardada en la variable del caso 2.
# lista_notas pasa de [] a [nota_alumno], por ejemplo: [7]

lista_notas.append(pedir_nota())
# Llamamos a pedir_nota() y el valor que devuelve se añade directamente a la lista.
# Es lo mismo que hacer:
#   nueva_nota = pedir_nota()
#   lista_notas.append(nueva_nota)
# pero en una sola línea.

print(lista_notas)
# Imprime la lista completa con los dos valores acumulados.
# Ejemplo de salida: [7, 9]


# =============================================================================
# EJEMPLO REAL: RECOGER LAS NOTAS DE UN GRUPO DE ALUMNOS
# =============================================================================
# Ahora combinamos todo lo anterior con un bucle for para recorrer
# una lista de nombres y pedir la nota de cada alumno.
# Este es el patrón más habitual en programas reales.
# =============================================================================

alumnos = ["Monica", "Ermengol", "Jordi", "Marisol"]
# Lista con los nombres de los alumnos del grupo.
# ALTERNATIVA: pedir los nombres por teclado con un bucle while.

notas = []
# Lista vacía donde iremos guardando las notas según las vayamos pidiendo.
# Es importante crearla ANTES del bucle para no borrarla en cada vuelta.

for alumno in alumnos:
    # El bucle recorre la lista "alumnos" de principio a fin.
    # En cada vuelta, "alumno" toma el valor del siguiente nombre:
    #   Vuelta 1: alumno = "Monica"
    #   Vuelta 2: alumno = "Ermengol"
    #   Vuelta 3: alumno = "Jordi"
    #   Vuelta 4: alumno = "Marisol"

    print("Dime la nota del alumno", alumno)
    # Informamos de qué alumno estamos introduciendo.
    # Ejemplo de salida: "Dime la nota del alumno Monica"
    # ALTERNATIVA con f-string: print(f"Dime la nota de {alumno}")

    notas.append(pedir_nota())
    # Llamamos a pedir_nota() para obtener una nota válida
    # y la añadimos al final de la lista "notas".
    # Al terminar el bucle, "notas" tendrá una nota por cada alumno.
    # Ejemplo: [8, 6, 9, 7]

# Al salir del bucle tenemos dos listas relacionadas:
#   alumnos = ["Monica", "Ermengol", "Jordi",  "Marisol"]
#   notas   = [8,        6,          9,         7       ]
# El alumno de la posición 0 tiene la nota de la posición 0, etc.
#
# RETO 1: imprime cada alumno con su nota usando un bucle for y range().
# RETO 2: calcula la nota media del grupo con sum(notas) / len(notas).
# RETO 3: encuentra al alumno con la nota más alta.

Repaso while

# =============================================================================
# BUCLES WHILE EN PYTHON
# =============================================================================
# Un bucle "while" repite un bloque de código MIENTRAS se cumpla una condición.
# Es ideal cuando no sabemos de antemano cuántas veces necesitamos repetir algo.
#
# Estructura básica:
#
#   while condición:
#       instrucción 1   ← estas líneas se repiten mientras la condición sea True
#       instrucción 2
#
# ¡CUIDADO! Si la condición nunca se vuelve False, el bucle no termina nunca
# (bucle infinito). Siempre hay que asegurarse de que algo dentro del bucle
# hace que la condición cambie.
#
# En este archivo veremos 6 ejemplos prácticos:
#   1. Calcular el factorial de un número
#   2. Repetir algo hasta que el usuario decida parar
#   3. Tabla de multiplicar del 7
#   4. Sumar los números del 0 al 100
#   5. Saludar a varios usuarios hasta que no introduzcan nombre
#   6. Acumular números introducidos por el usuario
# =============================================================================


# =============================================================================
# EJEMPLO 1: CÁLCULO DEL FACTORIAL CON UN BUCLE WHILE
# =============================================================================
# El factorial de un número n (escrito n!) es la multiplicación de todos los
# números enteros desde 1 hasta n.
# Ejemplo: 5! = 1 × 2 × 3 × 4 × 5 = 120
#
# Interacción esperada:
#   Introduce un numero: 5
#   El factorial de 5 es 120
# =============================================================================

numero = int(input("Introduce un numero: "))
# input() siempre devuelve texto (string). int() lo convierte a número entero.
# Si el usuario escribe "5", sin int() tendríamos la cadena "5", no el número 5.
# PRUEBA: quita el int() y observa el error que aparece al hacer cálculos.

factorial = 1
# Inicializamos factorial a 1, no a 0.
# Si lo inicializáramos a 0, cualquier multiplicación daría 0 (0 × lo que sea = 0).
# Esta variable irá acumulando el resultado de las multiplicaciones.

contador = 1
# El contador empieza en 1 porque la multiplicación parte de 1.
# Esta variable controla el bucle: irá de 1 hasta "numero".

while contador <= numero:
    # La condición comprueba si contador todavía no ha superado "numero".
    # Cuando contador > numero, el bucle se detiene.
    # Ejemplo con numero=4: el bucle se ejecuta para contador = 1, 2, 3, 4

    factorial = factorial * contador
    # Multiplicamos el factorial acumulado por el contador actual.
    # Es lo mismo que: factorial *= contador  (forma abreviada)
    # Paso a paso con numero=4:
    #   Vuelta 1: factorial = 1 × 1 = 1
    #   Vuelta 2: factorial = 1 × 2 = 2
    #   Vuelta 3: factorial = 2 × 3 = 6
    #   Vuelta 4: factorial = 6 × 4 = 24  → El factorial de 4 es 24 ✓

    contador = contador + 1
    # Incrementamos el contador para avanzar al siguiente número.
    # Es lo mismo que: contador += 1  (forma abreviada)
    # SIN esta línea, contador siempre valdría 1 y el bucle nunca terminaría.

print(f"El factorial de {numero} es {factorial}")
# Las f-strings permiten insertar variables dentro del texto con {}.
# ALTERNATIVA sin f-string: print("El factorial de", numero, "es", factorial)
# ALTERNATIVA avanzada: Python tiene math.factorial(n) que hace esto automáticamente:
#   import math
#   print(math.factorial(5))  → 120


# =============================================================================
# EJEMPLO 2: BUCLE CONTROLADO POR EL USUARIO (SEGUIR / PARAR)
# =============================================================================
# Este ejemplo muestra cómo dejar que el USUARIO decida cuándo parar el bucle.
# Contamos cuántas veces decide continuar.
#
# Interacción esperada:
#   0
#   ¿Deseas seguir? (Sí/No): Sí
#   1
#   ¿Deseas seguir? (Sí/No): Sí
#   2
#   ¿Deseas seguir? (Sí/No): No
# =============================================================================

seguir = "Sí"
# Inicializamos la variable centinela con "Sí" para que el bucle empiece.
# Una variable "centinela" es aquella cuyo valor controla si el bucle continúa.
# IMPORTANTE: debe coincidir exactamente con la condición del while.

contador = 0
# Empezamos el contador en 0 y lo iremos mostrando en cada vuelta.

while seguir == "Sí":
    # El bucle continúa MIENTRAS el usuario escriba exactamente "Sí".
    # Si escribe "si", "SI", "sí" con minúscula... el bucle se detiene.
    # ALTERNATIVA más flexible: while seguir.lower() == "sí":
    #   Con .lower() convertimos a minúsculas y aceptamos "Sí", "SÍ", "sí"...

    print(contador)
    # Mostramos el valor actual del contador antes de preguntar.

    seguir = input("¿Desas seguir? (Sí/No): ")
    # Aquí el usuario puede escribir "Sí" o "No".
    # Este input SOBREESCRIBE el valor anterior de "seguir".
    # Si escribe "No" (o cualquier cosa que no sea "Sí"), el bucle termina.

    contador = contador + 1
    # Incrementamos el contador en cada vuelta.


# =============================================================================
# EJEMPLO 3: TABLA DE MULTIPLICAR DEL 7
# =============================================================================
# Recorremos los números del 1 al 10 y multiplicamos cada uno por 7.
#
# Resultado esperado:
#   1 x 7 = 7
#   2 x 7 = 14
#   3 x 7 = 21
#   ...
#   10 x 7 = 70
# =============================================================================

numero = 1
# Reutilizamos el nombre "numero" (la variable del ejemplo anterior ya no se usa).
# El número empieza en 1 porque las tablas de multiplicar van del 1 al 10.

while numero <= 10:
    # El bucle se ejecuta mientras numero sea menor o igual a 10.
    # Se ejecutará exactamente 10 veces: para 1, 2, 3, ..., 10.

    print(f"{numero} x 7 = {numero * 7}")
    # Mostramos la operación completa y su resultado.
    # numero * 7 se calcula directamente dentro de la f-string.
    # ALTERNATIVA: resultado = numero * 7 y luego print(f"{numero} x 7 = {resultado}")

    numero = numero + 1
    # Avanzamos al siguiente número.
    # Sin esta línea: número siempre sería 1 → bucle infinito imprimiendo "1 x 7 = 7"

# ALTERNATIVA con for (más natural para recorrer rangos conocidos):
#   for numero in range(1, 11):       # range(1,11) genera: 1, 2, 3, ..., 10
#       print(f"{numero} x 7 = {numero * 7}")
#
# RETO: modifica el código para que pregunte al usuario qué tabla quiere ver.


# =============================================================================
# EJEMPLO 4: SUMAR TODOS LOS NÚMEROS DEL 0 AL 100
# =============================================================================
# Acumulamos la suma de 0 + 1 + 2 + 3 + ... + 100 = 5050
#
# Resultado esperado:
#   5050
# =============================================================================

contador = 0
# El contador empieza en 0 porque queremos sumar desde 0.

suma = 0
# La variable acumuladora también empieza en 0.
# Irá almacenando la suma parcial en cada vuelta.
# NUNCA inicialices una suma acumuladora a otro valor que no sea 0,
# ya que añadiría un "extra" a todos los cálculos.

while contador <= 100:
    # El bucle recorre todos los números de 0 a 100, ambos incluidos.

    suma = suma + contador
    # Añadimos el valor actual del contador a la suma acumulada.
    # Es lo mismo que: suma += contador
    # Paso a paso:
    #   Vuelta 0: suma = 0 + 0 = 0
    #   Vuelta 1: suma = 0 + 1 = 1
    #   Vuelta 2: suma = 1 + 2 = 3
    #   Vuelta 3: suma = 3 + 3 = 6
    #   ... y así hasta 100

    contador = contador + 1
    # Avanzamos al siguiente número.

print(suma)
# Imprime 5050. Existe una fórmula matemática directa: n*(n+1)/2 → 100*101/2 = 5050
# ALTERNATIVA con for y sum():
#   print(sum(range(101)))   → 5050  (range(101) genera del 0 al 100)


# =============================================================================
# EJEMPLO 5: SALUDAR USUARIOS HASTA QUE NO SE INTRODUZCA NOMBRE
# =============================================================================
# El bucle se repite mientras el usuario introduzca un nombre.
# Si pulsa Enter sin escribir nada, el programa termina.
#
# Interacción esperada:
#   Dime tu nombre (pulsa enter sin poner nada para salir)
#   Tu nombre: Ana
#   Hola Ana
#   Tu nombre: Luis
#   Hola Luis
#   Tu nombre:          ← el usuario pulsa Enter sin escribir
#   (el programa termina)
# =============================================================================

print("Dime tu nombre (pulsa enter sin poner nada para salir)")
# Mensaje informativo para que el usuario sepa cómo funciona el programa.

nombre = input("Tu nombre: ")
# Pedimos el nombre ANTES del bucle para poder comprobar la condición.
# Si lo pidiéramos dentro del bucle, no podríamos comprobar el primer valor.
# Si el usuario pulsa Enter sin escribir nada, nombre será "" (cadena vacía).

while nombre != "":
    # El bucle continúa MIENTRAS nombre sea diferente de "" (cadena vacía).
    # "" es una cadena sin ningún carácter, lo que ocurre al pulsar Enter vacío.
    # ALTERNATIVA equivalente: while nombre:  (una cadena vacía equivale a False)

    print("Hola", nombre)
    # Saludamos al usuario. También podría ser: print(f"Hola {nombre}")

    nombre = input("Tu nombre: ")
    # Volvemos a pedir el nombre al final del bucle para actualizar la condición.
    # Este patrón (pedir antes del while y al final del bucle) es muy común
    # cuando la condición depende de un input del usuario.


# =============================================================================
# EJEMPLO 6A: SUMAR LOS N PRIMEROS NÚMEROS (EL USUARIO ELIGE HASTA DÓNDE)
# =============================================================================
# El usuario indica un límite y sumamos todos los números desde 1 hasta ese límite.
#
# Interacción esperada:
#   Ingrese un numero hasta el que quiere sumar: 10
#   La suma de los 10 primeros números es: 55
# =============================================================================

limite = int(input("Ingrese un numero hasta el que quiere sumar: "))
# El usuario decide hasta qué número se suma.
# int() es necesario para poder usar el valor como límite numérico.

suma = 0
# Acumulador de la suma, siempre inicializado a 0.

contador = 1
# Empezamos en 1 porque queremos sumar los números POSITIVOS hasta el límite.
# Si el límite fuera 5: sumaríamos 1+2+3+4+5 = 15.

while contador <= limite:
    # El bucle avanza mientras el contador no supere el límite elegido.

    suma = suma + contador
    # Acumulamos el valor actual del contador en la suma.

    contador = contador + 1
    # Avanzamos al siguiente número.

print(f"La suma de los {limite} primeros números es: {suma}")
# Mostramos el resultado final con el límite y la suma obtenida.
# RETO: prueba con limite=100, ¿obtienes 5050 igual que en el ejemplo 4?


# =============================================================================
# EJEMPLO 6B: SUMAR NÚMEROS INTRODUCIDOS POR EL USUARIO (0 PARA TERMINAR)
# =============================================================================
# El usuario va introduciendo números uno a uno y los vamos sumando.
# Cuando introduce 0, el bucle termina y mostramos la suma total.
# El 0 es el "número centinela": su único papel es indicar que queremos parar.
#
# Interacción esperada:
#   ingrese un numero: 5
#   ingrese un numero: 3
#   ingrese un numero: 10
#   ingrese un numero: 0
#   18                ← 5 + 3 + 10 (el 0 no se suma)
# =============================================================================

numero = int(input("ingrese un numero: "))
# Pedimos el primer número antes del bucle para poder evaluarlo en la condición.
# Si lo pidiéramos dentro, no podríamos comprobar si es 0 antes de la primera vuelta.

suma = 0
# Acumulador que irá guardando la suma de todos los números introducidos.

while numero != 0:
    # El bucle continúa MIENTRAS el número introducido sea distinto de 0.
    # En cuanto el usuario escriba 0, el bucle termina SIN sumar ese 0.
    # Por eso el 0 no aparece en la suma: la condición se comprueba antes de sumar.

    suma = suma + numero
    # Añadimos el número actual a la suma acumulada.
    # Solo llegamos aquí si numero != 0, así que el 0 nunca se suma.

    numero = int(input("ingrese un numero: "))
    # Pedimos el siguiente número. Si el usuario introduce 0 aquí,
    # la próxima comprobación del while será False y el bucle terminará.

print(suma)
# Mostramos la suma total de todos los números introducidos (sin contar el 0).
# MEJORA: print(f"La suma total es: {suma}")
#
# RETO: modifica el programa para que también muestre cuántos números
# introdujo el usuario y cuál es la media.

Repaso if con funciones

# IF: si se cumple una condición hacemos algo y si no, otra cosa

edad=20  # Declaramos una variable entera. También podríamos pedirla al usuario: edad = int(input("¿Cuántos años tienes? "))

if edad>=18:               # Comprobamos si edad es mayor O IGUAL a 18
    print("Mayor de edad") # Si la condición es True, ejecutamos este bloque (está indentado con 4 espacios)
else:                      # Si la condición es False, ejecutamos el bloque alternativo
    print("Menor de edad") # Solo se ejecuta UNO de los dos bloques, nunca los dos

# -----------------------------------------------------------------------

# elif: si no se cumple una condición hacemos otra comprobación todas las veces que queramos
# Ideal para rangos

temperatura=25  # Prueba a cambiar este valor para ver cómo cambia el resultado

if temperatura<=0:          # Primera comprobación. Si es True, entra aquí y SALTA todos los demás
    print("Me congelo")
elif temperatura<=15:       # Solo se comprueba si la anterior fue False
    print("Hace frío")      # Aquí ya sabemos implícitamente que temperatura > 0
elif temperatura<=25:       # Solo se comprueba si todas las anteriores fueron False
    print("¡Que buen tiempo")
elif temperatura<=35:       # Podríamos añadir tantos elif como rangos necesitemos
    print("El caloret")
else:                       # Captura cualquier caso que no haya entrado en ningún if/elif anterior
    print("Me asfixio")     # Con temperatura=25, entrará en el tercer elif (<=25)

# -----------------------------------------------------------------------
# ALTERNATIVA con ifs independientes (sin elif):
# Diferencia clave: aquí se evalúan TODAS las condiciones siempre,
# aunque ya hayamos encontrado una verdadera. Es menos eficiente.
# Por eso cuando los casos son excluyentes, es mejor usar elif.

if temperatura<=0:
    print("Me congelo")
if temperatura>0 and temperatura<=15:   # Necesitamos indicar el límite inferior porque no hay elif
    print("Hace frío")                  # "and" significa que las DOS condiciones deben ser True
if temperatura>15 and temperatura<=25:  # Equivalente más compacto: if 15 < temperatura <= 25
    print("Que buen tiempo")
if temperatura>25 and temperatura<=35:
    print("El caloret")
if temperatura>35:
    print("Me asfixio")

# -----------------------------------------------------------------------

# función mayor a la que le pasamos dos números y nos devuelve el mayor
# mayor(1,2)->2  mayor(8,3)->8
# PASOS
# Defino la función y los parámetros
# Utilizo una variable para el resultado
# Hago el proceso de la función
# Devuelvo el resultado

def mayor(num1,num2):  # "def" define la función. num1 y num2 son parámetros (valores de entrada)
    resultado=0        # Inicializamos la variable de resultado. Buena práctica antes de usarla.
    if num1>num2:      # Comparamos los dos parámetros
        resultado=num1 # Si num1 es mayor, guardamos num1
    else:
        resultado=num2 # Si no, guardamos num2 (incluye el caso en que sean iguales)
    return resultado   # "return" devuelve el valor al lugar donde se llamó la función
                       # ALTERNATIVA más corta: return num1 if num1 > num2 else num2
                       # ALTERNATIVA con max(): return max(num1, num2)

print(mayor(1,2))  # Llamamos a la función con 1 y 2 → devuelve 2
print(mayor(8,3))  # Llamamos con 8 y 3 → devuelve 8
print(mayor(6,9))  # Llamamos con 6 y 9 → devuelve 9

# -----------------------------------------------------------------------

a=int(input("Ingrese el primer número (0 para salir) "))  # int() convierte el texto del input a número entero
b=int(input("Ingrese el segundo número "))                # Si el usuario escribe letras, dará error (pruébalo)

while a!=0:  # Bucle que se repite MIENTRAS a sea distinto de 0. Si el usuario escribe 0, el bucle termina.
    print(f"El mayor de {a} y {b} es {mayor(a,b)}")   # f-string: las variables entre {} se sustituyen por su valor
    a = int(input("Ingrese el primer número (0 para salir)")) # Pedimos de nuevo para poder volver a comprobar el while
    b = int(input("Ingrese el segundo número"))               # Importante: si no actualizamos a, el bucle sería infinito

# -----------------------------------------------------------------------

# Vamos a crear una función cadena_mas_corta a la que le pasamos dos cadenas
# y nos devuelve la más corta
# cadena_mas_corta("Eva","María")->"Eva"

def cadena_mas_corta(cadena1,cadena2):
    resultado=cadena1       # Asumimos que la primera cadena es la más corta (valor por defecto)

    if len(cadena1)>len(cadena2):  # len() devuelve el número de caracteres de una cadena
        resultado=cadena2          # Si cadena1 es MÁS LARGA, entonces cadena2 es la más corta
    # Ojo: si tienen la misma longitud, devuelve cadena1 (la asignada por defecto al inicio)
    # ALTERNATIVA más corta: return cadena1 if len(cadena1) <= len(cadena2) else cadena2
    # ALTERNATIVA con min(): return min(cadena1, cadena2, key=len)

    return resultado  # Devolvemos la cadena más corta

print(cadena_mas_corta("Eva","María"))                           # "Eva" tiene 3 letras, "María" tiene 5 → devuelve "Eva"
print(cadena_mas_corta("otorrinolaringólogo","patata"))          # "patata" es más corta → devuelve "patata"
print(cadena_mas_corta("alubias","supercalifragilísticoespialidoso")) # "alubias" es más corta → devuelve "alubias"

Explicación irpf con return

# ============================================================
# CALCULADORA DE IRPF (Impuesto sobre la Renta de las Personas Físicas)
# ============================================================
# El IRPF es un porcentaje que se descuenta del sueldo como impuesto.
# Cuanto más ganas, mayor es el porcentaje que pagas.
# ============================================================


# --- DEFINICIÓN DE LA FUNCIÓN ---
# Una función es un bloque de código reutilizable al que le damos un nombre.
# Así no tenemos que repetir el mismo código cada vez que lo necesitemos.
#
# "def" indica que estamos definiendo (creando) una función.
# "calculo_irpf" es el nombre que le damos a la función.
# "(sueldo)" es el parámetro: un valor que la función recibe para trabajar con él.
# Cada vez que llamemos a la función, le pasaremos un sueldo diferente.

def calculo_irpf(sueldo):

    # La función comprueba en qué tramo está el sueldo
    # y devuelve el porcentaje de IRPF correspondiente.
    # "return" significa "devuelve este resultado al código que me llamó".

    if sueldo <= 1000:
        # Si el sueldo es 1000€ o menos → 9% de IRPF
        # En Python, 0.09 equivale al 9% (9 dividido entre 100)
        return 0.09

    elif sueldo <= 5000:
        # Si el sueldo está entre 1001€ y 5000€ → 12% de IRPF
        # "elif" significa "si lo anterior no se cumplió, comprueba esto otro"
        return 0.12

    else:
        # Si el sueldo supera los 5000€ → 15% de IRPF
        # "else" significa "en cualquier otro caso"
        return 0.15


# ============================================================
# --- EJEMPLOS DE USO DE LA FUNCIÓN ---
# ============================================================

# EJEMPLO 1: Mostrar directamente el porcentaje de IRPF para 1000€
# Llamamos a la función con sueldo=1000 e imprimimos lo que devuelve.
# Resultado esperado: 0.09 (es decir, el 9%)
print(calculo_irpf(1000))


# EJEMPLO 2: Calcular la cantidad en euros que se paga de IRPF
# Primero obtenemos el porcentaje para 4000€ (será 0.12, el 12%)
# Luego multiplicamos ese porcentaje por el sueldo para saber cuántos euros son.
# 4000 * 0.12 = 480€ de impuesto
# Guardamos el resultado en la variable "irpf" para poder imprimirla después.
irpf = calculo_irpf(4000) * 4000
print(irpf)


# EJEMPLO 3: Calcular el sueldo NETO (lo que realmente cobras)
# Sueldo neto = sueldo bruto - lo que pagas de IRPF
# Para un sueldo de 10000€: 10000 - (0.15 * 10000) = 10000 - 1500 = 8500€
#
# Usamos una f-string para mostrar el resultado con texto.
# Las f-strings empiezan con la letra f antes de las comillas: f"..."
# Todo lo que va dentro de llaves {} se calcula y se muestra como número.
print(f"Sueldo bruto: {10000 - calculo_irpf(10000) * 10000}")


# ============================================================
# --- CÁLCULO PARA UN EMPLEADO CONCRETO ---
# ============================================================

# Guardamos el nombre y el sueldo en variables para trabajar con ellos
empleado = "Ana"
sueldo = 3000

# Calculamos el porcentaje de IRPF que le corresponde a Ana (será 0.12)
irpf = calculo_irpf(sueldo)

# Calculamos su sueldo neto y lo mostramos con una f-string
# sueldo - sueldo * irpf = 3000 - 3000 * 0.12 = 3000 - 360 = 2640€
print(f"El empleado {empleado} tiene un sueldo de {sueldo} y el sueldo neto es {sueldo - sueldo * irpf}")


# ============================================================
# --- CÁLCULO PARA VARIOS EMPLEADOS CON UN BUCLE ---
# ============================================================

# En lugar de repetir el código para cada persona, usamos listas y un bucle.

# Una lista es una colección de valores entre corchetes [], separados por comas.
# Aquí guardamos los nombres de los empleados en orden.
empleados = ["Ana", "Eva", "Iu", "Pep"]

# Otra lista con los sueldos, en el mismo orden que los nombres.
# empleados[0]="Ana" corresponde a sueldos[0]=3000, etc.
sueldos = [3000, 18000, 900, 4000]

# "for" crea un bucle: repite el bloque de código varias veces.
# "range(len(empleados))" genera los números 0, 1, 2, 3
#   - len(empleados) cuenta cuántos elementos hay en la lista (4 en este caso)
#   - range(4) produce la secuencia: 0, 1, 2, 3
# En cada vuelta del bucle, "i" toma uno de esos valores (0, 1, 2, 3)
# y lo usamos como índice para acceder al empleado y sueldo correspondiente.
for i in range(len(empleados)):

    # empleados[i] → el nombre del empleado en la posición i
    # sueldos[i]   → el sueldo del empleado en la posición i
    # calculo_irpf(sueldos[i]) → el porcentaje de IRPF según su sueldo
    # sueldos[i] - sueldos[i] * calculo_irpf(sueldos[i]) → su sueldo neto

    print(f"El empleado {empleados[i]} tiene un sueldo de {sueldos[i]} "
          f"y el sueldo neto es {sueldos[i] - sueldos[i] * calculo_irpf(sueldos[i])}")

# Resultado del bucle:
# i=0 → Ana,  3000€  → 3000  - 3000  * 0.12 = 2640.0€
# i=1 → Eva,  18000€ → 18000 - 18000 * 0.15 = 15300.0€
# i=2 → Iu,   900€   → 900   - 900   * 0.09 = 819.0€
# i=3 → Pep,  4000€  → 4000  - 4000  * 0.12 = 3520.0€

Funciones

# Definimos una función llamada "saludo".
# Una función es un bloque de código que podemos reutilizar cuantas veces queramos.
# La palabra "def" le dice a Python que vamos a definir (crear) una función.
# Los paréntesis () vacíos significan que esta función no necesita ningún dato de entrada.
# Los dos puntos ":" al final son obligatorios — indican que empieza el cuerpo de la función.
def saludo():
    # El código dentro de la función va con sangría (4 espacios).
    # Esto le dice a Python que esta línea pertenece a la función.
    print("Hola")
 
# Aquí "llamamos" a la función por su nombre seguido de ().
# Sin esta línea, la función existe pero nunca se ejecuta.
# Resultado: imprime "Hola"
saludo()
 
# Podemos llamarla tantas veces como queramos sin repetir el código.
# Resultado: imprime "Hola" otra vez
saludo()


# Ahora la función recibe un "parámetro" llamado "nombre".
# Un parámetro es como una variable especial que recibe un valor cuando llamamos la función.
# La "f" antes de las comillas indica un "f-string": permite meter variables dentro del texto
# usando llaves {}. Aquí {nombre} se sustituirá por el valor recibido.
def saludo_personal(nombre):
    print(f"Hola {nombre} ¿Qué tal estás?")
 
# Al llamar la función, le pasamos el valor "Ana".
# Dentro de la función, "nombre" valdrá "Ana". Resultado: "Hola Ana ¿Qué tal estás?"
saludo_personal("Ana")
 
# La misma función, pero ahora "nombre" valdrá "Pep". Resultado: "Hola Pep ¿Qué tal estás?"
saludo_personal("Pep")

# Esta función recibe DOS parámetros: "nombre" y "edad".
# Dentro usamos un "if/else" para tomar decisiones según el valor de "edad".
def portero_discoteca(nombre, edad):
    # Si "edad" es mayor o igual a 18, entra.
    if edad >= 18:
        print(f"Adelante, {nombre}, disfruta de la fiesta")
    # En cualquier otro caso (edad < 18), no entra.
    else:
        print(f"Lo siento, {nombre}, no puedes entrar")
 
# Ana tiene exactamente 18: entra (>= incluye el 18).
portero_discoteca("Ana", 18)
# Pep tiene 16: no entra.
portero_discoteca("Pep", 16)
# José Luis tiene 116: entra igualmente, porque 116 >= 18.
portero_discoteca("José Luis", 116)
# El parámetro "nombres" recibirá una lista (varios valores entre corchetes []).
# El bucle "for" recorre la lista uno a uno, guardando cada elemento en "nombre".
def saludo_multiple(nombres):
    for nombre in nombres:
        print(f"Hola, {nombre}")
 
# Pasamos la lista directamente. Imprime: "Hola, Ana", "Hola, Pep", "Hola, Eva".
saludo_multiple(["Ana", "Pep", "Eva"])
 
# También podemos guardar la lista en una variable primero y luego pasarla.
# Resultado idéntico: la función no sabe si recibe una lista directa o una variable.
alumnos = ["Iu", "Rosa", "Juan"]
saludo_multiple(alumnos)
 
# La función no sabe que esto son flores, solo ve una lista de textos.
# Resultado: "Hola, Rosa", "Hola, Jazmín", "Hola, Crisantemo".
flores = ["Rosa", "Jazmín", "Crisantemo"]
saludo_multiple(flores)
 
# Una lista con un solo elemento sigue siendo una lista.
# Resultado: "Hola, pEPE" (respeta mayúsculas y minúsculas tal cual).
saludo_multiple(["pEPE"])
# Esta función dibuja un "arbolito" de asteriscos.
# Usamos un bucle "while" (mientras) en lugar de "for".
# "while" repite el bloque MIENTRAS la condición sea verdadera.
def arbolito(tamanyo):
    # "contador" empieza en 0. Es la variable que controla cuántas veces repetimos.
    contador = 0
    # El bucle continúa mientras "contador" sea menor o igual que "tamanyo".
    while contador <= tamanyo:
        # "*" * contador repite el carácter "*" tantas veces como indica "contador".
        # Con contador=0 imprime "" (nada), con 1 imprime "*", con 2 "**", etc.
        print("*" * contador)
        # ¡Muy importante! Aumentamos el contador en 1 en cada vuelta.
        # Sin esta línea, el bucle nunca terminaría (bucle infinito).
        contador += 1
 
# Imprime líneas con 0, 1, 2, 3, 4 y 5 asteriscos.
arbolito(5)
# Imprime líneas con 0, 1, 2 y 3 asteriscos.
arbolito(3)
# Hasta ahora las funciones solo imprimían. Esta función DEVUELVE un resultado con "return".
# El valor devuelto puede guardarse en una variable o usarse en una expresión.
def doble(numero):
    return numero * 2
 
# La función calcula 5*2=10 y lo devuelve... pero nadie lo recoge. No pasa nada visible.
doble(5)  # Esto no hace nada
 
# Ahora sí: pasamos el valor devuelto directamente a print(). Imprime: 10.
print(doble(5))  # Esto imprime 10
 
# También podemos guardar el resultado en una variable para usarlo más tarde.
resultado = doble(5)  # resultado vale 10
 
# Esta versión usa print() dentro en lugar de return.
# Imprime el resultado, pero NO lo devuelve: no podemos guardarlo en una variable.
def doble_sin_return(numero):
    print(numero * 2)
 
# Imprime 10, pero no podemos hacer resultado = doble_sin_return(5) de forma útil.
doble_sin_return(5)
# Esta función comprueba si un número es par y devuelve True (verdadero) o False (falso).
# El operador "%" calcula el RESTO de una división. Si numero % 2 == 0, es divisible entre 2.
def es_par(numero):
    if numero % 2 == 0:  # 14 % 2 = 0 → es par
        return True
    else:
        return False
 
# Guardamos el número en una variable para mayor claridad.
numero = 14
# "if es_par(numero)" equivale a "if True" cuando el número es par.
# La función devuelve True/False directamente, así que podemos usarla en el if.
if es_par(numero):
    print("Es par")
else:
    print("Es impar")

Iteración de listas

# ─────────────────────────────────────────────
# DESCRIPCIÓN GENERAL DEL PROGRAMA
# ─────────────────────────────────────────────
# Este programa practica las operaciones más habituales sobre listas:
# - Encontrar el número más pequeño de una lista.
# - Encontrar el mayor y el menor
# - Dado un número, indica si está en la lista.
# - Añadido: contar cuantas veces aparece
# - Cuenta cuántas veces aparece cada número.

# La lista de trabajo. Contiene 20 números, algunos repetidos (3, -2, 7, 0)
# y algunos negativos. Todos los ejercicios trabajan sobre esta misma lista.
lista=[3, -2, 7, 0, 10, -5, 4, 8, -1, 6, 2, -3, 9, 1, -4, 5, 7, 0, 3, -2]


# ─────────────────────────────────────────────
# BLOQUE 1: Encontrar el mayor y el menor
# ─────────────────────────────────────────────

# Partimos de que el primer elemento es tanto el menor como el mayor provisional.
# lista[0] accede al primer elemento de la lista, que es el 3.
# Conforme recorramos la lista iremos actualizando estos valores si encontramos
# algo más pequeño o más grande.
menor=lista[0]
mayor=lista[0]

# Recorro la lista  ← comentario original
# En cada vuelta 'numero' toma el valor del siguiente elemento de la lista.
for numero in lista:

    # Si el número actual es menor que el menor que llevamos hasta ahora,
    # actualizamos el menor. Así al final de la lista menor tendrá el mínimo real.
    if numero<menor:
        menor=numero

    # Mismo patrón pero para el mayor.
    # Usamos dos if separados (no elif) porque queremos comparar ambas condiciones
    # en cada vuelta, aunque en la práctica un número nunca puede ser a la vez
    # mayor Y menor que el provisional.
    if numero>mayor:
        mayor=numero

# Imprimimos nuestro resultado y lo comparamos con las funciones built-in de Python.
# min(lista) y max(lista) hacen exactamente lo mismo que nuestro bucle pero en una línea.
# Si ambas columnas coinciden, nuestro algoritmo es correcto.
print(menor,min(lista))   # Resultado: -5 -5
print(mayor,max(lista))   # Resultado: 10 10

# Sugerencia: en la práctica siempre usarías min() y max() directamente.
# Hacer el bucle a mano sirve para entender cómo funcionan por dentro.


# ─────────────────────────────────────────────
# BLOQUE 2: Buscar si un número está en la lista
# ─────────────────────────────────────────────

# Dado un número, indica si está en la lista.  ← comentario original

# El número que queremos encontrar.
# Cambia este valor para buscar cualquier otro número.
buscado=9 # Número a buscar

# La bandera (flag) empieza en False.
# Se convertirá en True solo si encontramos el número.
encontrado=False

for numero in lista:
    if numero==buscado:
        encontrado=True
        # break interrumpe el bucle inmediatamente en cuanto encuentra el número.
        # No tiene sentido seguir recorriendo el resto de la lista si ya lo encontramos.
        # Sin el break el bucle seguiría hasta el final aunque ya supiera la respuesta.
        break

# Este if está FUERA del bucle. Se ejecuta una sola vez cuando el bucle ha terminado.
# En ese momento encontrado es True (si lo encontró) o False (si recorrió todo sin éxito).
if encontrado:
    print("Encontrado")
else:
    print("No encontrado")
# Resultado: Encontrado  (el 9 sí está en la lista, en la posición 12)

# Sugerencia: Python tiene una forma más corta de hacer esto sin bucle ni bandera:
#   if buscado in lista:
#       print("Encontrado")
# El operador 'in' hace la búsqueda por nosotros. Lo veremos más adelante en el código.


# ─────────────────────────────────────────────
# BLOQUE 3: Contar cuántas veces aparece un número concreto
# ─────────────────────────────────────────────

# Añadido: contar cuantas veces aparece  ← comentario original

# Ahora buscamos el 3. Fíjate que hay DOS 3 en la lista (posiciones 0 y 18).
buscado=3

# El contador acumulador empieza en 0.
# Cada vez que encontremos el número buscado, sumamos 1.
contador=0

for numero in lista:
    if numero==buscado:
        contador+=1   # equivale a: contador = contador + 1
                      # Aquí NO usamos break porque queremos contar TODAS las apariciones,
                      # no solo la primera. El bucle recorre la lista entera.

print(f"El número {buscado} aparece {contador} veces")
# Resultado: El número 3 aparece 2 veces

# Sugerencia: Python tiene el método count() que hace lo mismo en una línea:
#   print(lista.count(3))   → 2


# ─────────────────────────────────────────────
# BLOQUE 4: Contar cuántas veces aparece CADA número (con bucles anidados)
# ─────────────────────────────────────────────

# Cuenta cuántas veces aparece cada número.  ← comentario original

# Esta línea está comentada, es una lista de prueba más pequeña que el autor usó
# para verificar el comportamiento antes de aplicarlo a la lista grande.
#lista=[1,2,2,4]

# Bucle exterior: recorre cada número de la lista como "el buscado del momento".
# En cada vuelta buscado toma un valor de la lista: 3, luego -2, luego 7...
for buscado in lista:

    # El contador se reinicia a 0 en cada vuelta del exterior.
    # Esto es fundamental: si no lo reiniciáramos, el contador seguiría acumulando
    # de una búsqueda a la siguiente y los resultados serían incorrectos.
    contador = 0

    # Bucle interior: recorre TODA la lista buscando el número actual.
    # Por cada vuelta del exterior, el interior da 20 vueltas (tamaño de la lista).
    # Total de comparaciones: 20 × 20 = 400 comparaciones en total.
    for numero in lista:
        if numero == buscado:
            contador += 1

    # Este print está dentro del exterior pero fuera del interior.
    # Se ejecuta una vez por cada número buscado.
    print(f"El número {buscado} aparece {contador} veces")

# Resultado: imprime 20 líneas, una por cada elemento.
# Los repetidos aparecen varias veces: el 3 aparece dos veces en el resultado
# (una cuando buscado=3 la primera vez y otra cuando buscado=3 la segunda vez).
# Esto se mejorará en el último bloque usando una lista sin repetidos.

# Sugerencia: existe una estructura llamada diccionario (dict) que es perfecta
# para este tipo de conteos, pero eso es un tema más avanzado.


# ─────────────────────────────────────────────
# BLOQUE 5: Separar pares e impares con append
# ─────────────────────────────────────────────

# para añadir elementos a una lista usamos append  ← comentario original
# append(elemento) añade un elemento al FINAL de una lista existente.

# Dos listas vacías que iremos llenando dentro del bucle.
# Empiezan vacías [] y crecen con cada append.
pares=[]
impares=[]

for numero in lista:

    # numero%2 es el resto de dividir numero entre 2.
    # Si el resto es 0 el número es par (4%2=0, 6%2=0, 0%2=0).
    # Si el resto es 1 (o -1 para negativos) el número es impar.
    if numero%2==0:
        pares.append(numero)    # añade numero al final de la lista pares

    else:
        impares.append(numero)  # añade numero al final de la lista impares

# Los print están FUERA del bucle: muestran las listas completas al terminar.
print(f"Pares: {pares}")
print(f"Impares: {impares}")
# Resultado:
# Pares: [-2, 0, 10, 4, 8, 6, 2, 0, -2]  (divisibles entre 2, incluido el 0)
# Impares: [3, 7, -5, -1, -3, 9, 1, -4, 5, 7, 3]

# Sugerencia: Python tiene una forma compacta llamada comprensión de lista:
#   pares = [n for n in lista if n % 2 == 0]
# Pero usar append con el bucle es más claro cuando se está aprendiendo.


# ─────────────────────────────────────────────
# BLOQUE 6: Separar positivos y negativos
# ─────────────────────────────────────────────

# Con la otra lista separar entre positivos y negativos. El 0 lo ponemos en positivos
# ← comentario original. La decisión de incluir el 0 en positivos es arbitraria
# y se refleja en la condición: >= 0 en lugar de > 0.

positivos=[]
negativos=[]

for numero in lista:

    # El 0 entra aquí porque 0 >= 0 es True.
    # Si quisiéramos el 0 en negativos cambiaríamos >= por >.
    if numero>=0:
        positivos.append(numero)
    else:
        negativos.append(numero)

# len() devuelve el número de elementos que tiene una lista.
# len(positivos) nos dice cuántos números positivos (incluido el 0) hay.
print(f"Positivos: {positivos} y tiene {len(positivos)} elementos")
print(f"Negativos: {negativos}")
# Resultado:
# Positivos: [3, 7, 0, 10, 4, 8, 6, 2, 9, 1, 5, 7, 0, 3] y tiene 14 elementos
# Negativos: [-2, -5, -1, -3, -4, -2]


# ─────────────────────────────────────────────
# BLOQUE 7: Comprobar si un elemento está en una lista con 'in'
# ─────────────────────────────────────────────

# Como sabe si un elemento está en una lista  ← comentario original
# Este bloque muestra la forma corta de buscar, sin necesidad de bucle ni bandera.

buscado=34

# El operador 'in' recorre la lista internamente y devuelve True o False.
# Es equivalente al bucle con bandera del bloque 2, pero en una sola palabra.
# Si el 34 estuviera en la lista, entraría por el if. Como no está, entra por el else.
if buscado in lista:
    print(f"{buscado} está en lista")
else:
    print(f"{buscado} NO está en lista")
# Resultado: 34 NO está en lista

# 'not in' es el contrario: devuelve True si el elemento NO está en la lista.
# Ejemplo:
#   if buscado not in lista:
#       print("No está")


# ─────────────────────────────────────────────
# BLOQUE 8: Eliminar repetidos y contar apariciones de forma limpia
# ─────────────────────────────────────────────

# Redefinimos la lista con una más pequeña y con repetidos evidentes para ver mejor el resultado.
# Esta asignación sobreescribe la lista original del principio del programa.
lista=[1,2,2,4,1,4,9]

# Lista auxiliar donde guardaremos solo los valores únicos (sin repetidos).
# Iremos añadiendo números uno a uno, pero solo si no están ya dentro.
sin_repetidos=[]

for numero in lista:

    # 'not in' comprueba si numero NO está ya en sin_repetidos.
    # Si no está → lo añadimos. Si ya está → lo ignoramos (no entra en el if).
    # Traza con la lista [1,2,2,4,1,4,9]:
    # numero=1: sin_repetidos=[]       → 1 no está → añadimos → [1]
    # numero=2: sin_repetidos=[1]      → 2 no está → añadimos → [1,2]
    # numero=2: sin_repetidos=[1,2]    → 2 SÍ está → ignoramos → [1,2]
    # numero=4: sin_repetidos=[1,2]    → 4 no está → añadimos → [1,2,4]
    # numero=1: sin_repetidos=[1,2,4]  → 1 SÍ está → ignoramos → [1,2,4]
    # numero=4: sin_repetidos=[1,2,4]  → 4 SÍ está → ignoramos → [1,2,4]
    # numero=9: sin_repetidos=[1,2,4]  → 9 no está → añadimos → [1,2,4,9]
    if not numero in sin_repetidos:
        sin_repetidos.append(numero)

# Ahora sin_repetidos = [1, 2, 4, 9] — solo los valores únicos, en orden de aparición.

# Bucle exterior: recorre solo los valores únicos (4 vueltas en vez de 7).
# Así cada número aparece exactamente una vez en el resultado, sin duplicados.
for buscado in sin_repetidos:

    # Reiniciamos el contador en cada valor único buscado.
    contador = 0

    # Bucle interior: recorre la lista ORIGINAL completa (con repetidos)
    # para contar cuántas veces aparece el valor buscado.
    for numero in lista:
        if numero == buscado:
            contador += 1

    # Este print está dentro del exterior pero fuera del interior.
    # Imprime el número y su contador, pero SIN salto de línea al final (end=" ").
    # Así el siguiente print (vez/veces) se imprime en la misma línea.
    print(f"El número {buscado} aparece {contador}", end=" ")

    # Gramática correcta: "1 vez" en singular, "2 veces" en plural.
    # Este if decide qué palabra usar según el valor del contador.
    # Al no tener end=" ", este print sí añade el salto de línea normal al final.
    if contador != 1:
        print("veces")
    else:
        print("vez")

# Resultado final:
# El número 1 aparece 2 veces
# El número 2 aparece 2 veces
# El número 4 aparece 2 veces
# El número 9 aparece 1 vez    ← singular correcto gracias al if de arriba

# Sugerencia: el mismo resultado puede obtenerse con el método count() y sin bucles anidados:
#   for numero in sin_repetidos:
#       veces = lista.count(numero)
#       print(f"El número {numero} aparece {veces} {'vez' if veces == 1 else 'veces'}")
# La expresión 'vez' if veces == 1 else 'veces' es un if de una sola línea (operador ternario).

Explicación anidados

1. Configuración Inicial

Python

limite = 10

Aquí simplemente definimos hasta dónde queremos llegar. El programa hará cálculos para los números del 0 al 9 (porque en programación solemos empezar a contar desde el cero).

2. El Bucle Exterior (El «Contador de Filas»)

Python

for i in range(limite):

Este bucle se encarga de decidir con qué número estamos trabajando en cada momento. Se repetirá 10 veces.

  • En la primera vuelta, i vale 0.

  • En la última, i vale 9.

3. Preparando la Pizarra

Python

    suma = 0
    factorial = 1

¡Ojo aquí! Estas variables se reinician cada vez que el bucle exterior empieza una nueva vuelta.

  • La suma empieza en 0 porque es el elemento neutro de la suma.

  • El factorial empieza en 1 porque si fuera 0, cualquier multiplicación daría siempre 0.

4. El Bucle Interior (El «Calculador»)

Python

    for j in range(i):
        suma = suma + j + 1
        factorial = factorial * (j + 1)

Este es el corazón del código. Se encarga de hacer el trabajo sucio:

    • range(i): Si i vale 3, este bucle interno correrá para j = 0, 1, 2.

    • j + 1: Como j empieza en 0, le sumamos 1 para trabajar con números naturales (1, 2, 3…).

    • Suma: Va acumulando los números (1 + 2 + 3…).

    • Factorial: Va multiplicando los números (1 *2 * 3…).

Shutterstock
Explorar

5. El Resultado

Python

    print(f"{i}: {suma}, {factorial}")

Finalmente, el programa imprime una línea por cada valor de i, mostrándote cómo quedaron la suma y el factorial acumulados.

Ejemplo de lo que verías en pantalla:

i (Número) Suma (1 hasta i) Factorial (1 hasta i)
0 0 1
1 1 1
2 3 (1+2) 2 (1* 2)
3 6 (1+2+3) 6 (1* 2* 3)
4 10 (1+2+3+4) 24 (1* 2*t 3* 4)

Nota para principiantes: Fíjate que cuando i es 0, el bucle interior range(0) no se ejecuta ninguna vez, por eso la suma se queda en 0 y el factorial en 1.