Autor: ciberkaos

  • 1.6 – ¿Qué es un conjunto? (set)

    1.6 – ¿Qué es un conjunto? (set)

    Un set es una colección desordenada, mutable y sin elementos duplicados.

    • No mantiene orden
    • No permite duplicados
    • Es mutable
    • Permite operaciones matemáticas (unión, intersección, diferencia)
    • Muy eficiente en búsquedas

    Crear un conjunto

    mi_conjunto = {1, 2, 3, 4, 5}

    También usando el constructor:

    mi_conjunto = set([1, 2, 3, 4, 5])

    Conjunto vacío

    ⚠ {} crea un diccionario, no un set.

    conjunto_vacio = set()

    Características importantes

    • No tienen índices
    • No permiten slicing
    • No mantienen orden
    • Solo aceptan elementos inmutables (no listas, no diccionarios)
    # Esto da error
    mi_set = {[1,2,3]}

    Métodos principales

    Agregar elementos

    s = {1,2,3}
    s.add(4)

    Eliminar elementos

    remove(x)

    Error si no existe.

    s.remove(2)

    discard(x)

    No error si no existe.

    s.discard(10)

    pop()

    Elimina elemento aleatorio.

    s.pop()

    clear()

    s.clear()

    Operaciones matemáticas de conjuntos

    Sean:

    a = {1,2,3}
    b = {3,4,5}

    Unión (elementos de ambos)

    a | b
    a.union(b)

    Resultado → {1,2,3,4,5}


    Intersección (elementos comunes)

    a & b
    a.intersection(b)

    Resultado → {3}


    Diferencia (elementos de A que no están en B)

    a - b
    a.difference(b)

    Resultado → {1,2}


    Diferencia simétrica (elementos que NO son comunes)

    a ^ b
    a.symmetric_difference(b)

    Resultado → {1,2,4,5}


    Relaciones entre conjuntos

    Subconjunto

    {1,2}.issubset({1,2,3})

    Superconjunto

    {1,2,3}.issuperset({1,2})

    Sin elementos en común

    {1,2}.isdisjoint({3,4})

    Comprobar pertenencia

    3 in {1,2,3}
    10 in {1,2,3}

    Esta operación es muy rápida (O(1)).


    Convertir entre estructuras

    Lista → Set (eliminar duplicados)

    lista = [1,2,2,3,4,4]
    sin_dup = set(lista)

    Set → Lista

    lista = list(sin_dup)

    Copiar conjuntos

    copia = mi_conjunto.copy()

    Recorrer conjuntos

    for elemento in {1,2,3}:
    print(elemento)

    El orden puede cambiar.


    Ejemplo práctico

    conjunto1 = {1,2,3,4}
    conjunto2 = {3,4,5,6}print(conjunto1 | conjunto2)
    print(conjunto1 & conjunto2)
    print(conjunto1 - conjunto2)
    print(conjunto1 ^ conjunto2)conjunto1.add(7)
    conjunto1.discard(2)print(conjunto1)

    Cuándo usar conjuntos

    Usa sets cuando:

    • Necesites eliminar duplicados
    • Quieras operaciones matemáticas
    • Necesites búsquedas rápidas
    • No importe el orden

    Errores comunes

    Intentar indexar un set:

    s = {1,2,3}
    print(s[0]) # Error

    Intentar meter listas o diccionarios dentro.


    Tabla de métodos principales

    MétodoQué haceEjemploResultado
    add(x)Añade elemento{1,2}.add(3){1,2,3}
    remove(x)Elimina (error si no existe){1,2}.remove(2){1}
    discard(x)Elimina sin error{1,2}.discard(5){1,2}
    pop()Elimina elemento aleatorio{1,2}.pop(){2} o {1}
    clear()Vacía el conjuntos.clear()set()
    union()Unióna.union(b){1,2,3,4,5}
    intersection()Interseccióna.intersection(b){3}
    difference()Diferenciaa.difference(b){1,2}
    symmetric_difference()Diferencia simétricaa.symmetric_difference(b){1,2,4,5}
    issubset()Subconjunto{1}.issubset({1,2})True
    issuperset()Superconjunto{1,2}.issuperset({1})True
    isdisjoint()Sin comunes{1}.isdisjoint({2})True
    copy()Copia superficials.copy()nuevo set

    Ejercicios

    1. Elimina duplicados de una lista usando set.
    2. Calcula intersección entre dos listas convertidas a set.
    3. Comprueba si un conjunto es subconjunto de otro.
    4. Crea dos sets y calcula diferencia simétrica.
    5. Cuenta elementos únicos en una lista grande.
  • 1.7 – ¿Qué es un diccionario?

    1.7 – ¿Qué es un diccionario?

    Un diccionario (dict) es una colección de pares clave → valor.

    • Las claves son únicas
    • Son mutables
    • Permiten acceso rápido por clave
    • Desde Python 3.7 mantienen el orden de inserción

    Un diccionario es como una base de datos en miniatura.


    Crear un diccionario

    mi_diccionario = {
    "nombre": "Juan",
    "edad": 25,
    "ciudad": "Madrid"
    }

    También se puede crear así:

    mi_diccionario = dict(nombre="Juan", edad=25)

    Diccionario vacío

    diccionario_vacio = {}

    Características importantes

    • Las claves deben ser inmutables (str, int, tuple…)
    • No pueden existir claves duplicadas
    • El acceso es muy rápido (O(1))

    Esto es inválido:

    mi_dic = {[1,2,3]: "error"}  # Las listas no pueden ser clave

    Acceso a elementos

    Acceso directo

    nombre = mi_diccionario["nombre"]

    Si la clave no existe → error.


    Usar get()

    profesion = mi_diccionario.get("profesion", "No especificada")

    Evita errores si la clave no existe.


    Modificar o agregar elementos

    mi_diccionario["edad"] = 26
    mi_diccionario["profesion"] = "Ingeniero"

    Si la clave existe → modifica.
    Si no existe → crea.


    Eliminar elementos

    pop()

    edad = mi_diccionario.pop("edad")

    del

    del mi_diccionario["ciudad"]

    popitem()

    Elimina el último elemento añadido.

    ultimo = mi_diccionario.popitem()

    clear()

    mi_diccionario.clear()

    Consultar claves, valores y pares

    claves = mi_diccionario.keys()
    valores = mi_diccionario.values()
    items = mi_diccionario.items()

    Devuelven objetos tipo “vista”.


    Verificar si una clave existe

    "nombre" in mi_diccionario

    Copiar diccionarios

    copia = mi_diccionario.copy()

    O:

    copia = dict(mi_diccionario)

    Mezclar diccionarios

    update()

    otro = {"pais": "España", "edad": 30}
    mi_diccionario.update(otro)

    Sobrescribe si la clave ya existe.


    Operador moderno (Python 3.9+)

    nuevo = mi_diccionario | otro

    Longitud

    len(mi_diccionario)

    Iterar sobre diccionarios

    Solo claves

    for clave in mi_diccionario:
    print(clave)

    Solo valores

    for valor in mi_diccionario.values():
    print(valor)

    Clave y valor

    for clave, valor in mi_diccionario.items():
    print(clave, valor)

    Diccionarios anidados

    usuario = {
    "nombre": "Ana",
    "direccion": {
    "ciudad": "Barcelona",
    "codigo_postal": 08001
    }
    }print(usuario["direccion"]["ciudad"])

    Comprensión de diccionarios

    cuadrados = {x: x**2 for x in range(5)}
    print(cuadrados)

    Ejemplo práctico completo

    mi_diccionario = {
    "nombre": "Ana",
    "edad": 28,
    "ciudad": "Barcelona"
    }print(mi_diccionario["nombre"])mi_diccionario["edad"] = 29
    mi_diccionario["profesion"] = "Diseñadora"mi_diccionario.pop("ciudad")for clave, valor in mi_diccionario.items():
    print(f"{clave}: {valor}")

    Tabla resumen de métodos principales

    MétodoQué haceEjemploResultado
    get()Obtiene valor sin errord.get("x")valor o None
    pop()Elimina y devuelve valord.pop("edad")29
    popitem()Elimina último pard.popitem()(«clave», valor)
    clear()Vacía diccionariod.clear(){}
    keys()Devuelve clavesd.keys()vista de claves
    values()Devuelve valoresd.values()vista de valores
    items()Devuelve paresd.items()vista de tuplas
    update()Mezcla diccionariosd.update(otro)dic actualizado
    copy()Copia superficiald.copy()nuevo dict

    Cuándo usar diccionarios

    Usa diccionarios cuando:

    • Necesites acceso rápido por clave
    • Quieras representar datos estructurados
    • Trabajes con JSON
    • Simules registros o entidades

    Errores comunes

    • Intentar acceder a clave inexistente sin usar get()
    • Usar listas como claves
    • Confundir claves con valores
  • 1.8 – Bucles

    1.8 – Bucles

    Un bucle es una estructura que repite un bloque de código.

    En Python, usamos bucles para:

    • recorrer colecciones (listas, tuplas, sets, diccionarios, strings)
    • repetir acciones varias veces
    • ejecutar código hasta que se cumpla una condición
    • buscar elementos
    • construir resultados (sumas, contadores, filtros, etc.)

    Los dos bucles principales en Python son:

    • for → cuando recorres una secuencia/iterable o sabes “cuántas vueltas”
    • while → cuando repites mientras una condición sea verdadera (número de vueltas “desconocido”)

    1) Bucle for

    for recorre un iterable (lista, tupla, cadena, rango, diccionario, etc.)

    Recorrer una lista

    for elemento in [1, 2, 3, 4]:
    print(elemento)

    Recorrer un rango

    for i in range(5):   # 0,1,2,3,4
    print(i)

    Recorrer una cadena

    palabra = "Python"
    for letra in palabra:
    print(letra)

    1.1) range()

    range() genera números sin crear una lista enorme en memoria.

    • range(fin) → 0 … fin-1
    • range(inicio, fin) → inicio … fin-1
    • range(inicio, fin, paso) → saltos controlados
    for i in range(1, 11):
    print(i) # 1..10
    for i in range(10, 0, -1):
    print(i) # 10..1

    1.2) Recorrer con índice: enumerate()

    Cuando necesitas índice + valor, enumerate() es la forma correcta.

    nombres = ["Ana", "Luis", "Carlos"]for indice, nombre in enumerate(nombres):
    print(indice, nombre)

    Empezar desde 1 (muy útil para menús):

    for i, nombre in enumerate(nombres, start=1):
    print(i, nombre)

    1.3) Recorrer varias listas a la vez: zip()

    nombres = ["Ana", "Luis", "Carlos"]
    edades = [20, 25, 30]for nombre, edad in zip(nombres, edades):
    print(nombre, edad)

    2) Bucle while

    while repite mientras la condición sea verdadera.

    Contador simple

    contador = 0
    while contador < 5:
    print(contador)
    contador += 1

    Se usa mucho en:

    • validación de entrada (pedir hasta que sea correcto)
    • menús
    • bucles “hasta encontrar algo”
    • juegos (adivina el número)
    • lectura de datos hasta fin

    3) Control del bucle: break, continue, pass

    break (romper el bucle)

    for n in range(10):
    if n == 5:
    break
    print(n)

    continue (saltar a la siguiente vuelta)

    for n in range(10):
    if n % 2 != 0:
    continue
    print(n) # solo pares

    pass (no hacer nada, placeholder)

    for n in range(3):
    pass # "todavía no he programado esto"

    4) El else en bucles (truco ninja de Python)

    Un bucle puede tener else.
    Se ejecuta solo si NO se ha hecho break.

    Buscar un elemento

    objetivo = 7for n in [1, 3, 5, 7, 9]:
    if n == objetivo:
    print("Encontrado")
    break
    else:
    print("No encontrado")

    5) Bucles anidados

    Un bucle dentro de otro. Se usa en:

    • tablas (filas/columnas)
    • matrices
    • combinaciones
    for i in range(3):
    for j in range(2):
    print(f"i={i}, j={j}")

    Tabla de multiplicar

    numero = 5
    for i in range(1, 11):
    print(f"{numero} x {i} = {numero*i}")

    6) Iteradores: iter() y next()

    Puedes recorrer manualmente un iterable con un iterador.

    frutas = ["manzana", "banana", "cereza"]
    it = iter(frutas)print(next(it)) # manzana
    print(next(it)) # banana
    print(next(it)) # cereza

    Si te pasas, lanza StopIteration.


    7) Operadores relacionados: in y not in

    frutas = ["manzana", "banana", "cereza"]
    if "pera" not in frutas:
        print("La pera no está en la lista")

    8) Errores comunes y buenas prácticas

    Error típico: bucle infinito con while

    Si no actualizas la condición:

    # cuidado: infinito
    # while True:
    # print("hola")

    Buena práctica: evitar recorrer con índices si no hace falta

    Mejor:

    for fruta in frutas:
    print(fruta)

    En vez de:

    for i in range(len(frutas)):
    print(frutas[i])

    9) Ejemplos guiados

    Sumar lista de números

    numeros = [1, 2, 3, 4, 5]
    suma = 0for n in numeros:
    suma += nprint(suma)

    Filtrar pares

    numeros = [10, 15, 20, 25, 30]
    pares = []for n in numeros:
    if n % 2 == 0:
    pares.append(n)print(pares)

    Recorrer diccionario

    productos = {"manzana": 1.5, "banana": 0.8, "leche": 2.3}for producto, precio in productos.items():
    print(producto, precio)

    10) Ejercicios

    Bloque 1: Iterar sobre listas

    1. Dada una lista de números, calcula la suma.

    numeros = [1, 2, 3, 4, 5]

    2. Dada una lista de nombres, imprime cada nombre en mayúsculas.
    nombres = [«ana», «luis», «carlos»]

    1. Crea una lista con solo los pares.

    numeros = [10, 15, 20, 25, 30]

    Bloque 2: Iterar sobre diccionarios

    1. Imprime producto y precio.
    productos = {«manzana»: 1.5, «banana»: 0.8, «leche»: 2.3}

    1. Suma total de precios.
    2. Lista de productos cuyo precio sea mayor que 1.0.

    Bloque 3: Iterar sobre cadenas

    1. Cuenta vocales en una cadena.

    cadena = «Python es genial»

    2. Invierte una cadena con un bucle.

    cadena = «Hola Mundo»

    3. Elimina espacios y genera una nueva.

    cadena = «Hola a todos»

    Bloque 4: Índices (`range` / `enumerate`)

    1. Imprime del 1 al 10 con `range`.

    2. Tabla del 5.
    numero = 5

    3. Con enumerate, imprime índice y valor.

    frutas = [«manzana», «banana», «pera»]

    Bloque 5: `while`

    1. Imprime del 1 al 10.

    2. Pide números hasta que introduzca 0 y suma todo.

    3. Factorial con while.
    numero = 5


    Bloque 6: bucle infinito con break

    1. Contraseña correcta hasta acertar.

    contrasena_correcta = «python123»

    2. Genera aleatorios 1-10 hasta que salga > 8.
    import random

    3. Adivina número 1-20 o escribe «salir».

  • 1.9 – Patrones mentales de los bucles

    1.9 – Patrones mentales de los bucles

    1. Patrón ACUMULADOR (sumar / combinar resultados)

    Se usa cuando quieres acumular un resultado progresivamente.

    Ejemplos típicos:

    • Sumar números
    • Calcular media
    • Concatenar texto
    • Multiplicar valores

    Ejemplo: suma de una lista

    numeros = [10, 20, 30, 40]
    suma = 0   # ← acumulador
    for n in numeros:
        suma += nprint(suma)

    Idea mental:

    resultado = valor_inicial
    repetir:
    resultado = resultado + algo

    2. Patrón CONTADOR (contar ocurrencias)

    Se usa cuando quieres contar cuántas veces ocurre algo.

    Ejemplos:

    • Contar pares
    • Contar vocales
    • Contar aprobados

    Ejemplo: contar pares

    numeros = [1,2,3,4,5,6]
    contador = 0
    for n in numeros:
        if n % 2 == 0:
            contador += 1print(contador)

    Idea mental:

    contador = 0
    si ocurre algo → contador++

    3. Patrón CENTINELA (parar cuando ocurre algo)

    Se usa cuando no sabes cuántas iteraciones habrá y paras cuando aparece una condición especial.

    Muy común con while.

    Ejemplos:

    • Leer hasta introducir 0
    • Leer hasta EOF
    • Menús interactivos
    • Contraseña correcta

    Ejemplo: sumar hasta que el usuario introduzca 0

    suma = 0
    while True:
        n = int(input("Número (0 para salir): "))
        if n == 0:
            break
        suma += nprint(suma)

    Idea mental:

    repetir hasta que aparezca señal_de_parada

    La señal = centinela.


    4. Patrón BÚSQUEDA (encontrar algo)

    Se usa cuando quieres localizar un elemento.

    Ejemplos:

    • Buscar número en lista
    • Buscar palabra en texto
    • Buscar usuario en base de datos

    Ejemplo: buscar un número

    numeros = [5, 8, 3, 9, 2]
    objetivo = 9
    encontrado = False
    for n in numeros:
        if n == objetivo:
            encontrado = True
            breakprint(encontrado)

    Idea mental:

    encontrado = False
    recorrer:
    si coincide → parar

    5. Patrón FILTRADO (seleccionar algunos)

    Se usa cuando quieres quedarte solo con elementos que cumplen condición.

    Ejemplos:

    • Solo pares
    • Solo mayores de edad
    • Solo palabras largas

    Ejemplo: filtrar pares

    numeros = [1,2,3,4,5,6]
    pares = []
    for n in numeros ():
        if n % 2 == 0:
            pares.append(n)print(pares)

    Idea mental:

    resultado = []
    si cumple condición → guardar

    6. Patrón CONSTRUCCIÓN (generar algo nuevo)

    Se usa cuando quieres crear una nueva estructura a partir de otra.

    Ejemplos:

    • Crear lista de cuadrados
    • Convertir texto a lista
    • Transformar datos

    Ejemplo: cuadrados

    numeros = [1,2,3,4]
    cuadrados = []for n in numeros:
    cuadrados.append(n**2)print(cuadrados)

    Versión avanzada (list comprehension):

    cuadrados = [n**2 for n in numeros]

    Idea mental:

    resultado = []
    por cada elemento:
        transformar
        guardar

    Resumen mental

    PatrónPregunta mental
    Acumulador¿Estoy combinando resultados?
    Contador¿Estoy contando ocurrencias?
    Centinela¿Debo parar cuando pase algo?
    Búsqueda¿Estoy intentando encontrar algo?
    Filtrado¿Solo quiero algunos elementos?
    Construcción¿Estoy creando una nueva colección?

    Ejercicio de pensamiento (muy potente)

    Para cada problema, identifica primero el patrón:

    1. Sumar precios de una lista → Acumulador
    2. Contar aprobados → Contador
    3. Pedir contraseña hasta acertar → Centinela
    4. Buscar usuario → Búsqueda
    5. Quedarse con números > 10 → Filtrado
    6. Crear lista de cuadrados → Construcción
  • 1.12 – Batería de ejercicios (Con soluciones)

    1.12 – Batería de ejercicios (Con soluciones)

    Este conjunto de ejercicios en Python está organizado por bloques temáticos, y cubre los fundamentos del lenguaje de forma progresiva. Se incluyen:

    Impresión y variables: primeros pasos con print(), declaración de variables, tipos básicos (enteros, float, booleanos, cadenas).

    Funciones: creación de funciones con parámetros y valores de retorno, uso de funciones para modularizar código.

    Condicionales: uso de if, elif y else para tomar decisiones en base a condiciones.

    Bucles: implementación de bucles for y while para repetición de instrucciones.

    Colecciones: manejo de listas, tuplas, conjuntos y diccionarios. Acceso, modificación y recorrido.

    Archivos: lectura y escritura de archivos de texto utilizando open(), y comprensión básica de flujos de datos.

    Entrada de datos: uso de input() para interactuar con el usuario.

    Estos ejercicios están diseñados para estudiantes que se están iniciando en la programación, y cada script representa un problema o concepto básico resuelto de manera clara y concisa.

    Variables

    Ejercicio 1: Crea tres variables: una que guarde tu nombre, otra tu edad, y otra tu ciudad. Imprime los valores con un mensaje como:
    «Hola, mi nombre es [nombre], tengo [edad] años y vivo en [ciudad].»

    Ejercicio 2: Declara una variable con tu edad. Luego usa esta variable para calcular cuántos días, horas y minutos has vivido (aproximadamente).

        Ejemplo:
        Días: edad * 365
        Horas: días * 24
        Minutos: horas * 60

    Ejercicio 3: Escribe un programa que pida al usuario su nombre y su comida favorita. Luego imprime un mensaje como:
    «Hola [nombre], me alegra saber que te gusta [comida favorita].»

    Ejercicio 4: Declara una variable de cada tipo:

    • Un número entero (int), por ejemplo, 42.
    • Un número decimal (float), por ejemplo, 3.14.
    • Una cadena de texto (str), por ejemplo, «Hola».
    • Un valor booleano (bool), por ejemplo, True.
    • Imprime el valor y el tipo de cada variable con type().

    Condicionales

    Bloque 1: Introducción al uso de if

    1. Ejercicio 1:
      Pide al usuario un número. Si el número es mayor que 10, imprime:
      "El número es mayor que 10."
      Si no, no hagas nada.
    2. Ejercicio 2:
      Pide al usuario su edad. Si tiene 18 años o más, imprime:
      "Eres mayor de edad."
      Si tiene menos de 18, imprime:
      "Eres menor de edad."
    3. Ejercicio 3:
      Declara una variable con un número cualquiera. Si el número es positivo, imprime:
      "El número es positivo."
      Si el número es negativo, imprime:
      "El número es negativo."
      Si el número es 0, imprime:
      "El número es cero."

    Bloque 2: Operadores Comparativos

    1. Ejercicio 4:
      Pide al usuario dos números. Usa condicionales para imprimir:
      • "El primer número es mayor que el segundo."
      • "El segundo número es mayor que el primero."
      • "Ambos números son iguales."
    2. Ejercicio 5:
      Pide al usuario un número. Si el número está entre 1 y 100 (inclusive), imprime:
      "El número está dentro del rango."
      Si no, imprime:
      "El número está fuera del rango."

    Bloque 3: Uso de Condiciones Anidadas

    1. Ejercicio 6:
      Pide al usuario una letra. Si la letra es una vocal (a, e, i, o, u), imprime:
      "Es una vocal."
      Si no, imprime:
      "No es una vocal."
    2. Ejercicio 7:
      Pide al usuario un número del 1 al 7 y muestra el día de la semana correspondiente:
      • 1: Lunes
      • 2: Martes
      • …
      • 7: Domingo
        Si el número no está entre 1 y 7, imprime:
        "Número fuera de rango."

    Bloque 4: Problemas de Aplicación

    1. Ejercicio 8:
      Pide al usuario una contraseña y verifica si es igual a "secreto123". Si es correcta, imprime:
      "Acceso concedido."
      Si no, imprime:
      "Contraseña incorrecta."
    2. Ejercicio 9:
      Pide al usuario una temperatura en grados Celsius. Si la temperatura es:
      • Mayor o igual a 30: imprime "Hace calor."
      • Entre 10 y 29: imprime "El clima es templado."
      • Menor a 10: imprime "Hace frío."
    3. Ejercicio 10:
      Pide al usuario el precio de un producto. Si el precio es mayor a 1000, aplica un descuento del 10% y muestra el precio final. Si no, muestra el precio sin descuento.

    Bucles


    Bloque 1: Iterar sobre listas

    1. Dada una lista de números, calcula la suma de todos los elementos.numeros = [1, 2, 3, 4, 5]
    2. Dada una lista de nombres, imprime cada nombre en mayúsculas.nombres = ["ana", "luis", "carlos"]
    3. Crea una nueva lista que contenga solo los números pares de una lista dada.numeros = [10, 15, 20, 25, 30]

    Bloque 2: Iterar sobre diccionarios

    1. Dado un diccionario de productos y precios, imprime cada producto junto con su precio.productos = {"manzana": 1.5, "banana": 0.8, "leche": 2.3}
    2. Encuentra la suma total de los precios de los productos.
    3. Crea una lista de productos cuyo precio sea mayor que un valor dado (por ejemplo, 1.0).

    Bloque 3: Iterar sobre cadenas

    1. Dada una cadena, cuenta cuántas vocales contiene.cadena = "Python es genial"
    2. Invierte una cadena usando un bucle.cadena = "Hola Mundo"
    3. Elimina los espacios de una cadena y genera una nueva sin ellos.cadena = "Hola a todos"

    Bloque 4: Iterar con índices (range y enumerate)

    1. Usa range para imprimir los números del 1 al 10.# Salida esperada: 1, 2, ..., 10
    2. Usa range para generar una tabla de multiplicar (por ejemplo, la tabla del 5).numero = 5
    3. Usa enumerate para imprimir el índice y el valor de cada elemento en una lista.frutas = ["manzana", "banana", "pera"]

    Bloque 5: Bucle controlado por condición while

    1. Imprime los números del 1 al 10 usando un bucle while.
    2. Solicita al usuario números hasta que introduzca un 0, luego calcula la suma de los números ingresados.
    3. Dado un número, encuentra su factorial usando un bucle while.numero = 5

    Bloque 6: Bucle infinito controlado con break

    1. Simula una contraseña: solicita al usuario que introduzca una contraseña correcta hasta que lo haga bien.contrasena_correcta = "python123"
    2. Genera números aleatorios entre 1 y 10 hasta que se genere un número mayor que 8.import random
    3. Crea un programa que permita al usuario adivinar un número secreto entre 1 y 20. Termina cuando el usuario lo adivine o escriba «salir».

    Funciones

    1. Ejercicio 1: Función de resta
      • Define una función llamada restar que acepte dos parámetros y devuelva su diferencia.
      • Prueba la función con diferentes valores.
    2. Ejercicio 2: Función de saludo personalizado
      • Define una función llamada saludo_personalizado que acepte dos parámetros: nombre y edad.
      • La función debe imprimir un mensaje como «Hola, [nombre]. Tienes [edad] años.»
      • Prueba la función con diferentes valores.
    3. Ejercicio 3: Función de multiplicación
      • Define una función llamada multiplicar que acepte dos parámetros y devuelva su producto.
      • Prueba la función con diferentes valores.
    4. Ejercicio 4: Función de número par o impar
      • Define una función llamada es_par que acepte un número y devuelva True si el número es par y False si es impar.
      • Prueba la función con diferentes valores.
    5. Ejercicio 5: Función con parámetros por defecto
      • Define una función llamada saludar_con_titulo que acepte dos parámetros: nombre y titulo, con un valor por defecto para titulo.
      • La función debe imprimir un mensaje como «Hola, [titulo] [nombre].»
      • Prueba la función con y sin el parámetro titulo.
    6. Ejercicio 6: Función con *args
      • Define una función llamada promedio que acepte una cantidad arbitraria de números y devuelva su promedio.
      • Prueba la función con diferentes cantidades de argumentos.
    7. Ejercicio 7: Función con **kwargs
      • Define una función llamada imprimir_detalles que acepte una cantidad arbitraria de parámetros con nombre y los imprima en formato clave-valor.
      • Prueba la función con diferentes conjuntos de parámetros.

    Trabajando con Archivos

    1. Lectura de contenido: Crea un programa que lea el contenido de un archivo llamado notas.txt y lo imprima en la consola.
    2. Contar líneas: Escribe un programa que cuente cuántas líneas tiene un archivo de texto llamado poema.txt.
    3. Buscar palabra: Diseña un programa que busque una palabra dada por el usuario dentro de un archivo llamado articulo.txt e indique cuántas veces aparece.
    4. Escritura de lista: Escribe un programa que tome una lista de nombres y los guarde en un archivo llamado nombres.txt, un nombre por línea.
    5. Copiar contenido: Implementa un programa que copie el contenido de un archivo llamado origen.txt a un archivo llamado destino.txt.

    Descarga solución de los ejercicios.

    En programamación un solución puede tener multiples soluciones, mas tratandose de un lenguaje tan dinámico como Python. En el siguieten archivo encotraras una de esas posibilidades, pero recuerda que no por eso tu planteamiento para resolverlo sería incorrecto.

  • App Web Full-Stack (PHP/MySQL + HTML/JS)

    App Web Full-Stack (PHP/MySQL + HTML/JS)

    Desarrollar una aplicación web con frontend (HTML/JS) separado de un backend (PHP en POO + MySQL) expuesto como Web Service (API REST). La app debe incluir sistema de sesión con login y al menos un CRUD completo. El proyecto se versionará en GitHub con un README detallado y aplicará medidas mínimas de seguridad.

    1) Alcance funcional mínimo

    Elige una entidad (ej.: biblioteca, inventario, reservas, incidencias, tareas, alumnos/cursos).
    Debe incluir, como mínimo:

    • 1 entidad principal con CRUD completo: Crear, Leer (lista + detalle), Actualizar, Eliminar.
    • Autenticación de usuarios con login/logout y sesión (no solo “mock”).
    • Autorización básica: acciones protegidas solo para usuarios autenticados.
    • Listado con búsqueda y/o filtrado en el frontend.
    • Validaciones en front y back.

    Sugerencia de entidades: users (para login) + una entidad de negocio como items, courses, tickets, bookings, etc.

    2) Requisitos técnicos

    • Backend
      • Lenguaje: PHP (>=7.4; recomendable 8.x).
      • Estilo: POO (clases para modelos, controladores, servicios/repositorios).
      • Acceso a datos: MySQL con prepared statements (PDO o MySQLi).
      • Rutas tipo REST: /api/... que devuelvan JSON.
      • Gestión de sesión  (PHP session) 
    • Frontend
      • HTML + CSS + JS (vanilla).
      • Consume la API vía fetch.
    • Separación front/back
      • epositorio con carpetas: /frontend y /backend.
    • Base de datos
      • Script db/schema.sql + db/seed.sql (opcional) para poblar datos de prueba.
    • GitHub
      • Uso de commits frecuentes, ramas (opcional) y README completo.
    • Documentación
      • README.md en raíz con: descripción, arquitectura, requisitos, instalación, variables de entorno, comandos, endpoints, roles, capturas/GIF, autores.

    3) Seguridad (mínimos obligatorios)

    • Passwords: almacenar con password_hash() / password_verify().
    • SQL Injection: PDO + prepared statements SIEMPRE.
    • Sesiones:
      • session.cookie_httponly = 1, session.cookie_secure = 1 (si HTTPS), SameSite=Lax.
      • Regenerar ID de sesión tras login.
      • Tiempo de inactividad y expiración configurados.
    • Reglas de BD: usuario MySQL con mínimos privilegios (solo la base usada).
    • Errores: no exponer trazas en producción; logs internos.
    • Validación: back valida todo; front valida para UX, pero back es la autoridad
    • Backups: script/nota de backup de la BD (aunque sea manual).

     4) Entregables

    1. URL del repositorio GitHub (público o acceso concedido si privado).
    2. README.md completo.
    3. Script SQL (db/schema.sql + seed.sql opcional).
    4. Capturas o GIF corto del flujo principal (login + CRUD).
    5. Colección de Postman/Thunder Client (opcional, suma).
    6. Checklist de seguridad marcada (ver abajo).

    5) Plantilla de README.md (copiar/pegar)

    # Nombre del Proyecto

    Descripción corta del dominio elegido y objetivo de la app.

    ## Arquitectura
    – Frontend (HTML/JS) en `/frontend` consumiendo API REST JSON.
    – Backend (PHP OOP + MySQL) en `/backend` exponiendo `/api/…`.

    ## Requisitos
    – PHP 8.x, MySQL 5.7/8.x, servidor web (Apache/Nginx), Node (opcional).
    – Copiar `.env.example` a `.env` y completar valores.

    ## Instalación
    1. Clonar repo.
    2. Importar `db/schema.sql` (y `db/seed.sql` opcional).
    3. Configurar VirtualHost/servir `/backend/public` como raíz del back.
    4. Configurar `FRONT_ORIGIN` en `.env` y CORS.
    5. Abrir `/frontend/index.html` (o servir con dev server).

    ## Scripts útiles
    – (Opcional) `composer install`, `php -S localhost:8000 -t backend/public`, etc.

    ## Endpoints (resumen)
    – `POST /api/auth/login` — login (JSON body: email, password)
    – `POST /api/auth/logout` — logout
    – `GET /api/auth/me` — usuario autenticado
    – CRUD entidad principal: `/api/items`…

    ## Seguridad aplicada
    – Hashing, prepared statements, CSRF/SameSite, cabeceras, etc. (Checklist abajo)

    ## Capturas
    (Insertar imágenes o GIF)

    ## Checklist de Seguridad
    – [ ] …

  • SonarQube: El Guardián de la Calidad del Código

    SonarQube: El Guardián de la Calidad del Código

    SonarQube es una plataforma de análisis estático de código diseñada para inspeccionar la calidad y seguridad del software de forma automática. Dicho sin humo ni marketing: SonarQube lee tu código como un detective obsesivo y te señala errores, malas prácticas, vulnerabilidades y “deuda técnica” (ese conjunto de decisiones rápidas que luego pasan factura).

    A través de reglas basadas en estándares de la industria, SonarQube evalúa aspectos como bugs potenciales, código duplicado, complejidad, cobertura de pruebas, vulnerabilidades de seguridad y mantenibilidad. El resultado no es solo una lista de fallos, sino una radiografía del estado real del proyecto, permitiendo mejorar el código de forma continua.

    En entornos profesionales se integra dentro del ciclo de desarrollo (CI/CD), analizando automáticamente cada cambio en el código para evitar que los problemas crezcan en silencio. La filosofía es simple pero poderosa: la calidad no se inspecciona al final, se construye desde el principio.

    En esta práctica utilizarás SonarQube para analizar un proyecto real, interpretar sus métricas y aplicar mejoras, entendiendo cómo la calidad del código impacta directamente en la seguridad, el rendimiento y la mantenibilidad del software. Porque el código funciona… hasta que deja de hacerlo, y ahí es donde empieza la ingeniería de verdad.

    Arquitectura del proyecto

    En la máquina Ubuntu tendrás:

    • SonarQube (contenedor): servidor web + motor de análisis.
    • PostgreSQL (contenedor): base de datos de SonarQube.
    • Contenedores de desarrollo: php-apache, tomcat, etc.
    • Código fuente en el HOST (carpeta del alumno/grupo) y montado en los contenedores de desarrollo.
    • Sonar Scanner (mejor en un contenedor aparte, o instalado en el host) que:
      • lee el código (desde el host o volumen compartido)
      • manda el análisis a SonarQube por HTTP.

    Patrón recomendado:

    • El código vive en /home/alumno/proyectos/proyectoX
    • Ese directorio se monta en:
      • el contenedor de desarrollo (para ejecutar)
      • el contenedor “scanner” (para analizar)

    FASE 1 — Montar SonarQube en Docker (Ubuntu)

    Dejar SonarQube funcionando en la misma máquina Ubuntu, dentro de contenedores Docker, con base de datos PostgreSQL y persistencia, accesible desde el navegador.

    Al terminar, podrás entrar a:

    • http://localhost:9000 (si estás en la misma máquina)
    • o http://IP_DE_LA_MAQUINA:9000 (si accedes desde otra)

    1) Comprobar requisitos previos

    1.1 Verifica que Docker funciona

    En la terminal:

    docker --version
    docker ps
    

    Si docker ps no da error, Docker está operativo.

    1.2 Verifica que tienes Docker Compose

    En Ubuntu moderno suele ser el plugin docker compose (con espacio):

    docker compose version
    

    Si te funciona, perfecto. Si no, lo instalaremos más adelante (pero normalmente ya viene si Docker está bien instalado).


    2) Preparar carpeta del proyecto

    Vamos a trabajar de forma ordenada en una carpeta específica para SonarQube.

    mkdir -p ~/proyectos/sonarqube-docker
    cd ~/proyectos/sonarqube-docker
    

    Dentro crearemos el docker-compose.yml.


    3) Ajuste del sistema: vm.max_map_count (IMPORTANTE)

    SonarQube usa Elasticsearch internamente y en Linux necesita este parámetro del kernel.

    3.1 Ver el valor actual

    sysctl vm.max_map_count
    

    Si te devuelve algo como 65530 o mas bajo, hay que subirlo.

    3.2 Subirlo temporalmente (hasta reinicio)

    sudo sysctl -w vm.max_map_count=262144
    

    3.3 Hacerlo permanente

    echo "vm.max_map_count=262144" | sudo tee /etc/sysctl.d/99-sonarqube.conf
    sudo sysctl --system
    

    ✅ Comprobación final:

    sysctl vm.max_map_count
    

    Debe quedar en 262144.


    4) Crear el archivo docker-compose.yml

    En la carpeta ~/proyectos/sonarqube-docker crea el archivo:

    nano docker-compose.yml
    

    Pega esto:

    services:
      db:
        image: postgres:15
        container_name: sonar-db
        environment:
          POSTGRES_USER: sonar
          POSTGRES_PASSWORD: sonar
          POSTGRES_DB: sonarqube
        volumes:
          - sonar_db_data:/var/lib/postgresql/data
        networks:
          - sonar-net
        restart: unless-stopped
    
      sonarqube:
        image: sonarqube:community
        container_name: sonarqube
        depends_on:
          - db
        ports:
          - "9000:9000"
        environment:
          SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonarqube
          SONAR_JDBC_USERNAME: sonar
          SONAR_JDBC_PASSWORD: sonar
        volumes:
          - sonar_data:/opt/sonarqube/data
          - sonar_extensions:/opt/sonarqube/extensions
          - sonar_logs:/opt/sonarqube/logs
        networks:
          - sonar-net
        restart: unless-stopped
    
    volumes:
      sonar_db_data:
      sonar_data:
      sonar_extensions:
      sonar_logs:
    
    networks:
      sonar-net:
        driver: bridge
    

    Guarda y sal:

    • CTRL + O, Enter
    • CTRL + X

    5) Arrancar SonarQube

    En la misma carpeta:

    docker compose up -d
    

    Comprueba que están levantados:

    docker ps
    

    Deberías ver:

    • sonar-db
    • sonarqube

    6) Ver logs y esperar a que esté listo

    SonarQube tarda un poco en arrancar (no es instantáneo).

    Para ver el estado:

    docker logs -f sonarqube
    

    Cuando veas un mensaje tipo “SonarQube is up” o que el sistema ya ha iniciado, ya puedes abrir el navegador.

    (Para salir de logs: CTRL + C)


    7) Acceso web y primer login

    Abre en el navegador:

    • http://localhost:9000

    Credenciales por defecto:

    • Usuario: admin
    • Contraseña: admin

    Al entrar, SonarQube te obligará a cambiar la contraseña. Pon una segura (y anótala).



    8) Problemas típicos (y solución rápida)

    “SonarQube no arranca / se reinicia”

    Mira logs:

    docker logs --tail 200 sonarqube
    

    Casi siempre es vm.max_map_count o falta de RAM.

    “No puedo entrar a localhost:9000”

    Verifica:

    • ¿está el contenedor levantado? docker ps
    • ¿otro servicio usa el puerto 9000? (raro, pero posible)

    FASE 2 — Proyectos de ejemplo + preparación para análisis


    1) Estructura de carpetas (base del universo)

    Cada grupo trabajará con esta estructura:

    mkdir -p ~/proyectos/grupo01/proyecto-php/src
    mkdir -p ~/proyectos/grupo01/proyecto-java/src
    cd ~/proyectos/grupo01
    

    La regla cósmica:

    El código vive en el HOST, y se monta en los contenedores.

    Esto permite:

    • Ejecutar código en contenedor
    • Analizar el mismo código con Sonar

    2) Proyecto PHP con Apache en contenedor

    Entramos:

    cd ~/proyectos/grupo01/proyecto-php
    

    2.1 Crear archivo PHP con “malos olores”

    nano src/index.php
    

    Pega esto:

    <?php
    
    function suma($a, $b){
        return $a + $b;
    }
    
    function suma2($a, $b){ // duplicación
        return $a + $b;
    }
    
    $x = 5;
    $y = 10;
    
    if($x == $y){
        echo "Son iguales";
    } else {
        echo "No son iguales";
    }
    
    echo "<br>Resultado: " . suma($x, $y);
    
    // Código muerto
    $z = 100;
    
    ?>
    

    Guarda y sal.

    Este archivo tiene:

    • Duplicación
    • Código muerto
    • Comparación débil
    • Poca mantenibilidad

    Perfecto para SonarQube.


    2.2 Crear docker-compose del proyecto PHP

    nano docker-compose.yml
    
    services:
      php-apache:
        image: php:8.2-apache
        container_name: php-grupo01
        ports:
          - "8081:80"
        volumes:
          - ./src:/var/www/html
        restart: unless-stopped
    

    Arrancar:

    docker compose up -d
    

    Abrir navegador:

    • http://localhost:8081

    Debe mostrar:

    No son iguales
    Resultado: 15

    Si funciona, el contenedor ejecuta el código que vive en el host. Exactamente lo que queremos.


    3) Crear configuración básica de Sonar para PHP

    En la raíz del proyecto PHP:

    nano sonar-project.properties
    
    sonar.projectKey=lab1-php
    sonar.projectName=Proyecto PHP Lab1
    sonar.projectVersion=1.0
    sonar.sources=src
    sonar.sourceEncoding=UTF-8
    

    Este archivo le dice al scanner:

    • qué proyecto es
    • dónde está el código
    • qué analizar

    (No ejecutamos aún.)


    4) (Opcional pero recomendado) Proyecto Java simple

    Entramos:

    cd ~/proyectos/grupo01/proyecto-java
    
    nano src/Main.java
    
    public class Main {
    
        public static int suma(int a, int b){
            return a + b;
        }
    
        public static int suma2(int a, int b){ // duplicación
            return a + b;
        }
    
        public static void main(String[] args){
            int x = 5;
            int y = 5;
    
            if(x == y){
                System.out.println("Iguales");
            }
    
            int z = 100; // código muerto
            System.out.println(suma(x, y));
        }
    }
    

    sonar-project.properties para Java

    nano sonar-project.properties
    
    sonar.projectKey=lab1-java
    sonar.projectName=Proyecto JAVA Lab1
    sonar.projectVersion=1.0
    sonar.sources=.
    sonar.sourceEncoding=UTF-8
    
    

    (No hace falta contenedor para analizar Java simple.)


    5) Concepto crítico que deben entender

    SonarQube no analiza contenedores.

    Analiza carpetas con código.

    Por eso:

    • El código está en el host
    • El contenedor solo ejecuta
    • El scanner leerá la carpeta del host

    Esto es exactamente cómo funciona en empresas reales.



    FASE 3 — Primer análisis real con Sonar Scanner


    1) Crear Token en SonarQube

    El scanner necesita autenticarse. No se usan usuario/contraseña, se usa token.

    Paso 1 — Entrar en SonarQube

    Abrir:

    http://localhost:9000 o http://ipservidor:9000

    Login con tu usuario.


    Paso 2 — Crear token

    Ir a:

    User → My Account → Security → Generate Token

    Nombre sugerido:

    scanner-grupo01

    Copiar el token (solo se muestra una vez).

    Guárdalo. Es la llave del reino.


    2) Ejecutar Sonar Scanner desde contenedor

    No instalamos nada en el host. Usamos un contenedor efímero. Limpio. Reproducible. Profesional.

    Nos situamos en el proyecto PHP:

    cd ~/proyectos/grupo01/proyecto-php
    

    Ahora ejecutamos:

    sudo docker run --rm --network host \
      -e SONAR_HOST_URL="http://127.0.0.1:9000" \
      -e SONAR_TOKEN="sqa_3a808da1a78a8cbefd19cc39638a829ccb69e4ed" \
      -v "$(pwd):/usr/src" \
      -w /usr/src \
      sonarsource/sonar-scanner-cli \
      -Dsonar.ws.timeout=120 \
      -Dsonar.scanner.ws.timeout=120
    

    3) Qué está pasando realmente El contenedor scanner:

    • Entra
    • Lee /usr/src (tu proyecto montado)
    • Lee sonar-project.properties
    • Analiza el código
    • Envía resultados a SonarQube
    • Muere (porque usamos --rm)

    Herramienta efímera. Análisis persistente.


    4) Ver el resultado del análisis

    Ir a:

    http://ip_del_server:9000

    Entrar en:

    Projects

    Debería aparecer:

    Proyecto PHP Grupo01

    Entrar y observar:

    • Bugs
    • Vulnerabilities
    • Code Smells
    • Maintainability Rating
    • Duplications

    Tu código imperfecto ahora tiene diagnóstico.


    5) Analizar también el proyecto Java

    Entramos:

    cd ~/proyectos/grupo01/proyecto-java
    

    Lanzamos scanner igual:

    sudo docker run --rm --network host \
      -e SONAR_HOST_URL="http://127.0.0.1:9000" \
      -e SONAR_TOKEN="sqa_3a808da1a78a8cbefd19cc39638a829ccb69e4ed" \
      -v "$(pwd):/usr/src" \
      -w /usr/src \
      sonarsource/sonar-scanner-cli \
      -Dsonar.ws.timeout=120 \
      -Dsonar.scanner.ws.timeout=120
    
    

    Ir a SonarQube → Projects → ver Proyecto Java Grupo01.


    6) Problemas típicos y cómo sobrevivir


    “Not authorized”

    Token mal puesto o copiado con espacios.

    Regenera token y prueba otra vez.


    “Project not found”

    Falta sonar-project.properties o está mal.

    Verifica que esté en la raíz del proyecto.


    “SonarQube is not ready”

    El servidor aún no terminó de arrancar.

    Espera 30–60s y repite.


    Importante

    • Sonar no ejecuta código → lo analiza estáticamente
    • Detecta:
      • Duplicación
      • Código muerto
      • Riesgos de seguridad
      • Mala mantenibilidad
    • El análisis queda guardado aunque el scanner desaparezca
    • Esto es exactamente lo que hacen empresas reales en CI/CD

    Magnífica jugada pedagógica. Aquí el alumno deja de jugar con ejemplos de juguete y entra en territorio real: código con historia, decisiones, cicatrices y pecado técnico acumulado. Eso es donde SonarQube brilla de verdad. Vamos a plantearlo como RETO FINAL — Análisis real de un proyecto propio listo para entregar a alumnos.


    🧪 RETO FINAL — Auditoría real con SonarQube

    Misión

    Vas a analizar con SonarQube un proyecto real desarrollado por ti, no un ejemplo:

    Puedes elegir UNO o Todos:

    • Tu CRUD Java/PHP del proyecto anterior
    • Un WordPress (tema propio, plugin o código modificado)
    • Cualquier proyecto mediano (mínimo ~20–30 archivos)

    Objetivo: tratar el proyecto como si fueras el equipo de calidad de una empresa real.


    Qué debes conseguir

    1. Analizar el proyecto completo con SonarQube
    2. Evaluar el estado real del código
    3. Detectar problemas técnicos reales
    4. Aplicar refactorización
    5. Mejorar el Quality Gate

    Fase 1 — Preparar el proyecto

    Regla clave

    SonarQube analiza código fuente, no contenedores ni binarios.

    Tu proyecto debe tener:

    • Carpeta raíz clara
    • Código fuente accesible
    • Sin vendor/, node_modules/, target/, etc. (opcional pero recomendado ignorarlos)

    Crear sonar-project.properties

    En la raíz del proyecto:

    Para PHP / WordPress

    sonar.projectKey=proyecto-real-php
    sonar.projectName=Proyecto Real PHP
    sonar.sources=.
    sonar.sourceEncoding=UTF-8
    

    Para Java

    sonar.projectKey=proyecto-real-java
    sonar.projectName=Proyecto Real Java
    sonar.sources=src
    sonar.sourceEncoding=UTF-8
    

    Fase 2 — Ejecutar análisis

    Desde la raíz del proyecto:

    sudo docker run --rm --network host \
      -e SONAR_HOST_URL="http://127.0.0.1:9000" \
      -e SONAR_TOKEN="TU_TOKEN" \
      -v "$(pwd):/usr/src" \
      -w /usr/src \
      sonarsource/sonar-scanner-cli \
      -Dsonar.ws.timeout=120
    

    Ir a SonarQube → Projects → entrar al proyecto.


    Fase 3 — Diagnóstico técnico

    Debes analizar:

    1. Bugs

    Errores potenciales de ejecución.

    2. Vulnerabilities

    Riesgos de seguridad (inyección, validación, etc.)

    3. Code Smells

    Problemas de diseño/mantenibilidad.

    4. Duplicación

    Código repetido.

    5. Complejidad

    Métodos demasiado complejos.


    Fase 4 — Informe técnico (parte importante del reto)

    Debes escribir un informe con:

    Estado inicial

    • Quality Gate: PASS / FAIL
    • Nº Bugs
    • Nº Vulnerabilities
    • Nº Code Smells
    • Maintainability Rating

    Análisis crítico

    Selecciona mínimo 5 problemas reales y explica:

    • Qué problema detectó SonarQube
    • Por qué es un problema técnico real
    • Qué riesgo tiene (mantenibilidad, seguridad, errores)
    • Cómo lo solucionaste

    Refactorización aplicada

    Ejemplos:

    • Eliminación de duplicación
    • Validación de entrada
    • Mejora de legibilidad
    • División de funciones largas
    • Tipado fuerte
    • Eliminación de código muerto

    Estado final

    • Nuevo Quality Gate
    • Cambios en métricas
    • Mejora del rating

    Bonus

    • Integrar análisis en GitHub Actions / GitLab CI
    • Hacer que el merge falle si el gate está en rojo
    • Añadir tests → mejorar coverage
    • Analizar plugin o tema WordPress completo
    • Comparar análisis antes/después


    Cambiar SonarQube a español

    Método 1 — Cambiar idioma del usuario (lo primero que debes probar)

    1. Entra en SonarQube → http://localhost:9000
    2. Arriba a la derecha → My Account
    3. Preferences
    4. Language → Spanish (Español)
    5. Guardar

    Si tu versión tiene traducción cargada, la interfaz cambia inmediatamente (menús, labels, etc.).


    Si NO aparece Español en la lista

    Entonces tu SonarQube no tiene el paquete de idioma instalado (pasa en algunas builds).

    Método 2 — Instalar plugin de idioma (si disponible)

    1. Administration → Marketplace
    2. Buscar: Spanish o Localization
    3. Si aparece plugin de idioma → Install
    4. Reiniciar SonarQube

    ⚠️ En versiones recientes de SonarQube Community, el marketplace ya no siempre incluye todos los idiomas, y parte de la interfaz puede quedarse en inglés aunque cambies idioma. No es fallo, es así por diseño.


    Realidad técnica

    • El análisis, reglas, métricas → no dependen del idioma
    • Muchos equipos trabajan SonarQube en inglés (terminología estándar industria)
    • Aunque pongas español, algunas secciones seguirán en inglés
    • Los nombres clave no se traducen:
      • Quality Gate
      • Code Smell
      • Vulnerability
      • Maintainability

    Esto es normal.


  • Mis extensiones de Vs-Code

    Remote – SSH

    Nombre: Remote – SSH
    Autor: Microsoft

    Descripción:
    Permite conectarte a un servidor remoto mediante SSH y abrir carpetas, editar archivos, ejecutar terminal y trabajar exactamente igual que si el proyecto estuviera en tu equipo local. VS Code instala automáticamente un “mini-servidor” en la máquina remota para que todo funcione de forma transparente.

    Qué la hace especial (dato importante):

    • No trabajas sobre copias → trabajas directamente en el servidor
    • Permite usar entornos reales (Linux, Docker host, servidores cloud, Raspberry, etc.)
    • Soporta claves SSH, config ~/.ssh/config, jump hosts y túneles
    • Puedes ejecutar extensiones en remoto (Python, Java, Node, Docker…)
    • Ideal para desarrollo en servidores, laboratorios, contenedores o máquinas virtuales
    • Reduce el clásico caos: “en mi máquina funciona”
  • Subida de imágenes en Java JEE usando Servlets (Multipart)

    Subida de imágenes en Java JEE usando Servlets (Multipart)

    En esta práctica aprenderás a:

    • Enviar un archivo desde un formulario HTML hacia un Servlet.
    • Procesar peticiones multipart/form-data.
    • Guardar una imagen en el servidor.
    • Obtener el nombre del archivo subido.
    • Persistir la referencia en base de datos.
    • Entender el flujo completo cliente → servidor → disco → BD → vista.

    Arquitectura del proceso

    El flujo que vamos a implementar es el siguiente:

    [Formulario HTML]
            ↓
    POST multipart/form-data
            ↓
    [Servlet controlador]
            ↓
    Procesa campos + fichero
            ↓
    Guarda imagen en disco
            ↓
    Inserta datos en BD (nombre del archivo)
            ↓
    Redirección a galería
            ↓
    [Vista HTML muestra la imagen]

    Video

    El Servlet

    Anotaciones necesarias

    @WebServlet("/BestiasFoto")
    @MultipartConfig
    public class BestiasFoto extends HttpServlet {

    @WebServlet

    Define la URL que ejecuta el servlet:

    http://servidor/app/BestiasFoto

    @MultipartConfig

    Activa el soporte de subida de archivos.

    Sin esta anotación:

    • request.getPart("foto") no funcionará
    • El servidor no podrá procesar multipart/form-data

    Variables del Servlet

    private String pathFiles = "C:\\entornos\\clasesMaster\\BestiarioJEE\\bestiario\\src\\main\\webapp\\img\\";
    private File uploads = new File(pathFiles);
    • pathFiles → carpeta donde se guardarán las imágenes
    • uploads → objeto File que representa esa carpeta

    En esta práctica guardamos las imágenes dentro del proyecto. En producción normalmente se usa una carpeta externa.


    Método doPost()

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    
        if(request.getParameter("opcion").equals("1")) {
            insertar_actualizarBestia(request,response);
        } else if(request.getParameter("opcion").equals("2")) {
            listarBestias(Integer.parseInt(request.getParameter("id")),request,response);
        }
    }

    Este método actúa como enrutador.

    opcionAcción
    1Inserta o actualiza una bestia + sube imagen
    2Listado de bestias

    Método insertar_actualizarBestia()

    Este método:

    1. Recoge los datos del formulario
    2. Sube la imagen
    3. Inserta en BD
    4. Redirige a la galería
    public void insertar_actualizarBestia(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
    
        int id = Integer.parseInt(request.getParameter("id"));
        String nombre = request.getParameter("nombre");
        int tam = Integer.parseInt(request.getParameter("tam"));
        int alineamiento = Integer.parseInt(request.getParameter("alineamiento"));
        String descripcion = request.getParameter("descripcion");
    
        String ruta = this.subirFoto(request, response);
    
        if(id != 0) {
            // actualizar
        } else {
            Bestia b = new Bestia(nombre, tam, alineamiento, descripcion, ruta);
            try {
                b.insertar();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            response.sendRedirect("galeria.html");
        }
    }

    Explicación

    • getParameter() → obtiene datos normales del formulario
    • subirFoto() → guarda la imagen y devuelve el nombre del archivo
    • sendRedirect() → evita reenviar el formulario al refrescar

    Método subirFoto()

    Este método recibe el archivo, lo guarda en el servidor y devuelve el nombre.

    private String subirFoto(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
    
        Part part = request.getPart("foto");
    
        Path path = Paths.get(part.getSubmittedFileName());
        String fileName = path.getFileName().toString();
    
        InputStream input = part.getInputStream();
    
        String ruta ="";
        String nombreArchivo ="";
    
        if(input != null) {
    
            File file = new File(uploads, fileName);
    
            ruta = file.getAbsolutePath();
            nombreArchivo = file.getName();
    
            System.out.println("La ruta de la foto es: " + ruta);
    
            try {
                Files.copy(input, file.toPath());
            } catch (Exception e) {
                PrintWriter respuesta = response.getWriter();
                respuesta.print("Error al copiar la foto");
                System.out.println("Error al copiar la foto");
            }
        }
    
        return nombreArchivo;
    }

    Qué ocurre paso a paso

    1. Obtener el archivo enviado

    Part part = request.getPart("foto");

    Part representa el archivo enviado desde el formulario.


    2. Obtener el nombre del archivo

    Path path = Paths.get(part.getSubmittedFileName());
    String fileName = path.getFileName().toString();

    Se usa Path para eliminar posibles rutas y quedarse solo con el nombre.


    3. Obtener los bytes del archivo

    InputStream input = part.getInputStream();

    Un InputStream permite leer el contenido del archivo.


    4. Crear el archivo destino

    File file = new File(uploads, fileName);

    Esto crea el archivo dentro de la carpeta img.


    5. Copiar el archivo

    Files.copy(input, file.toPath());

    Copia los bytes desde el InputStream al fichero del servidor.


    6. Devolver el nombre del archivo

    returnnombreArchivo;

    En la base de datos solo guardamos el nombre, no la ruta completa.


    Formulario HTML necesario

    <form action="BestiasFoto" method="post" enctype="multipart/form-data">
    
      <input type="hidden" name="opcion" value="1">
      <input type="hidden" name="id" value="0">
    
      <input type="text" name="nombre" placeholder="Nombre">
      <input type="number" name="tam" placeholder="Tamaño">
      <input type="number" name="alineamiento" placeholder="Alineamiento">
      <textarea name="descripcion"></textarea>
    
      <input type="file" name="foto" accept="image/*">
    
      <button type="submit">Guardar</button>
    
    </form>

    Mostrar la imagen guardada

    Si en BD se guarda dragon.jpg:

    <img src="img/dragon.jpg">

    Métodos utilizados

    HttpServletRequest

    MétodoFunción
    getParameter()Obtiene valores del formulario
    getPart()Obtiene el archivo subido

    Part

    MétodoFunción
    getSubmittedFileName()Nombre original del archivo
    getInputStream()Bytes del archivo
    getSize()Tamaño del archivo
    getContentType()Tipo MIME

    Files / Path

    MétodoFunción
    Files.copy()Copia el archivo al servidor
    Paths.get()Crea un Path
    getFileName()Obtiene el nombre del archivo

    Buenas prácticas

    • Guardar solo el nombre del archivo en BD
    • Validar que el archivo no esté vacío
    • Validar tipo MIME (image/png, image/jpeg…)
    • Evitar nombres duplicados
    • Crear la carpeta si no existe

    Tabla de métodos/clases usados

    Servlet / Request / Response

    ElementoMétodo¿Para qué sirve?
    HttpServletRequestgetParameter("x")Lee un campo normal del formulario (texto, hidden, number…)
    HttpServletRequestgetPart("foto")Recupera un archivo (o parte) del multipart con ese name
    HttpServletResponsesendRedirect("url")Redirige al navegador a otra página tras el POST
    HttpServletResponsegetWriter()Permite escribir una respuesta de texto al cliente

    Multipart / Part

    ElementoMétodo¿Para qué sirve?
    PartgetSubmittedFileName()Devuelve el nombre original del fichero enviado
    PartgetInputStream()Devuelve un stream con los bytes del archivo
    PartgetSize()Tamaño del archivo en bytes (útil para validar)
    PartgetContentType()Tipo MIME (ej. image/png)

    Files / Path

    ElementoMétodo¿Para qué sirve?
    Pathsget(String)Crea un Path a partir de una ruta o nombre
    PathgetFileName()Devuelve el último segmento (nombre)
    Filescopy(InputStream, Path, ...)Copia datos de un stream a un fichero
    FilegetAbsolutePath()Ruta absoluta en disco
    FilegetName()Nombre del archivo sin ruta

    Validaciones en la subida de imágenes (métodos independientes)

    • Validar el nombre del archivo
    • Evitar archivos duplicados
    • Validar tipo MIME
    • Validar extensión
    • Validar tamaño
    • Validar que el archivo sea realmente una imagen
    • Generar nombre seguro (UUID)

    Cada método tiene una responsabilidad única.


    1. Validar nombre de archivo

    Evitar nombres peligrosos, rutas maliciosas o caracteres inválidos.

    Método

    private String validarNombreArchivo(String originalName) throws ServletException {
    
        if (originalName == null || originalName.isBlank()) {
            throw new ServletException("El archivo no tiene nombre válido");
        }
    
        // eliminar rutas potenciales
        String nombre = Paths.get(originalName).getFileName().toString();
    
        // permitir solo letras, números, punto, guion y guion bajo
        if (!nombre.matches("[a-zA-Z0-9._-]+")) {
            throw new ServletException("Nombre de archivo inválido");
        }
    
        return nombre;
    }

    Qué hace

    • Evita nombres vacíos
    • Elimina rutas (C:\algo\malicioso\file.jpg)
    • Bloquea caracteres peligrosos
    • Reduce riesgo de path traversal

    2. Validar tipo MIME

    Evitar que suban archivos ejecutables disfrazados de imagen.

    Método

    private void validarTipoMime(Part part) throws ServletException {
    
        String mime = part.getContentType();
    
        if (mime == null || !mime.startsWith("image/")) {
            throw new ServletException("Solo se permiten archivos de imagen");
        }
    }

    Qué hace

    • Comprueba que el archivo es image/*
    • Bloquea .exe, .jsp, .php, etc.

    3. Validar extensión

    Permitir solo formatos conocidos.

    Método

    private void validarExtension(String fileName) throws ServletException {
    
        String ext = "";
    
        int punto = fileName.lastIndexOf('.');
        if (punto >= 0) {
            ext = fileName.substring(punto + 1).toLowerCase();
        }
    
        switch (ext) {
            case "jpg":
            case "jpeg":
            case "png":
            case "gif":
            case "webp":
                return;
            default:
                throw new ServletException("Extensión no permitida");
        }
    }

    Qué hace

    • Extrae la extensión
    • Permite solo imágenes conocidas
    • Evita subir scripts renombrados

    4. Validar tamaño del archivo

    Evitar archivos demasiado grandes.

    Método

    private void validarTamano(Part part, long maxBytes) throws ServletException {
    
        if (part.getSize() == 0) {
            throw new ServletException("El archivo está vacío");
        }
    
        if (part.getSize() > maxBytes) {
            throw new ServletException("El archivo supera el tamaño permitido");
        }
    }

    Uso típico

    validarTamano(part, 2*1024*1024); // 2 MB

    5. Validar que realmente sea imagen

    Evitar archivos falsos con extensión .jpg pero que no son imagen.

    Método

    private void validarContenidoImagen(InputStream input) throws ServletException, IOException {
    
        BufferedImage img = ImageIO.read(input);
    
        if (img == null) {
            throw new ServletException("El archivo no es una imagen válida");
        }
    }

    Qué hace

    • Intenta decodificar la imagen
    • Si falla → no es imagen real

    6. Evitar archivos duplicados

    No sobrescribir archivos existentes.

    Método

    private File evitarDuplicado(File carpeta, String fileName) {
    
        File file = new File(carpeta, fileName);
    
        if (!file.exists()) {
            return file;
        }
    
        String nombre = fileName;
        String base = nombre;
        String ext = "";
    
        int punto = nombre.lastIndexOf('.');
        if (punto >= 0) {
            base = nombre.substring(0, punto);
            ext = nombre.substring(punto);
        }
    
        int contador = 1;
        File nuevo;
    
        do {
            nuevo = new File(carpeta, base + "_" + contador + ext);
            contador++;
        } while (nuevo.exists());
    
        return nuevo;
    }

    Qué hace

    Si ya existe:

    dragon.jpg

    Genera:

    dragon_1.jpg
    dragon_2.jpg
    dragon_3.jpg

    7. Generar nombre seguro (UUID)

    Esto penalizaria el SEO si se trata de archivos que los buscadores pudiesen indexar

    Evitar colisiones y nombres maliciosos.

    Método

    private String generarNombreSeguro(String original) {
    
        String ext = "";
    
        int punto = original.lastIndexOf('.');
        if (punto >= 0) {
            ext = original.substring(punto);
        }
    
        return java.util.UUID.randomUUID().toString() + ext;
    }

    Qué hace

    Convierte:

    dragon.png

    en algo como:

    f47ac10b-58cc-4372-a567-0e02b2c3d479.png

    8. Crear carpeta si no existe

    private void asegurarCarpeta(File carpeta) throws ServletException {
    
        if (!carpeta.exists()) {
            if (!carpeta.mkdirs()) {
                throw new ServletException("No se pudo crear la carpeta de subida");
            }
        }
    }

    9. Ejemplo integrando todas las validaciones

    Este sería el flujo dentro de subirFoto():

    Part part = request.getPart("foto");
    
    validarTipoMime(part);
    validarTamano(part, 2 * 1024 * 1024);
    
    String nombreOriginal = validarNombreArchivo(part.getSubmittedFileName());
    validarExtension(nombreOriginal);
    
    InputStream input = part.getInputStream();
    validarContenidoImagen(input);
    
    // resetear stream tras validación
    input = part.getInputStream();
    
    asegurarCarpeta(uploads);
    
    String nombreSeguro = generarNombreSeguro(nombreOriginal);
    
    File destino = evitarDuplicado(uploads, nombreSeguro);
    
    Files.copy(input, destino.toPath());
    
    return destino.getName();


    Subir un archivo parece trivial, pero en realidad es una intersección entre E/S, seguridad, validación, arquitectura y persistencia. Cuando el alumno domina esto, deja de “subir fotos” y empieza a construir backend serio.

  • 3.1 – EL DOM (DOCUMENT OBJECT MODEL)

    3.1 – EL DOM (DOCUMENT OBJECT MODEL)

    1. ¿Qué es el DOM?

    El DOM (Document Object Model) es la representación en forma de árbol de objetos que el navegador crea a partir del HTML.

    El navegador no “lee HTML y ya”.
    Lo convierte en objetos manipulables con JavaScript.

    Ejemplo simple:

    HTML:

    <body>
      <h1>Hola</h1>
      <p>Texto</p>
    </body>

    El navegador lo convierte mentalmente en:

    document
     └── body
         ├── h1
         │   └── "Hola"
         └── p
             └── "Texto"

    Cada etiqueta es un nodo (node).
    Cada nodo es un objeto JavaScript.

    Eso significa que puedes:

    • Leerlo
    • Modificarlo
    • Crear nuevos nodos
    • Borrarlos
    • Moverlos

    El DOM es, literalmente, la memoria viva del HTML.


    2. El objeto document

    El punto de entrada al DOM es:

    document

    Es el objeto raíz del árbol. Desde aquí puedes acceder a todo el HTML.

    Ejemplo:

    console.log(document.title);
    console.log(document.body);

    3. Seleccionar elementos del DOM

    Antes de modificar algo, hay que localizar el nodo.

    3.1 getElementById (el clásico)

    HTML:

    document.querySelector("#titulo");      // por id
    document.querySelector(".clase");       // por clase
    document.querySelector("h1");           // por etiqueta
    document.querySelector("div p");        // descendiente

    JS:

    const titulo = document.getElementById("titulo");
    console.log(titulo);

    Devuelve un solo elemento.


    3.2 querySelector (el más versátil)

    Permite usar selectores CSS.

    document.querySelector("#titulo");      // por id
    document.querySelector(".clase");       // por clase
    document.querySelector("h1");           // por etiqueta
    document.querySelector("div p");        // descendiente

    Devuelve el primer elemento que coincide.


    3.3 querySelectorAll (varios elementos)

    const items = document.querySelectorAll("li");
    console.log(items);

    Devuelve una NodeList (colección).

    Se recorre así:

    items.forEach(item => {
      console.log(item.textContent);
    });

    4. Leer y modificar contenido

    4.1 textContent (forma segura)

    const titulo = document.getElementById("titulo");
    titulo.textContent = "Nuevo título";

    Nunca ejecuta HTML. Solo texto.


    4.2 innerHTML (usar con cuidado)

    titulo.innerHTML = "<b>Hola</b>";

    Inserta HTML real.
    Peligroso si el contenido viene del usuario (XSS).

    Regla:
    Si solo es texto → textContent.


    5. Manipular atributos

    Leer atributo

    const img = document.querySelector("img");
    console.log(img.getAttribute("src"));

    Modificar atributo

    img.setAttribute("src", "nueva.jpg");

    6. Manipular clases CSS

    Añadir clase

    elemento.classList.add("activo");

    Quitar clase

    elemento.classList.remove("activo");

    Alternar

    elemento.classList.toggle("activo");

    7. Crear elementos (crear nodos)

    Aquí empieza la verdadera manipulación del DOM.

    Crear un elemento

    const p = document.createElement("p");
    p.textContent = "Soy nuevo en el DOM";

    Todavía no está en la página.


    8. Insertar elementos en el DOM

    append (al final)

    document.body.append(p);

    prepend (al principio)

    document.body.prepend(p);

    append en otro nodo

    const contenedor = document.getElementById("contenedor");
    contenedor.append(p);

    9. Eliminar elementos

    p.remove();

    10. Ejercicio guiado — Pintar lista desde un array

    HTML:

    <ulid="lista"></ul>

    JS:

    const datos = ["Manzana", "Pera", "Plátano", "Kiwi"];
    
    const lista = document.getElementById("lista");
    
    datos.forEach(fruta => {
      const li = document.createElement("li");
      li.textContent = fruta;
      lista.append(li);
    });

    Resultado: el DOM se construye dinámicamente.


    11. Cómo piensa un programador al usar el DOM

    El flujo mental correcto es:

    1. Selecciono el nodo contenedor
    2. Creo elementos nuevos
    3. Les pongo contenido
    4. Los inserto en el DOM

    Es exactamente el mismo pensamiento que usarías manipulando objetos en Java.


    12. Errores comunes

    ❌ Usar innerHTML para todo

    Malo para seguridad y mantenimiento.

    ❌ Crear elementos sin insertarlos

    Si no haces append/prepend → no aparecen.

    ❌ Duplicar IDs

    Un ID debe ser único.

    ❌ Mezclar lógica y HTML sin orden

    Primero datos, luego render.


    13. Mini-reto

    Crea una tabla HTML y, desde JavaScript, genera 5 filas con:

    • Nombre
    • Edad
    • Ciudad

    Todo debe construirse con createElement.

    Nada de escribir filas en el HTML.


    14. Lo que debes haber aprendido

    Al terminar este bloque debes poder:

    • Entender qué es el DOM
    • Seleccionar elementos
    • Modificar texto
    • Manipular atributos y clases
    • Crear elementos dinámicamente
    • Insertar y eliminar nodos
    • Construir HTML desde datos

    Si esto está claro, en el siguiente bloque el cerebro da el salto natural:

    JSON → JavaScript → DOM

    Y ahí empieza el verdadero front conectado a tu backend JEE.

    El DOM no es difícil. Solo es un árbol.
    Y los árboles, curiosamente, se doman rama por rama. 🌿

    puedes hacerme una tabla con los metodos o atributos que puedo sacar de cada elemento.

    El DOM es como una criatura mitológica hecha de objetos. Cada elemento HTML hereda poderes de varias “clases ancestrales”: Node → Element → HTMLElement. Eso significa que casi todos los elementos comparten un mismo arsenal de propiedades y métodos. No memorizas etiquetas… entiendes la especie.

    Aquí tienes la tabla de referencia esencial que usarás constantemente.


    Métodos y atributos principales de un elemento DOM

    TipoNombreQué haceEjemplo
    📦 PropiedadidObtiene o cambia el id del elementoel.id = "nuevo"
    📦 PropiedadclassNameClases CSS como stringel.className = "rojo grande"
    📦 PropiedadclassListManejo avanzado de clasesel.classList.add("activo")
    📦 PropiedadtextContentTexto interno (seguro)el.textContent = "Hola"
    📦 PropiedadinnerHTMLHTML internoel.innerHTML = "<b>Hola</b>"
    📦 PropiedadchildrenElementos hijosel.children.length
    📦 PropiedadparentElementNodo padreel.parentElement
    📦 PropiedadstyleEstilos inlineel.style.color = "red"
    📦 PropiedadvalueValor (inputs, textarea)input.value
    📦 PropiedadcheckedEstado checkbox/radiocheck.checked
    📦 PropiedadsrcRuta de imagen/videoimg.src
    📦 PropiedadhrefEnlacesa.href
    📦 PropiedaddisabledHabilitar/deshabilitarbtn.disabled = true
    📦 PropiedaddatasetAcceso a data-*el.dataset.id

    Métodos de atributos

    MétodoQué haceEjemplo
    getAttribute(name)Lee atributoel.getAttribute("src")
    setAttribute(name, value)Cambia atributoel.setAttribute("alt","img")
    removeAttribute(name)Elimina atributoel.removeAttribute("disabled")
    hasAttribute(name)Comprueba atributoel.hasAttribute("id")

    Métodos de clases CSS

    MétodoQué haceEjemplo
    classList.add()Añade claseel.classList.add("rojo")
    classList.remove()Quita claseel.classList.remove("rojo")
    classList.toggle()Alterna claseel.classList.toggle("activo")
    classList.contains()Comprueba claseel.classList.contains("rojo")

    Métodos de creación y manipulación

    MétodoQué haceEjemplo
    createElement(tag)Crea nododocument.createElement("div")
    append()Inserta al finalpadre.append(hijo)
    prepend()Inserta al iniciopadre.prepend(hijo)
    remove()Elimina nodoel.remove()
    cloneNode(true)Clona nodoel.cloneNode(true)

    Métodos de búsqueda dentro de un elemento

    MétodoQué haceEjemplo
    querySelector()Primer descendienteel.querySelector("p")
    querySelectorAll()Todos los descendientesel.querySelectorAll("li")
    getElementsByClassName()Por claseel.getElementsByClassName("x")
    getElementsByTagName()Por etiquetael.getElementsByTagName("div")

    Eventos (interacción)

    MétodoQué haceEjemplo
    addEventListener()Escucha eventobtn.addEventListener("click", fn)
    removeEventListener()Quita eventobtn.removeEventListener("click", fn)

    Eventos comunes:

    • click
    • input
    • change
    • submit
    • keydown
    • mouseover

    PropiedadQué hace
    parentElementPadre
    childrenHijos elementos
    firstElementChildPrimer hijo
    lastElementChildÚltimo hijo
    nextElementSiblingHermano siguiente
    previousElementSiblingHermano anterior