IDS Guía de estudio

Semana 3 · Módulo 4

Estructuras repetitivas y patrones de control

Un negocio no procesa una venta: procesa cientos. Esta semana el programa aprende a repetir: leer datos hasta que el usuario diga “fin”, recorrer una serie de montos, acumular totales y detenerse cuando una aproximación es suficientemente buena.

Pregunta orientadora¿Cómo proceso cientos de transacciones sin escribir cientos de líneas, y cómo garantizo que el proceso termina?

Lecturas
PCC 7 · PCC 4 (inicio) · BOU 3
Proyecto
Fase 2 (esta semana)
Evaluación
Control 2: semana 5

Cómo usar esta guía

Antes de clasePCC 7, el inicio de PCC 4 y BOU 3. Luego el checkpoint.
Durante el laboratorioLa práctica es de trazas sin depurador: usa las tablas como modelo de cómo anotar.
Después de claseLos ejercicios usan input(): ejecútalos y prueba entradas raras.
ProyectoEntrega de la Fase 2: diagrama + matriz de casos borde.
  • Pregunta
  • →
  • Exploración
  • →
  • Implementación
  • →
  • Resultado
  • →
  • Interpretación
  • →
  • Verificación

Qué puedes usar esta semana

Sí, desde ahora

  • Todo lo de las semanas 1 y 2
  • input() (siempre devuelve str) y int()/float() para convertir
  • while, banderas (activo = True), break, continue
  • for sobre texto, sobre range() y sobre una lista escrita directamente: for monto in [120, 45, 300]:
  • Acumuladores, contadores, máximo/mínimo, bucles anidados
  • abs(), len() de un texto

Todavía no

  • Índices lista[0], append() y demás métodos de listas → semana 5. Esta semana una lista solo se recorre.
  • Funciones propias def → semana 4
  • try/except → semana 7 (la validación sigue siendo con if)
  • Diccionarios → semana 7 (PCC 7 termina con while sobre listas y diccionarios: déjalo para después)

Lecturas de preparación

Complementos gratuitos

Reading checkpoint

El usuario escribe 25 cuando el programa ejecuta cantidad = input("Cantidad: "). ¿Qué es cantidad?

input() siempre devuelve texto. Para operar hay que convertir: int(cantidad). Por eso cantidad * 2 daría "2525".

¿Cuántas veces se ejecuta el cuerpo de for mes in range(1, 12):?

range(inicio, fin) no incluye fin. Para los 12 meses: range(1, 13). Este “uno de menos” (off-by-one) es de los errores más frecuentes.

¿Qué hace continue dentro de un bucle?

continue = “esta vuelta no me interesa, sigue con la próxima” (por ejemplo, ignorar una devolución). break = “ya terminé, sal del bucle”.

¿Por qué este bucle nunca termina?

saldo = 500
while saldo > 0:
    print(f"Saldo pendiente: {saldo}")

Todo while necesita que algo en su cuerpo acerque la condición a ser falsa. Si te pasa en la terminal, detén el programa con Ctrl+C.

¿Cuándo es más natural usar while en lugar de for?

for: “para cada elemento” o “exactamente N veces”. while: “mientras se cumpla una condición”, con un número de vueltas desconocido.

¿Qué imprime este código?

total = 0
for monto in [10, 20, 30]:
    total = total + monto
    print(total)

El print está indentado, así que está dentro del bucle y se ejecuta en cada vuelta mostrando el acumulado. Si lo quisieras una sola vez, iría sin sangría, después del bucle.

Una bandera en un bucle es…

Ejemplo: activo = True y while activo:; cualquier parte del cuerpo puede poner activo = False. También sirve para registrar hallazgos: encontrado = True.

¿Cuántas líneas imprime este bucle anidado?

for sucursal in range(3):
    for dia in range(5):
        print(sucursal, dia)

Por cada una de las 3 vueltas externas, el bucle interno da 5 vueltas completas: 3 × 5 = 15.

while e input(): repetir hasta que algo pase

En palabras simples

while es “mientras haya clientes en la fila, sigue atendiendo”. No sabes cuántos vendrán; solo sabes cuándo parar. Si nada en el bucle acerca el final (nadie sale de la fila), el programa atiende para siempre: eso es un bucle infinito.

Caso: la caja registradora del día

Pregunta

El cajero digita los montos de las ventas una por una; al cerrar escribe fin. El sistema debe reportar el total, cuántas ventas hubo, el promedio y la venta más alta. Montos inválidos se rechazan sin detener la caja.

Exploración
  • No sabemos cuántas ventas habrá → while.
  • fin es un centinela: un valor especial que significa “ya no hay más datos”.
  • Necesitamos un acumulador (total), un contador (cantidad) y un máximo. Los tres se inicializan antes del bucle.
  • Caso borde: si el cajero escribe fin de inmediato, el promedio sería una división entre cero.
Diagrama de un bucle con centinela: inicializar total y cantidad, leer entrada, si es fin se sale y se muestra el reporte; si no, se acumula y se vuelve a leer.
Un bucle en un diagrama es una flecha que regresa a un punto anterior. La decisión es la condición de salida.
Implementación
total = 0.0
cantidad = 0
venta_mayor = 0.0
registrando = True          # bandera

print("Caja abierta. Escriba 'fin' para cerrar.")
while registrando:
    entrada = input("Monto: ").strip().lower()

    if entrada == "fin":
        registrando = False
    elif not entrada.replace(".", "", 1).isdigit():
        print("  Monto inválido, no se registró.")
    elif float(entrada) == 0:
        print("  Un monto de $0 no es una venta, no se registró.")
    else:
        monto = float(entrada)
        total = total + monto
        cantidad = cantidad + 1
        if monto > venta_mayor:
            venta_mayor = monto

print("--- Cierre de caja ---")
if cantidad > 0:
    print(f"Ventas: {cantidad}")
    print(f"Total: ${total:,.2f}")
    print(f"Promedio: ${total / cantidad:,.2f}")
    print(f"Venta mayor: ${venta_mayor:,.2f}")
else:
    print("No se registraron ventas.")
Resultado

Sesión en la terminal (lo que escribe el cajero va después de Monto:):

Caja abierta. Escriba 'fin' para cerrar.
Monto: 25.50
Monto: 100
Monto: diez
  Monto inválido, no se registró.
Monto: 0
  Un monto de $0 no es una venta, no se registró.
Monto: 74.50
Monto: fin
--- Cierre de caja ---
Ventas: 3
Total: $200.00
Promedio: $66.67
Venta mayor: $100.00
Interpretación

El bucle procesó 6 entradas pero registró solo 3 ventas: las inválidas se reportan y se ignoran sin detener la caja. La bandera registrando es la única forma de salir. Observa también que la validación con isdigit() va antes de float(entrada): si el orden se invierte, "diez" detiene el programa.

Una alternativa equivalente que verás en PCC 7 es while True: con break cuando llega fin.

Verificación
  • 25.50 + 100 + 74.50 = 200 ✔; 200 / 3 = 66.67 ✔.
  • Casos que debes probar: fin como primera entrada (¿evita dividir entre 0?), FIN en mayúsculas, un texto vacío (solo Enter), -5, 12.5.3.

for y range(): repetir un número conocido de veces

En palabras simples

for es “para cada factura del fajo, haz esto”. Sabes de antemano cuántas son. range(1, 6) es un fajo numerado del 1 al 5: el 6 marca dónde termina, pero no entra.

Caso: ¿cuánto crece un ahorro a plazo?

Pregunta

Un cliente deposita $1,000 a una tasa anual del 8 % con capitalización anual. ¿Cuánto tendrá al final de cada uno de los próximos 5 años?

Exploración

Sabemos exactamente cuántas vueltas: 5 → for con range(1, 6) (el 6 no se incluye). Cada año el saldo se multiplica por 1.08: el saldo es un acumulador que crece multiplicando.

range(1, 6) incluye los números del 1 al 5: el inicio se incluye y el fin no.
El “uno de menos” más famoso de Python: el fin de range nunca se incluye.
Implementación
capital = 1000.00
TASA = 0.08
ANIOS = 5

saldo = capital
print("Año   Interés      Saldo")
for anio in range(1, ANIOS + 1):
    interes = saldo * TASA
    saldo = saldo + interes
    print(f"{anio:>3}  {interes:>8.2f}  {saldo:>9.2f}")

print(f"Ganancia total: ${saldo - capital:,.2f}")
Resultado
Año   Interés      Saldo
  1     80.00    1080.00
  2     86.40    1166.40
  3     93.31    1259.71
  4    100.78    1360.49
  5    108.84    1469.33
Ganancia total: $469.33
Barras apiladas del saldo de un depósito de 1000 dólares al 8 % anual: 1080, 1166.40, 1259.71, 1360.49 y 1469.33; la parte de interés crece cada año.
El capital (azul) no cambia; la parte de interés (ocre) crece cada año porque se calcula sobre un saldo mayor. Valores exactos en la tabla de arriba.
Interpretación

El interés crece cada año porque se calcula sobre un saldo mayor: eso es el interés compuesto. En 5 años la ganancia ($469.33) supera el 5 × 8 % = 40 % que daría el interés simple ($400). Los formatos :>8.2f alinean los números a la derecha en columnas de ancho fijo.

Verificación

La fórmula cerrada es capital * (1 + TASA) ** ANIOS = 1000 × 1.08⁵ = 1469.33. Si el bucle y la fórmula coinciden, ambos están bien. Comprobar un resultado con un método independiente es una técnica que vas a usar todo el curso.

Recorrer una serie de valores

Un for también recorre directamente los elementos de una lista escrita en el código. Esta semana solo la recorremos; crearla, modificarla y usar índices es tema de la semana 5.

for monto in [120, 45, 300, 80]:
    print(f"Procesando venta de ${monto}")
print("Listo.")          # sin sangría: se ejecuta UNA vez, al final
Procesando venta de $120
Procesando venta de $45
Procesando venta de $300
Procesando venta de $80
Listo.
Errores de indentación (PCC 4)
  • Olvidar sangrar el cuerpo → IndentationError: expected an indented block.
  • Sangrar algo que debía ir después del bucle → se repite N veces (error lógico: no hay mensaje, solo un resultado equivocado).
  • Olvidar los dos puntos : al final del for → SyntaxError.

Patrones de bucle que se repiten en todo problema de negocio

En palabras simples

Un acumulador es la caja registradora que va sumando; un contador es el clicker del portero; una bandera es un banderín que levantas si pasó algo. Los tres se preparan antes de empezar a atender, nunca adentro del bucle.

PatrónAntes del bucleDentro del bucleResponde…
Acumuladortotal = 0total = total + monto¿cuánto se vendió?
Contadorn = 0n = n + 1 (a veces dentro de un if)¿cuántas ventas superan $100?
Máximo / mínimomayor = primer valor (o un valor seguro)if x > mayor: mayor = x¿cuál fue la mejor venta?
Banderahubo_devolucion = Falseif monto < 0: hubo_devolucion = True¿ocurrió algo al menos una vez?
Promedioacumulador + contadorambosdespués del bucle: total / n si n > 0

break y continue en un mismo ejemplo: revisar las transacciones del día, ignorar las devoluciones (negativas) y detenerse si aparece un monto sospechoso.

LIMITE_SOSPECHOSO = 5000
total = 0
revisadas = 0

for monto in [120, -30, 450, 7200, 80]:
    if monto < 0:
        continue                      # devolución: se salta
    if monto > LIMITE_SOSPECHOSO:
        print(f"Alerta: ${monto} requiere autorización. Revisión detenida.")
        break                         # deja de revisar
    total = total + monto
    revisadas = revisadas + 1

print(f"Revisadas: {revisadas}, total: ${total}")
Alerta: $7200 requiere autorización. Revisión detenida.
Revisadas: 2, total: $570

El 80 nunca se procesó: break termina el bucle completo. El −30 se saltó sin contarse.

Bucles anidados

# Valor futuro de $1,000 para distintas tasas y plazos
for tasa in [0.05, 0.10]:
    print(f"Tasa {tasa:.0%}:", end=" ")
    for anios in range(1, 4):
        valor = 1000 * (1 + tasa) ** anios
        print(f"{anios}a=${valor:,.2f}", end="  ")
    print()   # salto de línea al terminar cada tasa
Tasa 5%: 1a=$1,050.00  2a=$1,102.50  3a=$1,157.63
Tasa 10%: 1a=$1,100.00  2a=$1,210.00  3a=$1,331.00

El bucle interno completa todas sus vueltas por cada vuelta del externo. end=" " hace que print no salte de línea.

Aproximación numérica: repetir hasta que sea “suficientemente bueno”

En palabras simples

Aproximar es como adivinar un número con “más alto / más bajo”: cada intento te acerca y paras cuando estás lo bastante cerca. La computadora hace eso miles de veces por segundo.

Caso: la tasa de crecimiento anual promedio

Pregunta

Las ventas pasaron de $100 mil a $121 mil en 2 años. ¿Cuál fue la tasa de crecimiento anual promedio? Matemáticamente es √1.21 − 1, pero queremos calcular la raíz sin una función de raíz, para entender cómo lo hace una computadora.

Exploración

El método de Herón: si x es una estimación de √v, entonces (x + v / x) / 2 es una estimación mejor. Se repite hasta que la mejora sea menor que una tolerancia. No sabemos cuántas vueltas harán falta → while.

Implementación
valor = 1.21           # 121 / 100: factor de crecimiento en 2 años
TOLERANCIA = 0.0001

x = valor              # primera estimación (cualquier positivo sirve)
iteracion = 0
seguir = True
while seguir:
    nueva_x = (x + valor / x) / 2
    iteracion = iteracion + 1
    cambio = abs(nueva_x - x)
    print(f"Iteración {iteracion}: x = {nueva_x:.6f}  (cambio {cambio:.6f})")
    if cambio < TOLERANCIA:
        seguir = False
    x = nueva_x

print(f"Crecimiento anual promedio: {x - 1:.2%}")
Resultado
Iteración 1: x = 1.105000  (cambio 0.105000)
Iteración 2: x = 1.100011  (cambio 0.004989)
Iteración 3: x = 1.100000  (cambio 0.000011)
Crecimiento anual promedio: 10.00%
Interpretación

En 3 iteraciones el método llegó a 1.1: las ventas crecieron 10 % anual en promedio. Ojo: no es 21 % / 2 = 10.5 %. Con crecimiento compuesto, promediar tasas simples sobreestima.

Verificación

Comprueba al revés: 100 × 1.10 × 1.10 = 121 ✔. Y garantiza que el bucle termina: cada iteración reduce el cambio, así que eventualmente baja de la tolerancia. ¿Qué pasaría con valor = 0 o negativo? (Pista: valor / x con x = 0.) Eso se valida antes del bucle.

Tablas de traza (sin depurador)

En el laboratorio de esta semana se trazan programas a mano, anotando el estado de las variables en cada vuelta. Una fila = una vuelta del bucle, con los valores al final de esa vuelta.

Traza 1 · Acumulador y contador

total = 0
grandes = 0
for monto in [120, 45, 300, 80]:
    total = total + monto
    if monto >= 100:
        grandes = grandes + 1
print(total, grandes)
545 2
Vueltamontototalgrandes
1
2
3
4

Traza 2 · Pago de una deuda

Deuda de $1,000 con interés del 2 % mensual y pagos de $300. El último pago es solo lo que falta.

saldo = 1000.00
TASA_MENSUAL = 0.02
CUOTA = 300.00
mes = 0

while saldo > 0:
    mes = mes + 1
    interes = round(saldo * TASA_MENSUAL, 2)
    saldo = saldo + interes
    if saldo < CUOTA:
        pago = saldo
    else:
        pago = CUOTA
    saldo = round(saldo - pago, 2)
    print(f"Mes {mes}: interés {interes:.2f}, pago {pago:.2f}, saldo {saldo:.2f}")
mesinterespagosaldo al final
1
2
3
4
Preguntas sobre la traza 2

¿Cuánto pagó en total? 300 × 3 + 145.95 = $1,045.95, es decir $45.95 de intereses.

¿Qué pasa si la cuota fuera $15? El primer mes el interés es $20: el saldo crece y el while nunca termina. Un programa defensivo verifica antes del bucle que CUOTA > saldo * TASA_MENSUAL.

Traza 3 · Método de Herón (√1.21)

Usa heron.py. Redondea a 6 decimales; en “¿para?” escribe sí o no.

iteraciónx antesnueva_xcambio¿para?
11.21
2
3

Ejercicios

E1 · ¿En cuántos meses pago mi deuda?

while + validación · ★★

Pide con input() el monto de la deuda, la tasa de interés mensual (en %, p. ej. 2) y la cuota. Muestra en cuántos meses se paga y cuánto se pagó de intereses. Si la cuota no alcanza a cubrir el interés del primer mes, avisa y no entres al bucle.

Pista

Reutiliza la traza 2. Convierte la tasa: tasa = float(texto) / 100. Acumula el interés en intereses_totales.

Solución
saldo = float(input("Deuda: $"))
tasa = float(input("Interés mensual (%): ")) / 100
cuota = float(input("Cuota: $"))

if saldo <= 0 or cuota <= 0 or tasa < 0:
    print("Los datos deben ser positivos.")
elif cuota <= saldo * tasa:
    print("La cuota no cubre ni el interés: la deuda nunca se pagaría.")
else:
    meses = 0
    intereses_totales = 0.0
    while saldo > 0:
        meses = meses + 1
        interes = round(saldo * tasa, 2)
        intereses_totales = intereses_totales + interes
        saldo = saldo + interes
        if saldo < cuota:
            saldo = 0
        else:
            saldo = round(saldo - cuota, 2)
    print(f"Se paga en {meses} meses; intereses: ${intereses_totales:,.2f}")
Deuda: $1000
Interés mensual (%): 2
Cuota: $300
Se paga en 4 meses; intereses: $45.95

Verificación: coincide con la traza 2 ($45.95). Prueba la cuota de $15 (debe avisar sin colgarse) y la de $20 exacta (la cuota iguala el interés: también debe avisar, por eso la condición es <=). Aquí float(input()) todavía se detiene si escriben letras; para evitarlo, valida con la técnica de la semana 2 o espera a try en la semana 7.

E2 · ¿Cuándo se duplica mi inversión?

while vs. for · ★

Con $1,000 al 8 % anual compuesto, ¿cuántos años hacen falta para al menos duplicar el capital? Compáralo con la “regla del 72” (72 / tasa en % ≈ años para duplicar).

Solución
capital = 1000.00
TASA = 0.08

saldo = capital
anios = 0
while saldo < 2 * capital:
    saldo = saldo * (1 + TASA)
    anios = anios + 1

print(f"Se duplica en {anios} años (saldo: ${saldo:,.2f})")
print(f"Regla del 72: {72 / (TASA * 100):.1f} años")
Se duplica en 10 años (saldo: $2,158.92)
Regla del 72: 9.0 años

Interpretación: en el año 9 el saldo es $1,999.00, a un dólar de duplicarse; por eso hacen falta 10 años completos. La regla del 72 es una aproximación mental, y el programa la confirma. Aquí while es natural: no sabíamos cuántos años.

E3 · PIN con tres intentos

for + break + bandera · ★★

Un cajero automático da 3 intentos para ingresar el PIN ("4821"). Si acierta, muestra “Bienvenido” y deja de pedir. Si falla los 3, muestra “Tarjeta retenida”. En cada fallo indica cuántos intentos quedan.

Solución
PIN_CORRECTO = "4821"
MAX_INTENTOS = 3
acceso = False

for intento in range(1, MAX_INTENTOS + 1):
    pin = input("PIN: ").strip()
    if pin == PIN_CORRECTO:
        acceso = True
        break
    restantes = MAX_INTENTOS - intento
    print(f"PIN incorrecto. Intentos restantes: {restantes}")

if acceso:
    print("Bienvenido.")
else:
    print("Tarjeta retenida. Contacte a su banco.")
PIN: 1111
PIN incorrecto. Intentos restantes: 2
PIN: 4821
Bienvenido.

Por qué la bandera: al salir del bucle hay dos historias posibles (acertó o se acabaron los intentos). acceso recuerda cuál fue. El PIN se compara como texto: "04821" no es lo mismo que "4821", y así debe ser.

Verificación: acertar al primer intento, al tercero, y fallar los tres.

E4 · Resumen de ventas de la semana

patrones combinados · ★★

Ventas de lunes a domingo: [340, 410, 0, 520, 610, 890, 450]. Sin índices ni métodos de lista, calcula: total, promedio diario, mejor día (número del 1 al 7) y cuántos días estuvieron por encima del promedio.

Pista

Para saber qué número de día es, lleva un contador dia que aumente en cada vuelta. Para “por encima del promedio” necesitas conocer el promedio primero… ¿cuántas pasadas hacen falta?

Solución
total = 0
dias = 0
mejor_venta = 0
mejor_dia = 0

for venta in [340, 410, 0, 520, 610, 890, 450]:
    dias = dias + 1
    total = total + venta
    if venta > mejor_venta:
        mejor_venta = venta
        mejor_dia = dias

promedio = total / dias

sobre_promedio = 0
for venta in [340, 410, 0, 520, 610, 890, 450]:   # segunda pasada
    if venta > promedio:
        sobre_promedio = sobre_promedio + 1

print(f"Total: ${total}  Promedio: ${promedio:.2f}")
print(f"Mejor día: {mejor_dia} (${mejor_venta})")
print(f"Días sobre el promedio: {sobre_promedio}")
Total: $3220  Promedio: $460.00
Mejor día: 6 ($890)
Días sobre el promedio: 3

Interpretación: hacen falta dos pasadas porque el promedio no se conoce hasta terminar la primera. Repetir la lista escrita dos veces es incómodo (y peligroso si cambias una y no la otra); en la semana 5 la guardarás en una variable.

Verificación: 340+410+0+520+610+890+450 = 3220; 3220/7 = 460; sobre 460: 520, 610, 890 → 3 ✔.

E5 · Auditoría: el reporte que no suma

auditoría · ★★★

Este código debería reportar total, promedio y días sin venta de [200, 150, 0, 350]. Corre sin errores, pero imprime Total: 350, promedio: 87.5, días sin venta: 0. Traza y encuentra los tres fallos.

dias_sin_venta = 0
for monto in [200, 150, 0, 350]:
    total = 0
    total = total + monto
    if monto == 0:
        dias_sin_venta + 1
promedio = total / 4
print(f"Total: {total}, promedio: {promedio}, días sin venta: {dias_sin_venta}")
Total: 350, promedio: 87.5, días sin venta: 0

Y en este otro, ¿por qué el programa se queda “colgado”? (no lo ejecutes sin saber cómo detenerlo: Ctrl+C)

dia = 1
while dia <= 7:
    print(f"Registrando ventas del día {dia}")
Solución
  1. Acumulador reiniciado dentro del bucle: total = 0 se ejecuta en cada vuelta, así que solo queda el último monto (350). Va antes del for.
  2. Expresión sin asignar: dias_sin_venta + 1 calcula un valor y lo tira. Debe ser dias_sin_venta = dias_sin_venta + 1.
  3. Divisor fijo: / 4 funciona hoy, pero falla en silencio si mañana hay 5 días. Usa un contador.
  4. Segundo programa: dia nunca cambia; falta dia = dia + 1 dentro del bucle.
total = 0
dias = 0
dias_sin_venta = 0
for monto in [200, 150, 0, 350]:
    total = total + monto
    dias = dias + 1
    if monto == 0:
        dias_sin_venta = dias_sin_venta + 1
promedio = total / dias
print(f"Total: {total}, promedio: {promedio}, días sin venta: {dias_sin_venta}")
Total: 700, promedio: 175.0, días sin venta: 1

Lección: ninguno de estos fallos produce un mensaje de error. Solo los detecta alguien que traza el código o que compara el resultado con un cálculo independiente (200+150+0+350 = 700). Es el tipo de error que se esconde en código generado por IA que “se ve bien”.

Práctica intensiva

Primero ejercicios rápidos de lectura de bucles (se corrigen solos), luego problemas tipo laboratorio. El P3 es el tipo de “aproximación numérica” que menciona el programa del curso.

Ejercicios rápidos · ¿Qué imprime?

Escribe todo lo que imprime, separado por espacios (por ejemplo 0 1 2).

CódigoTu respuesta
for i in range(3):
    print(i)
for i in range(2, 11, 3):
    print(i)
for i in range(5, 0, -2):
    print(i)
n = 10
v = 0
while n > 0:
    n -= 3
    v += 1
print(v, n)
total = 0
for x in range(1, 5):
    total += x * x
print(total)
for n in range(1, 10):
    if n * n > 20:
        break
print(n)
s = 0
for n in range(6):
    if n % 2 == 0:
        continue
    s += n
print(s)
c = 0
for a in range(3):
    for b in range(a):
        c += 1
print(c)

Problemas tipo examen

P1 · Tabla de amortización completa

for + acumulador + ajuste final · ★★★

Un préstamo de $1,200 al 1.5 % mensual en 6 cuotas fijas. Imprime, mes a mes, la cuota, el interés, el abono a capital y el saldo. La última cuota debe dejar el saldo exactamente en $0.00 (ajusta los centavos). Al final, muestra el total de intereses.

Tu programa debe cumplir estos casos de prueba:

Qué comprobarValor esperado
Cuota fija$210.63
Interés del mes 1$18.00 (1,200 × 1.5 %)
Saldo después del mes 6$0.00
Suma de abonos a capital$1,200.00
Intereses totales$63.78
Pista

La cuota se calcula una vez con la fórmula de la semana 4 (aquí escrita directamente). En cada mes: interés = saldo × tasa; capital = cuota − interés; saldo = saldo − capital. En el último mes, el capital es todo lo que queda.

Solución
monto = 1200.00
i = 0.015                 # 1.5 % mensual
n = 6

cuota = round(monto * i / (1 - (1 + i) ** -n), 2)
saldo = monto
total_intereses = 0

print("Mes   Cuota   Interés   Capital     Saldo")
for mes in range(1, n + 1):
    interes = round(saldo * i, 2)
    if mes == n:
        capital = saldo              # la última cuota ajusta los centavos
    else:
        capital = round(cuota - interes, 2)
    saldo = round(saldo - capital, 2)
    total_intereses += interes
    print(f"{mes:>3} {interes + capital:>7.2f} {interes:>9.2f} {capital:>9.2f} {saldo:>9.2f}")

print(f"Intereses totales: ${total_intereses:.2f}")
Mes   Cuota   Interés   Capital     Saldo
  1  210.63     18.00    192.63   1007.37
  2  210.63     15.11    195.52    811.85
  3  210.63     12.18    198.45    613.40
  4  210.63      9.20    201.43    411.97
  5  210.63      6.18    204.45    207.52
  6  210.63      3.11    207.52      0.00
Intereses totales: $63.78

Interpretación: al principio casi un 9 % de la cuota es interés; al final, apenas un 1.5 %. Por eso adelantar pagos al inicio de un préstamo ahorra más.

P2 · Movimientos de inventario

while + input + validación · ★★★

El bodeguero registra movimientos: +N es una entrada y -N una salida. Con fin termina. El stock empieza en 20. Una salida mayor que el stock se rechaza; un texto inválido también. Al final: stock, total de entradas, total de salidas y número de rechazos. El programa nunca debe detenerse por un dato raro.

Tu programa debe cumplir estos casos de prueba:

Entradas que digitaResultado esperado
+5, -30, -10, diez, +3, finStock final 18 · entradas 8 · salidas 10 · rechazos 2
finStock final 20 · todo en 0
-20, -1, finStock final 0 · salidas 20 · rechazos 1
Pista

Usa startswith("+") para saber el tipo de movimiento y replace("+", "", 1).isdigit() para validar el número antes de convertirlo.

Solución
stock = 20
entradas = 0
salidas = 0
rechazos = 0

print("Movimientos: +N para entrada, -N para salida, 'fin' para terminar.")
seguir = True
while seguir:
    mov = input("> ").strip().lower()
    if mov == "fin":
        seguir = False
    elif mov.startswith("+") and mov.replace("+", "", 1).isdigit():
        cantidad = int(mov.replace("+", "", 1))
        stock += cantidad
        entradas += cantidad
    elif mov.startswith("-") and mov.replace("-", "", 1).isdigit():
        cantidad = int(mov.replace("-", "", 1))
        if cantidad > stock:
            print(f"  Rechazado: solo hay {stock} unidades.")
            rechazos += 1
        else:
            stock -= cantidad
            salidas += cantidad
    else:
        print("  Formato inválido.")
        rechazos += 1

print(f"Stock final: {stock} | entradas: {entradas} | salidas: {salidas} | rechazos: {rechazos}")
Movimientos: +N para entrada, -N para salida, 'fin' para terminar.
> +5
> -30
  Rechazado: solo hay 25 unidades.
> -10
> diez
  Formato inválido.
> +3
> fin
Stock final: 18 | entradas: 8 | salidas: 10 | rechazos: 2

Verificación: 20 + 5 = 25; −30 se rechaza; 25 − 10 = 15; “diez” se rechaza; 15 + 3 = 18 ✔.

P3 · ¿Qué tasa esconde esta cuota?

aproximación numérica (bisección) · ★★★★

Un anuncio dice: “Llévate $1,000 y paga solo $94.56 al mes durante 12 meses”. No dice la tasa. Encuéntrala con el método de bisección: prueba la tasa del medio entre un mínimo y un máximo; si la cuota que da es muy alta, la tasa real está en la mitad de abajo; si es muy baja, en la de arriba. Repite hasta que la cuota calculada esté a menos de $0.001 de la del anuncio.

Tu programa debe cumplir estos casos de prueba:

Qué comprobarValor esperado
Tasa mensual≈ 2.00 %
Tasa anual nominal≈ 24 %
Iteracionespocas (menos de 20)
Garantía de que terminalímite de 100 iteraciones
Pista

Es el mismo “más alto / más bajo” de adivinar un número. Guarda dos variables, bajo y alto, y en cada vuelta reemplaza una de las dos por medio.

Solución
monto = 1000.00
n = 12
cuota_banco = 94.56          # lo único que dice el anuncio

bajo = 0.0001                # tasa mensual mínima que probamos
alto = 0.10                  # tasa mensual máxima que probamos
iteraciones = 0
encontrada = False

while not encontrada and iteraciones < 100:
    medio = (bajo + alto) / 2
    cuota = monto * medio / (1 - (1 + medio) ** -n)
    iteraciones += 1
    if abs(cuota - cuota_banco) < 0.001:
        encontrada = True
    elif cuota > cuota_banco:
        alto = medio         # la tasa es demasiado alta
    else:
        bajo = medio         # la tasa es demasiado baja

print(f"Tasa mensual: {medio:.4%} en {iteraciones} iteraciones")
print(f"Tasa anual nominal: {medio * 12:.2%}")
Tasa mensual: 2.0002% en 8 iteraciones
Tasa anual nominal: 24.00%

Interpretación: 2 % mensual suena poco, pero son 24 % anuales. Así se calcula la tasa implícita de cualquier crédito cuando solo conoces la cuota. Verificación: sustituye 0.02 en la fórmula de la cuota y debe dar $94.56.

Proyecto integrador · Fase 2

Semana 3 · Lógica algorítmica y casos de prueba.

Entregables de esta fase

Plantilla de matriz (ejemplo para un sistema de inventario):

IDRegla que pruebaEntradaResultado esperado¿Por qué es borde?
CB-01No vender más que el stockstock 10, pedido 10Venta aceptada; stock 0Igual al límite
CB-02No vender más que el stockstock 10, pedido 11Rechazada, stock sigue en 10Uno por encima del límite
CB-03Cantidad válidapedido 0Rechazada: “cantidad debe ser > 0”Valor nulo
CB-04Cantidad válidapedido "diez"Rechazada sin detener el programaTipo incorrecto
CB-05Alerta de reordenstock queda en 5 (mínimo 5)Se genera alertaFrontera <= vs <

Deja una columna vacía de “resultado obtenido”: en la Fase 5 estos casos se convierten en pruebas automáticas.

Preguntas de defensa

¿Cómo garantizan que cada bucle de su sistema termina?

Para cada while, señala qué variable cambia en cada vuelta y por qué eso acerca la condición a ser falsa. Si depende del usuario (un centinela), explica qué pasa con entradas inválidas.

¿Por qué eligieron for o while en tal parte?

for cuando se conoce la cantidad o se recorren todos los elementos; while cuando se repite hasta que ocurra algo (centinela, saldo en cero, tolerancia).

¿Qué caso borde encontraron que no esperaban?

Ten uno preparado con su historia: cómo lo encontraron (traza, prueba), qué fallaba y cómo lo resolvieron. Es de las preguntas que mejor muestran dominio del trabajo.

¿Qué pasa si el usuario escribe texto donde va un número?

Hoy: se valida con métodos de texto antes de convertir. Desde la Fase 4 (semana 7): try/except ValueError. En ambos casos, el programa no debe cerrarse.

Expliquen el diagrama de flujo: ¿dónde está la decisión más importante?

Cada integrante debe poder recorrer el diagrama con un caso concreto de la matriz y decir por qué rama pasa.

Rumbo al Control 2

El Control 2 es en la semana 5 (confirma el temario en Moodle). De esta semana, asegúrate de poder:

Fuentes

  • [PCC] Matthes, E. (2023). Python Crash Course, 3.ª ed. Cap. 7 y sección inicial del cap. 4.
  • [BOU] Bouna, P. (2022). Fundamentals of Programming Using Flowchart and Pseudocode. Cap. 3.
  • Python Software Foundation. Tutorial — Más herramientas para control de flujo.