From 953b00b244d267e4359df7b9b0b739c57269b857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Ram=C3=ADrez?= Date: Mon, 15 Sep 2025 18:04:15 +0200 Subject: [PATCH 1/2] Solved lab --- lab-python-error-handling.ipynb | 237 +++++++++++++++++++++++++++++++- 1 file changed, 233 insertions(+), 4 deletions(-) diff --git a/lab-python-error-handling.ipynb b/lab-python-error-handling.ipynb index 3e50ef8..a77780f 100644 --- a/lab-python-error-handling.ipynb +++ b/lab-python-error-handling.ipynb @@ -44,15 +44,244 @@ "execution_count": null, "id": "cc2c441d-9dcf-4817-b097-cf6cbe440846", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Inicialización del inventario ===\n", + "\n", + "=== Pedidos del cliente ===\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n" + ] + } + ], "source": [ - "# your code goes here" + "# ================= Excepciones personalizadas =================\n", + "class InvalidQuantityError(ValueError):\n", + " pass\n", + "\n", + "class InvalidPriceError(ValueError):\n", + " pass\n", + "\n", + "class InvalidProductError(ValueError):\n", + " pass\n", + "\n", + "class OutOfStockError(RuntimeError):\n", + " pass\n", + "\n", + "\n", + "# ================= Utilidades de entrada segura =================\n", + "def pedir_entero_no_negativo(prompt: str) -> int:\n", + " \"\"\"Devuelve un entero >= 0. Aplica try/except/else/finally con mensajes claros.\"\"\"\n", + " while True:\n", + " try:\n", + " texto = input(prompt)\n", + " valor = int(texto)\n", + " if valor < 0:\n", + " raise InvalidQuantityError(\"El valor no puede ser negativo.\")\n", + " except ValueError:\n", + " print(\"Error: introduce un número entero válido (ej. 0, 1, 2...).\")\n", + " except InvalidQuantityError as e:\n", + " print(f\"Error: {e}\")\n", + " else:\n", + " # Solo si no hubo excepción\n", + " return valor\n", + " finally:\n", + " # Se ejecuta siempre (útil para trazas/separadores/limpiezas)\n", + " print(\"— intento procesado —\")\n", + "\n", + "\n", + "def pedir_entero_positivo(prompt: str) -> int:\n", + " while True:\n", + " try:\n", + " texto = input(prompt)\n", + " valor = int(texto)\n", + " if valor <= 0:\n", + " raise InvalidQuantityError(\"Debe ser un entero positivo.\")\n", + " except (ValueError, InvalidQuantityError) as e:\n", + " print(f\"Error: {e}\")\n", + " else:\n", + " return valor\n", + " finally:\n", + " print(\"— intento procesado —\")\n", + "\n", + "\n", + "def pedir_float_positivo(prompt: str) -> float:\n", + " while True:\n", + " try:\n", + " texto = input(prompt).replace(\",\", \".\")\n", + " valor = float(texto)\n", + " if valor <= 0:\n", + " raise InvalidPriceError(\"El precio debe ser mayor que 0.\")\n", + " except ValueError:\n", + " print(\"Error: introduce un número (puedes usar coma o punto).\")\n", + " except InvalidPriceError as e:\n", + " print(f\"Error: {e}\")\n", + " else:\n", + " return valor\n", + " finally:\n", + " print(\"— intento procesado —\")\n", + "\n", + "\n", + "# ================= Lógica del dominio =================\n", + "def initialize_inventory(products):\n", + " \"\"\"Inicializa el inventario pidiendo cantidades con manejo de errores.\"\"\"\n", + " inventory = {}\n", + " print(\"=== Inicialización del inventario ===\")\n", + " for product in products:\n", + " qty = pedir_entero_no_negativo(f\"Cantidad disponible de {product}: \")\n", + " inventory[product] = qty\n", + " return inventory\n", + "\n", + "\n", + "def get_customer_orders(inventory, products):\n", + " \"\"\"\n", + " Solicita N pedidos. Valida que:\n", + " - N sea entero positivo.\n", + " - El producto exista.\n", + " - Haya stock disponible.\n", + " Demostración de raise InvalidProductError y OutOfStockError.\n", + " \"\"\"\n", + " print(\"\\n=== Pedidos del cliente ===\")\n", + " num_orders = pedir_entero_positivo(\"Número de unidades a pedir: \")\n", + "\n", + " pedidos = []\n", + " productos_validos = [p.lower() for p in products]\n", + "\n", + " for i in range(num_orders):\n", + " while True:\n", + " try:\n", + " prod = input(f\"Producto #{i+1}: \").strip().lower()\n", + " if prod not in productos_validos:\n", + " raise InvalidProductError(\n", + " f\"Producto inválido. Elige entre: {', '.join(products)}.\"\n", + " )\n", + " if inventory.get(prod, 0) <= 0:\n", + " raise OutOfStockError(\n", + " f\"No hay stock de '{prod}'. Stock actual: {inventory.get(prod, 0)}.\"\n", + " )\n", + " except (InvalidProductError, OutOfStockError) as e:\n", + " print(f\"Error: {e}\")\n", + " else:\n", + " pedidos.append(prod)\n", + " break\n", + " finally:\n", + " # útil para trazar cada intento\n", + " print(\"— intento de selección procesado —\")\n", + " return pedidos\n", + "\n", + "\n", + "def total_price_customer_orders(customer_orders):\n", + " \"\"\"\n", + " Pide el precio unitario de cada producto distinto y calcula el total.\n", + " Usa else para sumar solo cuando no hay error.\n", + " \"\"\"\n", + " print(\"\\n=== Precios ===\")\n", + " from collections import Counter\n", + " cuenta = Counter(customer_orders)\n", + " total = 0.0\n", + "\n", + " for producto, unidades in cuenta.items():\n", + " while True:\n", + " try:\n", + " precio = pedir_float_positivo(f\"Precio unitario de {producto}: \")\n", + " except Exception as e:\n", + " # En principio pedir_float_positivo ya maneja todo,\n", + " # pero dejamos el catch por si se modifica a futuro.\n", + " print(f\"Error inesperado pidiendo precio: {e}\")\n", + " else:\n", + " total += precio * unidades\n", + " break\n", + " finally:\n", + " print(\"— intento de precio procesado —\")\n", + " return total\n", + "\n", + "\n", + "def update_inventory(customer_orders, inventory):\n", + " \"\"\"Descuenta 1 unidad por cada ocurrencia del producto en customer_orders.\"\"\"\n", + " from collections import Counter\n", + " cuenta = Counter(customer_orders)\n", + "\n", + " for producto, unidades in cuenta.items():\n", + " stock = inventory.get(producto, 0)\n", + " # Si por lógica del programa llegamos aquí sin stock suficiente,\n", + " # sería un error de consistencia → raise\n", + " if unidades > stock:\n", + " raise OutOfStockError(\n", + " f\"Inconsistencia: intentas descontar {unidades} de '{producto}' pero solo hay {stock}.\"\n", + " )\n", + " inventory[producto] = stock - unidades\n", + " return inventory\n", + "\n", + "\n", + "def calculate_order_statistics(customer_orders, initial_total_units):\n", + " unidades_pedidas = len(customer_orders)\n", + " porcentaje = (unidades_pedidas / initial_total_units * 100) if initial_total_units > 0 else 0.0\n", + " return unidades_pedidas, porcentaje\n", + "\n", + "\n", + "def print_order_statistics(stats):\n", + " print(f\"\"\"\n", + "=== Estadísticas del pedido ===\n", + "Unidades pedidas: {stats[0]}\n", + "% sobre el inventario inicial: {stats[1]:.2f}%\n", + "\"\"\")\n", + "\n", + "\n", + "def print_updated_inventory(inventory):\n", + " print(\"=== Inventario actualizado ===\")\n", + " for prod, qty in inventory.items():\n", + " print(f\"{prod}: {qty}\")\n", + "\n", + "\n", + "# ================= Programa principal con manejo global =================\n", + "if __name__ == \"__main__\":\n", + " try:\n", + " products = [\"t-shirt\", \"mug\", \"hat\", \"book\", \"keychain\"]\n", + " inventory = initialize_inventory(products)\n", + " initial_total_units = sum(inventory.values())\n", + "\n", + " orders = get_customer_orders(inventory, products)\n", + "\n", + " stats = calculate_order_statistics(orders, initial_total_units)\n", + " print_order_statistics(stats)\n", + "\n", + " inventory = update_inventory(orders, inventory)\n", + " print_updated_inventory(inventory)\n", + "\n", + " total = total_price_customer_orders(orders)\n", + " print(f\"\\nTotal a pagar: {total:.2f}\")\n", + "\n", + " except KeyboardInterrupt:\n", + " print(\"\\nOperación cancelada por el usuario (Ctrl+C).\")\n", + " except Exception as e:\n", + " # Captura final: evita que el programa “reviente” sin mensaje\n", + " print(f\"\\nSe produjo un error inesperado: {type(e).__name__}: {e}\")\n", + " print(\"Por favor, revisa los datos de entrada e inténtalo de nuevo.\")\n", + " finally:\n", + " # Siempre se ejecuta, haya o no errores\n", + " print(\"\\n— Ejecución finalizada —\")\n" ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv", "language": "python", "name": "python3" }, @@ -66,7 +295,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.12.2" } }, "nbformat": 4, From 0cacbb062dab4834b5ce840c9f8a6545954815bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Ram=C3=ADrez?= Date: Mon, 15 Sep 2025 18:11:28 +0200 Subject: [PATCH 2/2] Fixing lab --- lab-python-error-handling.ipynb | 391 +++++++++++++++++--------------- 1 file changed, 206 insertions(+), 185 deletions(-) diff --git a/lab-python-error-handling.ipynb b/lab-python-error-handling.ipynb index a77780f..656875f 100644 --- a/lab-python-error-handling.ipynb +++ b/lab-python-error-handling.ipynb @@ -71,211 +71,232 @@ } ], "source": [ - "# ================= Excepciones personalizadas =================\n", - "class InvalidQuantityError(ValueError):\n", - " pass\n", + "import re\n", + "import string\n", + "from typing import Iterable, List, Dict, Tuple, Callable\n", "\n", - "class InvalidPriceError(ValueError):\n", - " pass\n", + "# ==========================\n", + "# Utilidades y validaciones\n", + "# ==========================\n", "\n", - "class InvalidProductError(ValueError):\n", + "class InputTypeError(TypeError):\n", " pass\n", "\n", - "class OutOfStockError(RuntimeError):\n", + "class MathDomainError(ValueError):\n", " pass\n", "\n", - "\n", - "# ================= Utilidades de entrada segura =================\n", - "def pedir_entero_no_negativo(prompt: str) -> int:\n", - " \"\"\"Devuelve un entero >= 0. Aplica try/except/else/finally con mensajes claros.\"\"\"\n", - " while True:\n", - " try:\n", - " texto = input(prompt)\n", - " valor = int(texto)\n", - " if valor < 0:\n", - " raise InvalidQuantityError(\"El valor no puede ser negativo.\")\n", - " except ValueError:\n", - " print(\"Error: introduce un número entero válido (ej. 0, 1, 2...).\")\n", - " except InvalidQuantityError as e:\n", - " print(f\"Error: {e}\")\n", - " else:\n", - " # Solo si no hubo excepción\n", - " return valor\n", - " finally:\n", - " # Se ejecuta siempre (útil para trazas/separadores/limpiezas)\n", - " print(\"— intento procesado —\")\n", - "\n", - "\n", - "def pedir_entero_positivo(prompt: str) -> int:\n", - " while True:\n", - " try:\n", - " texto = input(prompt)\n", - " valor = int(texto)\n", - " if valor <= 0:\n", - " raise InvalidQuantityError(\"Debe ser un entero positivo.\")\n", - " except (ValueError, InvalidQuantityError) as e:\n", - " print(f\"Error: {e}\")\n", - " else:\n", - " return valor\n", - " finally:\n", - " print(\"— intento procesado —\")\n", - "\n", - "\n", - "def pedir_float_positivo(prompt: str) -> float:\n", - " while True:\n", - " try:\n", - " texto = input(prompt).replace(\",\", \".\")\n", - " valor = float(texto)\n", - " if valor <= 0:\n", - " raise InvalidPriceError(\"El precio debe ser mayor que 0.\")\n", - " except ValueError:\n", - " print(\"Error: introduce un número (puedes usar coma o punto).\")\n", - " except InvalidPriceError as e:\n", - " print(f\"Error: {e}\")\n", - " else:\n", - " return valor\n", - " finally:\n", - " print(\"— intento procesado —\")\n", - "\n", - "\n", - "# ================= Lógica del dominio =================\n", - "def initialize_inventory(products):\n", - " \"\"\"Inicializa el inventario pidiendo cantidades con manejo de errores.\"\"\"\n", - " inventory = {}\n", - " print(\"=== Inicialización del inventario ===\")\n", - " for product in products:\n", - " qty = pedir_entero_no_negativo(f\"Cantidad disponible de {product}: \")\n", - " inventory[product] = qty\n", - " return inventory\n", - "\n", - "\n", - "def get_customer_orders(inventory, products):\n", + "def assert_iterable_not_string(value, name=\"value\"):\n", + " if isinstance(value, (str, bytes)):\n", + " raise InputTypeError(f\"'{name}' no puede ser str/bytes; se esperaba una colección de elementos.\")\n", + " try:\n", + " iter(value)\n", + " except TypeError:\n", + " raise InputTypeError(f\"'{name}' debe ser iterable (lista, tupla, set, etc.).\")\n", + "\n", + "# =========================================\n", + "# 1) get_unique_list: únicos preservando orden\n", + "# =========================================\n", + "def get_unique_list(items: Iterable) -> List:\n", " \"\"\"\n", - " Solicita N pedidos. Valida que:\n", - " - N sea entero positivo.\n", - " - El producto exista.\n", - " - Haya stock disponible.\n", - " Demostración de raise InvalidProductError y OutOfStockError.\n", + " Devuelve los elementos únicos preservando el orden de aparición.\n", + " Maneja entradas no iterables / strings con raise y mensajes claros.\n", " \"\"\"\n", - " print(\"\\n=== Pedidos del cliente ===\")\n", - " num_orders = pedir_entero_positivo(\"Número de unidades a pedir: \")\n", - "\n", - " pedidos = []\n", - " productos_validos = [p.lower() for p in products]\n", - "\n", - " for i in range(num_orders):\n", - " while True:\n", - " try:\n", - " prod = input(f\"Producto #{i+1}: \").strip().lower()\n", - " if prod not in productos_validos:\n", - " raise InvalidProductError(\n", - " f\"Producto inválido. Elige entre: {', '.join(products)}.\"\n", - " )\n", - " if inventory.get(prod, 0) <= 0:\n", - " raise OutOfStockError(\n", - " f\"No hay stock de '{prod}'. Stock actual: {inventory.get(prod, 0)}.\"\n", - " )\n", - " except (InvalidProductError, OutOfStockError) as e:\n", - " print(f\"Error: {e}\")\n", - " else:\n", - " pedidos.append(prod)\n", - " break\n", - " finally:\n", - " # útil para trazar cada intento\n", - " print(\"— intento de selección procesado —\")\n", - " return pedidos\n", - "\n", + " try:\n", + " assert_iterable_not_string(items, \"items\")\n", + " vistos = set()\n", + " resultado = []\n", + " for x in items:\n", + " if x not in vistos:\n", + " resultado.append(x)\n", + " vistos.add(x)\n", + " except InputTypeError as e:\n", + " print(f\"Error: {e}\")\n", + " raise\n", + " else:\n", + " return resultado\n", + " finally:\n", + " # Punto de trazabilidad (opcional)\n", + " pass\n", "\n", - "def total_price_customer_orders(customer_orders):\n", + "# =========================================\n", + "# 2) count_case: conteo de mayúsculas/minúsculas\n", + "# =========================================\n", + "def count_case(texto: str) -> Dict[str, int]:\n", " \"\"\"\n", - " Pide el precio unitario de cada producto distinto y calcula el total.\n", - " Usa else para sumar solo cuando no hay error.\n", + " Cuenta letras mayúsculas y minúsculas. Ignora no letras.\n", " \"\"\"\n", - " print(\"\\n=== Precios ===\")\n", - " from collections import Counter\n", - " cuenta = Counter(customer_orders)\n", - " total = 0.0\n", - "\n", - " for producto, unidades in cuenta.items():\n", - " while True:\n", - " try:\n", - " precio = pedir_float_positivo(f\"Precio unitario de {producto}: \")\n", - " except Exception as e:\n", - " # En principio pedir_float_positivo ya maneja todo,\n", - " # pero dejamos el catch por si se modifica a futuro.\n", - " print(f\"Error inesperado pidiendo precio: {e}\")\n", - " else:\n", - " total += precio * unidades\n", - " break\n", - " finally:\n", - " print(\"— intento de precio procesado —\")\n", - " return total\n", - "\n", - "\n", - "def update_inventory(customer_orders, inventory):\n", - " \"\"\"Descuenta 1 unidad por cada ocurrencia del producto en customer_orders.\"\"\"\n", - " from collections import Counter\n", - " cuenta = Counter(customer_orders)\n", - "\n", - " for producto, unidades in cuenta.items():\n", - " stock = inventory.get(producto, 0)\n", - " # Si por lógica del programa llegamos aquí sin stock suficiente,\n", - " # sería un error de consistencia → raise\n", - " if unidades > stock:\n", - " raise OutOfStockError(\n", - " f\"Inconsistencia: intentas descontar {unidades} de '{producto}' pero solo hay {stock}.\"\n", - " )\n", - " inventory[producto] = stock - unidades\n", - " return inventory\n", - "\n", - "\n", - "def calculate_order_statistics(customer_orders, initial_total_units):\n", - " unidades_pedidas = len(customer_orders)\n", - " porcentaje = (unidades_pedidas / initial_total_units * 100) if initial_total_units > 0 else 0.0\n", - " return unidades_pedidas, porcentaje\n", - "\n", - "\n", - "def print_order_statistics(stats):\n", - " print(f\"\"\"\n", - "=== Estadísticas del pedido ===\n", - "Unidades pedidas: {stats[0]}\n", - "% sobre el inventario inicial: {stats[1]:.2f}%\n", - "\"\"\")\n", - "\n", + " if not isinstance(texto, str):\n", + " raise InputTypeError(\"count_case espera un str como entrada.\")\n", + " try:\n", + " upper = sum(1 for c in texto if c.isalpha() and c.isupper())\n", + " lower = sum(1 for c in texto if c.isalpha() and c.islower())\n", + " except Exception as e:\n", + " print(f\"Error inesperado en count_case: {e}\")\n", + " raise\n", + " else:\n", + " return {\"upper\": upper, \"lower\": lower}\n", + " finally:\n", + " pass\n", "\n", - "def print_updated_inventory(inventory):\n", - " print(\"=== Inventario actualizado ===\")\n", - " for prod, qty in inventory.items():\n", - " print(f\"{prod}: {qty}\")\n", + "# =========================================\n", + "# 3) remove_punctuation: elimina puntuación\n", + "# =========================================\n", + "_PUNCT_TABLE = str.maketrans(\"\", \"\", string.punctuation)\n", "\n", + "def remove_punctuation(texto: str, keep: str = \"\") -> str:\n", + " \"\"\"\n", + " Elimina puntuación estándar. Si 'keep' incluye caracteres, estos se conservan.\n", + " \"\"\"\n", + " if not isinstance(texto, str):\n", + " raise InputTypeError(\"remove_punctuation espera un str.\")\n", + " try:\n", + " if keep:\n", + " # Construir una clase de regex que quite toda puntuación excepto los 'keep'\n", + " keep_escaped = re.escape(keep)\n", + " pattern = rf\"[{re.escape(string.punctuation).replace(keep_escaped, '')}]\"\n", + " return re.sub(pattern, \"\", texto)\n", + " # vía tabla rápida\n", + " return texto.translate(_PUNCT_TABLE)\n", + " except Exception as e:\n", + " print(f\"Error en remove_punctuation: {e}\")\n", + " raise\n", + " finally:\n", + " pass\n", "\n", - "# ================= Programa principal con manejo global =================\n", - "if __name__ == \"__main__\":\n", + "# =========================================\n", + "# 4) word_count: cuenta palabras (normalizado)\n", + "# =========================================\n", + "def word_count(texto: str, lowercase: bool = True, remove_punct: bool = True) -> Dict[str, int]:\n", + " \"\"\"\n", + " Cuenta frecuencia de palabras. Normaliza espacios; opcionalmente quita puntuación y pasa a minúsculas.\n", + " \"\"\"\n", + " if not isinstance(texto, str):\n", + " raise InputTypeError(\"word_count espera un str.\")\n", + " try:\n", + " if remove_punct:\n", + " texto = remove_punctuation(texto)\n", + " if lowercase:\n", + " texto = texto.lower()\n", + " # separa por cualquier espacio\n", + " palabras = re.findall(r\"\\S+\", texto)\n", + " freqs: Dict[str, int] = {}\n", + " for w in palabras:\n", + " freqs[w] = freqs.get(w, 0) + 1\n", + " except Exception as e:\n", + " print(f\"Error en word_count: {e}\")\n", + " raise\n", + " else:\n", + " return freqs\n", + " finally:\n", + " pass\n", + "\n", + "# =========================================\n", + "# 5) Calculadora con operaciones múltiples y recursividad\n", + "# =========================================\n", + "\n", + "def factorial(n: int) -> int:\n", + " \"\"\"Recursivo: factorial con validación.\"\"\"\n", + " if n < 0:\n", + " raise MathDomainError(\"factorial no definido para números negativos.\")\n", + " if n in (0, 1):\n", + " return 1\n", + " return n * factorial(n - 1)\n", + "\n", + "def potencia(base: float, exp: int) -> float:\n", + " \"\"\"Recursivo (exponente entero).\"\"\"\n", + " if exp == 0:\n", + " return 1.0\n", + " if exp < 0:\n", + " # a^(−n) = 1/a^n\n", + " return 1.0 / potencia(base, -exp)\n", + " # exp > 0\n", + " return base * potencia(base, exp - 1)\n", + "\n", + "def mcd(a: int, b: int) -> int:\n", + " \"\"\"Recursivo: máximo común divisor (Euclides).\"\"\"\n", + " a, b = abs(a), abs(b)\n", + " if b == 0:\n", + " return a\n", + " return mcd(b, a % b)\n", + "\n", + "Operation = Callable[..., float]\n", + "\n", + "def safe_div(a: float, b: float) -> float:\n", + " if b == 0:\n", + " raise MathDomainError(\"División por cero.\")\n", + " return a / b\n", + "\n", + "OPERACIONES: Dict[str, Callable] = {\n", + " \"sum\": lambda a, b: a + b,\n", + " \"sub\": lambda a, b: a - b,\n", + " \"mul\": lambda a, b: a * b,\n", + " \"div\": safe_div,\n", + " \"pow\": potencia, # exp entero\n", + " \"fact\": factorial, # unario\n", + " \"gcd\": mcd, # entero\n", + "}\n", + "\n", + "def calcular(op: str, *args) -> float:\n", + " \"\"\"\n", + " Enrutador de operaciones con manejo de errores y else/finally.\n", + " Uso:\n", + " calcular('sum', 2, 3) -> 5\n", + " calcular('div', 10, 2) -> 5\n", + " calcular('pow', 2, 8) -> 256\n", + " calcular('fact', 5) -> 120\n", + " calcular('gcd', 48, 18) -> 6\n", + " \"\"\"\n", " try:\n", - " products = [\"t-shirt\", \"mug\", \"hat\", \"book\", \"keychain\"]\n", - " inventory = initialize_inventory(products)\n", - " initial_total_units = sum(inventory.values())\n", + " if op not in OPERACIONES:\n", + " raise KeyError(f\"Operación desconocida '{op}'. Disponibles: {', '.join(OPERACIONES)}\")\n", + " fn = OPERACIONES[op]\n", + "\n", + " # Validaciones según aridad conocida\n", + " if fn is factorial:\n", + " if len(args) != 1 or not isinstance(args[0], int):\n", + " raise InputTypeError(\"factorial espera 1 entero.\")\n", + " elif fn is mcd:\n", + " if len(args) != 2 or not all(isinstance(x, int) for x in args):\n", + " raise InputTypeError(\"gcd espera 2 enteros.\")\n", + " elif fn is potencia:\n", + " if len(args) != 2 or not isinstance(args[1], int):\n", + " raise InputTypeError(\"pow espera (base: float|int, exp: int).\")\n", + " else:\n", + " if len(args) != 2:\n", + " raise InputTypeError(f\"'{op}' espera 2 argumentos numéricos.\")\n", + "\n", + " res = fn(*args)\n", + " except (InputTypeError, MathDomainError, KeyError, TypeError, ValueError) as e:\n", + " print(f\"Error en calcular: {e}\")\n", + " raise\n", + " else:\n", + " return float(res) if isinstance(res, (int, float)) else res\n", + " finally:\n", + " # lugar para logs/telemetría/cierre de recursos si hiciera falta\n", + " pass\n", "\n", - " orders = get_customer_orders(inventory, products)\n", + "# ==========================\n", + "# Mini tests / demostración\n", + "# ==========================\n", + "if __name__ == \"__main__\":\n", + " # 1) get_unique_list\n", + " print(get_unique_list([1, 2, 2, 3, 1, 4])) # [1, 2, 3, 4]\n", "\n", - " stats = calculate_order_statistics(orders, initial_total_units)\n", - " print_order_statistics(stats)\n", + " # 2) count_case\n", + " print(count_case(\"Hola MUndo!!\")) # {'upper': 3, 'lower': 7}\n", "\n", - " inventory = update_inventory(orders, inventory)\n", - " print_updated_inventory(inventory)\n", + " # 3) remove_punctuation\n", + " print(remove_punctuation(\"Hola, mundo! ¿Todo bien?\")) # \"Hola mundo ¿Todo bien\" (sin ! ,)\n", "\n", - " total = total_price_customer_orders(orders)\n", - " print(f\"\\nTotal a pagar: {total:.2f}\")\n", + " # 4) word_count\n", + " print(word_count(\"Hola hola, mundo Mundo!\")) # {'hola': 2, 'mundo': 2}\n", "\n", - " except KeyboardInterrupt:\n", - " print(\"\\nOperación cancelada por el usuario (Ctrl+C).\")\n", - " except Exception as e:\n", - " # Captura final: evita que el programa “reviente” sin mensaje\n", - " print(f\"\\nSe produjo un error inesperado: {type(e).__name__}: {e}\")\n", - " print(\"Por favor, revisa los datos de entrada e inténtalo de nuevo.\")\n", - " finally:\n", - " # Siempre se ejecuta, haya o no errores\n", - " print(\"\\n— Ejecución finalizada —\")\n" + " # 5) Calculadora\n", + " print(calcular(\"sum\", 2, 5)) # 7.0\n", + " print(calcular(\"div\", 10, 2)) # 5.0\n", + " print(calcular(\"pow\", 2, 8)) # 256.0\n", + " print(calcular(\"fact\", 5)) # 120.0\n", + " print(calcular(\"gcd\", 48, 18)) # 6.0\n" ] } ],