Proyecto corto 1 · La caja registradora de TechShop¶
Objetivos. En este proyecto escribirás y probarás tu primer programa completo: un menú de terminal que muestra un catálogo, permite añadir productos a un carrito, calcula el importe con un posible descuento y genera un ticket formateado. Solo necesitarás lo aprendido en 1.2–1.7.
Evidencia de logro. Entregarás el script 05b_caja_registradora.py, junto con una ejecución de ejemplo y una breve explicación de
la herramienta utilizada para resolver cada requisito.
Contexto y motivación¶
En los capítulos anteriores has aprendido a usar variables, condicionales, bucles, listas y diccionarios. Ahora toca combinarlos para construir un programa que funcione de principio a fin.
TechShop va a abrir su primer punto de venta físico y necesita una caja registradora de terminal. Para este proyecto no habrá base de datos ni librerías externas: solo Python y las herramientas que ya conoces. Está pensado para unas 3 horas y sirve para consolidar lo aprendido en la semana 4; no es un examen.
El proyecto también te permitirá detectar código que se repite. Esa observación prepara el siguiente capítulo, 1.8 · Funciones: allí aprenderás a escribir una operación una sola vez y reutilizarla. Conserva tu solución, porque volverás a trabajar con ella.
Vocabulario¶
| Término | Significado en el proyecto |
|---|---|
| Catálogo | Los productos disponibles, con su precio |
| Carrito | Lo que el cliente va comprando: {código: unidades} |
| Subtotal | Precio × unidades de una línea |
| Total | Suma de los subtotales |
| Ticket | El resumen final de la compra |
| Menú | El bucle que muestra opciones y ejecuta la elegida |
| Acumulador | Variable que va sumando resultados (total += subtotal) |
Prerrequisitos¶
- 1.2 · Variables y tipos:
input()devuelve texto,int()para convertir, f-strings para mostrar. - 1.4 · Condicionales y 1.5 · Bucles:
el menú es un
whileconif/elif. - 1.6 · Listas y tuplas y 1.7 · Diccionarios y conjuntos: el catálogo y el carrito son diccionarios.
El programa no utiliza funciones (se introducen en 1.8), ficheros (1.10) ni librerías externas (1.15).
El enunciado¶
La caja debe cumplir estos requisitos:
- Mostrar un menú con 5 opciones y repetirse hasta que se elija salir.
- Ver catálogo: listar los productos con su precio alineado en columnas.
- Añadir al carrito: pedir código y unidades; avisar si el código no existe o las unidades son menores que 1.
- Ver carrito: listar las líneas, el total y el número de unidades.
- Cobrar: emitir un ticket; si el total llega a 60 €, aplicar un descuento del 10 %; vaciar el carrito tras cobrar.
- Salir con un mensaje de despedida.
1. Alinear los precios¶
Para que el catálogo y el ticket sean fáciles de leer, los precios deben quedar
alineados en columnas. Ya conoces las f-strings de 1.2; en este ejemplo se
introduce una opción que estudiarás con más detalle en 1.9 · Cadenas:
dentro de las llaves puedes indicar un ancho fijo y el número de decimales.
:>8.2f alinea a la derecha en un campo de 8 caracteres y muestra 2 decimales;
:<8 alinea a la izquierda:
Salida esperada:
Al usar un ancho fijo, los precios ocupan el mismo espacio aunque tengan distintas longitudes y las columnas quedan alineadas. Cuando estudies 1.9, puedes volver a este ejemplo para consultar las demás opciones de formato.
2. Paso 1: el catálogo¶
El catálogo es un diccionario de diccionarios: cada código apunta a otro diccionario con los datos del producto. Los códigos son enteros, no texto.
catalogo = {
101: {"nombre": "Camiseta TechShop", "precio": 14.50},
102: {"nombre": "Taza de la marca", "precio": 8.90},
103: {"nombre": "Sudadera con capucha", "precio": 32.00},
104: {"nombre": "Pegatinas (pack x5)", "precio": 3.20},
105: {"nombre": "Gorra bordada", "precio": 18.00},
}
print("CATÁLOGO")
for codigo, producto in catalogo.items():
print(f" {codigo} · {producto['nombre']:<24}{producto['precio']:>7.2f} €")
Salida esperada:
CATÁLOGO
101 · Camiseta TechShop 14.50 €
102 · Taza de la marca 8.90 €
103 · Sudadera con capucha 32.00 €
104 · Pegatinas (pack x5) 3.20 €
105 · Gorra bordada 18.00 €
Esta estructura resulta útil porque el código es la clave: comprobar si un
producto existe (codigo in catalogo) es inmediato y no hace falta recorrer una
lista.
3. Paso 2: añadir al carrito¶
El carrito relaciona cada código con sus unidades: {código: unidades}. Lo
importante es acumular las nuevas unidades cuando el producto ya está en el
carrito. Para ello usaremos .get(clave, valor_si_no_está):
carrito = {}
carrito[101] = carrito.get(101, 0) + 2 # primeras 2 camisetas
carrito[103] = carrito.get(103, 0) + 1
carrito[104] = carrito.get(104, 0) + 4
print(carrito)
Salida esperada:
Si el cliente vuelve a pedir camisetas, carrito.get(101, 0) devuelve 2 y
las nuevas unidades se suman a las que ya había. Una asignación como
carrito[101] = 2 sobrescribiría el valor anterior. Es uno de los errores
que conviene evitar.
4. Paso 3: ver el carrito y el total¶
Para mostrar el carrito, lo recorremos con .items() y mantenemos dos
acumuladores: uno para las unidades y otro para el total.
print("CARRITO")
total_unidades = 0
total = 0.0
for codigo, unidades in carrito.items():
nombre = catalogo[codigo]["nombre"]
subtotal = catalogo[codigo]["precio"] * unidades
total_unidades += unidades
total += subtotal
print(f" {unidades:>2} × {nombre:<24}{subtotal:>7.2f} €")
print(f" {'Total':<28}{total:>7.2f} € ({total_unidades} unidades)")
Salida esperada:
CARRITO
2 × Camiseta TechShop 29.00 €
1 × Sudadera con capucha 32.00 €
4 × Pegatinas (pack x5) 12.80 €
Total 73.80 € (7 unidades)
El carrito solo guarda códigos, no nombres ni precios: estos datos se consultan en el catálogo cuando hacen falta. Así, cada dato se guarda una sola vez.
5. Paso 4: el descuento¶
El descuento se decide con un if sobre el total acumulado. El umbral y el
porcentaje se guardan en constantes con nombre, al principio del programa,
para poder cambiarlos desde un único lugar:
DESCUENTO_MINIMO = 60.0 # total a partir del que se aplica el descuento
DESCUENTO = 0.10 # 10 %
# El total del paso 3, que aquí fijamos para poder ejecutar el bloque suelto.
total = 73.80
if total >= DESCUENTO_MINIMO:
ahorro = total * DESCUENTO
total = total - ahorro
print(f"Descuento 10 %: -{ahorro:.2f} €")
print(f"Total final: {total:.2f} €")
Salida esperada:
El {ahorro:.2f} de la f-string significa «muestra el número con dos decimales».
Es un formato, no un redondeo del valor: si ahorro fuera 7.3456, imprimiría
-7.35 €.
Cambia la línea total = 73.80 por total = 45.00 y vuelve a ejecutar: la
condición no se cumple, no se imprime la línea del descuento y el total final
queda intacto. Esa es la comprobación de que el if está haciendo su trabajo.
6. El programa completo¶
Ahora reunimos las piezas en un while True. Usaremos if/elif según la
opción elegida, break para salir y continue para volver al menú después de
mostrar un aviso. Guarda el programa como 05b_caja_registradora.py y ejecútalo
en la terminal con uv run python 05b_caja_registradora.py:
# 05b_caja_registradora.py — Proyecto corto 1 · La caja registradora de TechShop
catalogo = {
101: {"nombre": "Camiseta TechShop", "precio": 14.50},
102: {"nombre": "Taza de la marca", "precio": 8.90},
103: {"nombre": "Sudadera con capucha", "precio": 32.00},
104: {"nombre": "Pegatinas (pack x5)", "precio": 3.20},
105: {"nombre": "Gorra bordada", "precio": 18.00},
}
carrito = {} # {código: unidades}
DESCUENTO_MINIMO = 60.0 # total a partir del que se aplica el descuento
DESCUENTO = 0.10 # 10 %
while True:
print()
print("=== CAJA REGISTRADORA TECHSHOP ===")
print(" 1) Ver catálogo")
print(" 2) Añadir al carrito")
print(" 3) Ver carrito")
print(" 4) Cobrar y emitir ticket")
print(" 0) Salir")
opcion = input("Elige una opción: ")
if opcion == "1":
print("CATÁLOGO")
for codigo, producto in catalogo.items():
print(f" {codigo} · {producto['nombre']:<24}{producto['precio']:>7.2f} €")
elif opcion == "2":
codigo = int(input("Código del producto: "))
if codigo not in catalogo:
print(" Aviso: ese código no existe en el catálogo.")
continue
unidades = int(input("¿Cuántas unidades? "))
if unidades < 1:
print(" Aviso: las unidades deben ser al menos 1.")
continue
carrito[codigo] = carrito.get(codigo, 0) + unidades
print(f" Añadidas {unidades} × {catalogo[codigo]['nombre']}.")
elif opcion == "3":
if len(carrito) == 0:
print(" El carrito está vacío.")
continue
print("CARRITO")
total_unidades = 0
total = 0.0
for codigo, unidades in carrito.items():
nombre = catalogo[codigo]["nombre"]
subtotal = catalogo[codigo]["precio"] * unidades
total_unidades += unidades
total += subtotal
print(f" {unidades:>2} × {nombre:<24}{subtotal:>7.2f} €")
print(f" {'Total':<28}{total:>7.2f} € ({total_unidades} unidades)")
elif opcion == "4":
if len(carrito) == 0:
print(" Aviso: no puedes cobrar con el carrito vacío.")
continue
print("=" * 38)
print(f"{'TICKET TECHSHOP':^38}")
print("=" * 38)
total = 0.0
for codigo, unidades in carrito.items():
nombre = catalogo[codigo]["nombre"]
subtotal = catalogo[codigo]["precio"] * unidades
total += subtotal
print(f"{unidades:>2} × {nombre:<24}{subtotal:>7.2f} €")
print("-" * 38)
print(f"{'Base':<29}{total:>7.2f} €")
if total >= DESCUENTO_MINIMO:
ahorro = total * DESCUENTO
total = total - ahorro
print(f"{'Descuento 10 %':<29}{-ahorro:>7.2f} €")
print(f"{'TOTAL A PAGAR':<29}{total:>7.2f} €")
print("=" * 38)
print("¡Gracias por tu compra!")
carrito = {}
elif opcion == "0":
print("Hasta pronto.")
break
else:
print(" Aviso: opción no válida.")
Esta es una sesión completa de ejemplo. En la transcripción, el texto que
aparece después de : representa lo que escribe el usuario:
=== CAJA REGISTRADORA TECHSHOP ===
1) Ver catálogo
2) Añadir al carrito
3) Ver carrito
4) Cobrar y emitir ticket
0) Salir
Elige una opción: 1
CATÁLOGO
101 · Camiseta TechShop 14.50 €
102 · Taza de la marca 8.90 €
103 · Sudadera con capucha 32.00 €
104 · Pegatinas (pack x5) 3.20 €
105 · Gorra bordada 18.00 €
=== CAJA REGISTRADORA TECHSHOP ===
1) Ver catálogo
2) Añadir al carrito
3) Ver carrito
4) Cobrar y emitir ticket
0) Salir
Elige una opción: 2
Código del producto: 101
¿Cuántas unidades? 2
Añadidas 2 × Camiseta TechShop.
=== CAJA REGISTRADORA TECHSHOP ===
1) Ver catálogo
2) Añadir al carrito
3) Ver carrito
4) Cobrar y emitir ticket
0) Salir
Elige una opción: 2
Código del producto: 103
¿Cuántas unidades? 1
Añadidas 1 × Sudadera con capucha.
=== CAJA REGISTRADORA TECHSHOP ===
1) Ver catálogo
2) Añadir al carrito
3) Ver carrito
4) Cobrar y emitir ticket
0) Salir
Elige una opción: 2
Código del producto: 104
¿Cuántas unidades? 4
Añadidas 4 × Pegatinas (pack x5).
=== CAJA REGISTRADORA TECHSHOP ===
1) Ver catálogo
2) Añadir al carrito
3) Ver carrito
4) Cobrar y emitir ticket
0) Salir
Elige una opción: 3
CARRITO
2 × Camiseta TechShop 29.00 €
1 × Sudadera con capucha 32.00 €
4 × Pegatinas (pack x5) 12.80 €
Total 73.80 € (7 unidades)
=== CAJA REGISTRADORA TECHSHOP ===
1) Ver catálogo
2) Añadir al carrito
3) Ver carrito
4) Cobrar y emitir ticket
0) Salir
Elige una opción: 4
======================================
TICKET TECHSHOP
======================================
2 × Camiseta TechShop 29.00 €
1 × Sudadera con capucha 32.00 €
4 × Pegatinas (pack x5) 12.80 €
--------------------------------------
Base 73.80 €
Descuento 10 % -7.38 €
TOTAL A PAGAR 66.42 €
======================================
¡Gracias por tu compra!
=== CAJA REGISTRADORA TECHSHOP ===
1) Ver catálogo
2) Añadir al carrito
3) Ver carrito
4) Cobrar y emitir ticket
0) Salir
Elige una opción: 0
Hasta pronto.
Práctica de transferencia¶
Prueba estos casos: un código inexistente (999), cero unidades, intentar
cobrar con el carrito vacío y una opción que no existe (7). Para cada caso,
anota la entrada, el aviso que aparece y el estado del carrito después de la
operación. El programa debe volver al menú y no aceptar el dato incorrecto.
7. Qué nos falta para reutilizar el código¶
Si revisas el programa, encontrarás dos repeticiones importantes:
- El bucle que recorre el carrito y suma el total aparece dos veces (en las opciones 3 y 4). Si mañana añades el IVA, tendrás que modificar ambos bloques. Si solo actualizas uno, el carrito y el ticket mostrarán resultados distintos.
- El patrón «pedir el código, validarlo, pedir las unidades y validarlas» también podría aparecer en cualquier otro menú.
En 1.8 aprenderás a resolverlo con funciones: definir el cálculo del total una sola vez y llamarlo desde donde haga falta. El capítulo siguiente parte de este proyecto para mostrar esa transformación. Conserva tu solución: la retomaremos allí.
Errores frecuentes¶
| Error | Qué pasa | Cómo evitarlo |
|---|---|---|
Comparar la opción con un entero (opcion == 1) |
Nunca se cumple: input() devuelve texto |
Comparar con "1" o convertir con int() |
int() sobre una entrada vacía o con letras |
ValueError y el programa se cae |
En este proyecto se asumen entradas numéricas; en 1.11 aprenderás a controlar este error con try/except |
Asignar carrito[codigo] = unidades |
Pisa las unidades que ya había | Acumular con carrito.get(codigo, 0) + unidades |
Olvidar el continue tras un aviso |
El programa sigue ejecutando la opción con datos inválidos | Saltar a la siguiente iteración en cuanto se detecte el problema |
| Cobrar sin comprobar el carrito | Se emite un ticket de 0,00 € que no corresponde a ninguna compra | Comprobar len(carrito) == 0 antes de emitirlo |
Usar los literales 60 y 0.10 dentro del if |
Cambiar la política de descuento obliga a buscar esos valores por el código | Definir constantes con nombre al principio del programa |
| Elegir los anchos de formato sin probarlos | Las columnas se desplazan con nombres largos | Probar con el producto de nombre más largo y ajustar los anchos |
Ampliaciones¶
Si quieres seguir practicando, puedes ampliar el programa (todavía sin funciones):
- Descuento por volumen: aplica un 5 % a las líneas con 3 o más unidades del mismo producto. Modifica el bucle del ticket, no el del carrito.
- IVA: añade una línea
IVA (21 %)entre la base y el total. Decide si el descuento se aplica antes o después del IVA y documenta la decisión. - Estadística del día: registra cuántos tickets se han emitido y cuál es su
importe medio. Necesitarás acumuladores que sobrevivan al
carrito = {}de cada cobro. - Para pensar: ¿qué ocurre si el usuario escribe letras en el código?
Anótalo; aprenderás a resolverlo en 1.11 con
try/except.
Producto evaluable¶
Entrega el script 05b_caja_registradora.py y asegúrate de que cumple estos
requisitos. Es un script y no un cuaderno porque el menú lee del teclado con
input(), y eso se hace en la terminal.
- El menú se repite hasta que se elige la opción de salir.
- El carrito acumula unidades al repetir el mismo producto.
- Cobra solo con carrito no vacío y lo vacía tras emitir el ticket.
- El descuento se aplica solo cuando el total lo alcanza.
- Con las mismas entradas, el ticket es siempre idéntico (salida estable).
- Incluye una tabla de «requisito → herramienta usada» (condicional, bucle,
diccionario, f-string) y una conclusión sobre qué parte del código convendría
no repetir, escritas como comentario al final del script o en un
README.mdjunto a él.
Criterio de aceptación: el programa responde a entradas inválidas con un aviso y sin caerse cuando la entrada tiene el tipo esperado pero no pertenece al dominio (código inexistente, unidades menores que 1 u opción no disponible), y la sesión completa reproduce la salida esperada. Las entradas no numéricas quedan fuera de esta primera versión porque todavía no se han introducido las excepciones; se incorporarán al robustecer el programa en 1.11.
Resumen¶
| Requisito del proyecto | Herramienta que lo resuelve |
|---|---|
| Menú que se repite | while True + if/elif + break |
| Catálogo y búsquedas | Diccionario de diccionarios, in, .items() |
| Carrito acumulativo | .get(clave, 0) + unidades |
| Totales | Acumuladores (total += subtotal) |
| Descuento | Condicional sobre el total, constantes con nombre |
| Columnas alineadas | f-strings con ancho fijo (:>7.2f, :<24) |
| Validaciones con aviso | continue tras comprobar los datos |
Idea clave. Ya puedes construir un programa completo combinando las estructuras básicas. Las funciones, los ficheros y las excepciones no cambian las operaciones fundamentales; ayudan a organizarlas, reutilizarlas y controlar los errores.