El Día 008 del reto 100 Días de Python crea un codificador y decodificador de Cifrado César. Cada letra se desplaza por el alfabeto una cantidad elegida por el usuario, convirtiendo un mensaje legible en texto revuelto, y de vuelta otra vez.

Este proyecto es una introducción compacta a dos ideas que aparecen en todas partes de la programación: las funciones reutilizables que reciben parámetros y la aritmética modular para devolver un valor a un rango válido. También es una excelente primera mirada a cómo funcionan por dentro los algoritmos básicos de cifrado.

Esta guía recorre el código fuente completo del Día 008 y explica exactamente cómo funciona la lógica de desplazamiento y de vuelta al inicio, incluyendo una sutileza con los números negativos que confunde a muchos principiantes que vienen de otros lenguajes.

Descripción del Proyecto

La función principal recibe el texto original, la cantidad de desplazamiento y si el usuario quiere codificar o decodificar. Decodificar reutiliza exactamente la misma lógica de desplazamiento que codificar, simplemente invirtiendo la dirección del desplazamiento (multiplicándolo por -1), en lugar de escribir código separado para cada dirección.

Código Fuente

El proyecto del Día 008 está en day008/main.py, dentro del repositorio 100daysofpython, e importa su arte del título desde un archivo art.py aparte en la misma carpeta:

from art import LOGO

print(LOGO)

alphabet = [
    'a','b','c','d','e','f','g','h','i','j','k','l','m',
    'n','o','p','q','r','s','t','u','v','w','x','y','z'
]

def plain2cipher(original_text, shift_amount, encode_or_decoder):
    output_text = ""
    selected_encode_or_decoder = ""

    if encode_or_decoder == "2":
        selected_encode_or_decoder = "decode"
        shift_amount *= -1
    else:
        selected_encode_or_decoder = "encode"

    for letter in original_text:
        if letter not in alphabet:
            output_text += letter
        else:
            shifted_position = alphabet.index(letter) + shift_amount
            shifted_position %= len(alphabet)
            output_text += alphabet[shifted_position]

    print("****************************************************************")
    print(f"Here is your secret message {selected_encode_or_decoder}d result: {output_text}")
    print("****************************************************************")

def caesar_cipher():
    should_continue = True

    while should_continue:
        while True:
            direction = input("Type '1' to encode, type '2' to decode: ")
            if direction == "1" or direction == "2":
                break
            print("Invalid input. Please type '1' to encode or '2' to decode.")
            print("****************************************************************")

        text = input("Type your message: ").lower()
        shift = int(input("Type the shift number: "))

        plain2cipher(original_text=text, shift_amount=shift, encode_or_decoder=direction)

        restart = input("Type 'y' if you want to go again. Otherwise, type 'n': ").lower()

        if restart == "n":
            should_continue = False
            print("Goodbye")

caesar_cipher()

Cómo Funciona el Programa

Una Función Maneja Ambas Direcciones

if encode_or_decoder == "2":
    selected_encode_or_decoder = "decode"
    shift_amount *= -1
else:
    selected_encode_or_decoder = "encode"

En lugar de escribir funciones separadas para codificar y decodificar, plain2cipher() reutiliza el mismo ciclo de desplazamiento para ambas. Decodificar es en realidad codificar con el desplazamiento invertido: si codificar desplaza cada letra 3 posiciones hacia adelante, decodificar desplaza cada letra 3 posiciones hacia atrás, que es exactamente lo que logra shift_amount *= -1.

Encontrar y Desplazar la Posición de una Letra

shifted_position = alphabet.index(letter) + shift_amount
shifted_position %= len(alphabet)
output_text += alphabet[shifted_position]

alphabet.index(letter) encuentra la posición numérica de una letra ('a' es 0, 'b' es 1, y así sucesivamente). Sumar shift_amount mueve esa posición hacia adelante o hacia atrás, y %= len(alphabet) devuelve el resultado a un índice válido, de modo que desplazar 'z' una posición hacia adelante regresa a 'a' en lugar de producir un índice inválido.

Por Qué el Módulo Funciona Correctamente Incluso con Desplazamientos Negativos

Esta es la parte sutil. Al decodificar, shift_amount es negativo, lo que significa que shifted_position también puede quedar negativo; por ejemplo, alphabet.index('a') + (-3) es -3. En algunos lenguajes, el módulo de un número negativo devuelve un resultado negativo, lo que haría fallar alphabet[shifted_position] con un índice inválido. En Python, % siempre devuelve un resultado con el mismo signo que el divisor, así que -3 % 26 se evalúa correctamente como 23, no como -3. Ese comportamiento es justo la razón por la que este código funciona bien al decodificar sin lógica adicional para manejar el signo: se apoya en una peculiaridad real y útil de cómo está definido el operador módulo de Python.

Conservar los Caracteres que No Son Letras

if letter not in alphabet:
    output_text += letter

Como alphabet solo contiene letras minúsculas, cualquier carácter que no esté en él (espacios, signos de puntuación, números) se agrega a la salida sin cambios. Combinado con text = input("Type your message: ").lower() más arriba, esto significa que el cifrado funciona correctamente con oraciones completas, no solo con palabras sueltas.

Qué Enseña Este Proyecto

El Día 008 introduce:

  • Parámetros de funciones y reutilizar una misma función para dos comportamientos distintos
  • Indexación de listas con .index()
  • Aritmética modular (%) para devolver valores a un rango válido
  • Cómo el operador módulo de Python se comporta de forma distinta a otros lenguajes con números negativos
  • Conservar los caracteres que no pertenecen al alfabeto durante el procesamiento de texto

Un Cifrado César es educativo, no un cifrado seguro, porque su espacio de claves pequeño (solo 26 desplazamientos posibles) puede adivinarse por fuerza bruta casi al instante. El proyecto se entiende mejor como una introducción a los algoritmos y al procesamiento de cadenas, no como criptografía del mundo real.

Ejecutar el Proyecto

git clone https://github.com/victorhugo81/100daysofpython.git
cd 100daysofpython/day008
python main.py

El proyecto también importa su logo del archivo art.py de la carpeta, que debe permanecer en el mismo directorio que main.py.

Posibles Siguientes Pasos

Cuando entiendas el código existente, intenta ampliarlo:

  • Envuelve la entrada del desplazamiento en un bloque try/except: a diferencia de la pregunta de la dirección, int(input(...)) aquí no tiene validación, así que escribir un desplazamiento no numérico hace fallar el programa
  • Admite letras mayúsculas directamente, en lugar de depender de .lower() para forzar todo a minúsculas primero
  • Prueba automáticamente todos los desplazamientos posibles (0–25) e imprime todos los resultados, para demostrar con qué facilidad se puede descifrar un Cifrado César por fuerza bruta
  • Agrega una verificación sencilla con una lista de palabras para señalar automáticamente cuál resultado decodificado parece texto real en inglés

Conclusión

El Día 008 del reto 100 Días de Python convierte una idea matemática simple (desplazar letras y volver al inicio al llegar al final del alfabeto) en un codificador y decodificador funcional usando funciones, indexación de listas y aritmética modular. Entender exactamente por qué el operador % de Python maneja correctamente los desplazamientos negativos es un detalle pequeño, pero es el tipo de comportamiento específico del lenguaje que conviene conocer bien antes de depender de él en proyectos más avanzados.

Consulta el código fuente del Día 008 en GitHub y sigue explorando el índice de proyectos del repositorio mientras construyes más programas en Python, un día y un proyecto pequeño a la vez.