Autor: ciberkaos

  • 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 ifelif 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 itemscoursesticketsbookings, 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 InjectionPDO + prepared statements SIEMPRE.
    • Sesiones:
      • session.cookie_httponly = 1session.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

  • 3.2 – EVENTOS EN JAVASCRIPT

    3.2 – EVENTOS EN JAVASCRIPT

    1. Qué es un evento

    Un evento es cualquier interacción que ocurre en la página:

    • El usuario hace clic
    • Escribe en un input
    • Envía un formulario
    • Mueve el ratón
    • Pulsa una tecla
    • La página termina de cargar

    Cuando ocurre algo → el navegador lo detecta → JavaScript puede reaccionar.

    El DOM no es solo un árbol… es un sistema reactivo.


    2. Qué es un Event Listener

    Un Event Listener es una función que espera a que ocurra un evento.

    Cuando el evento ocurre → se ejecuta la función.

    La forma moderna y correcta de escuchar eventos es:

    elemento.addEventListener("evento", funcion);

    3. Sintaxis de addEventListener

    boton.addEventListener("click", () => {
      console.log("Se hizo clic");
    });

    Parámetros:

    • Tipo de evento → "click"
    • Función a ejecutar → callback

    4. Ejemplo básico

    HTML:

    <buttonid="btn">Haz clic</button>

    JavaScript:

    const btn = document.getElementById("btn");
    
    btn.addEventListener("click", () => {
      alert("Botón pulsado");
    });

    5. El objeto event

    Cada vez que ocurre un evento, JavaScript genera un objeto llamado event que contiene información.

    btn.addEventListener("click", (event) => {
      console.log(event);
    });

    Propiedades útiles:

    PropiedadQué indica
    event.targetElemento que disparó el evento
    event.typeTipo de evento
    event.clientXPosición X del ratón
    event.clientYPosición Y del ratón
    event.keyTecla pulsada
    event.preventDefault()Evita comportamiento por defecto
    event.stopPropagation()Detiene propagación

    6. Eventos más comunes

    Click

    element.addEventListener("click", fn);

    Input (cuando el usuario escribe)

    input.addEventListener("input", fn);

    Change (cuando cambia valor)

    select.addEventListener("change", fn);

    Submit (formularios)

    form.addEventListener("submit", fn);

    Keydown (teclado)

    document.addEventListener("keydown", fn);

    Mouseover

    element.addEventListener("mouseover", fn);

    7. Evitar comportamiento por defecto

    Ejemplo: evitar que un formulario recargue la página.

    form.addEventListener("submit", (e) => {
      e.preventDefault();
      console.log("Formulario capturado");
    });

    8. Propagación de eventos (Bubbling)

    Cuando ocurre un evento, no solo afecta al elemento… sube por el árbol DOM.

    Ejemplo:

    <div id="padre">
      <button id="hijo">Click</button>
    </div>
    padre.addEventListener("click", () => console.log("Padre"));
    hijo.addEventListener("click", () => console.log("Hijo"));

    Resultado al hacer click:

    Hijo
    Padre

    Primero el elemento → luego sus padres.


    9. Detener propagación

    hijo.addEventListener("click", (e) => {
      e.stopPropagation();
    });

    10. Delegación de eventos (muy importante)

    En lugar de añadir eventos a cada elemento, se añade uno solo al contenedor.

    tabla.addEventListener("click", (e) => {
      if (e.target.classList.contains("btn-ver")) {
        const id = e.target.dataset.id;
        console.log("Ver registro", id);
      }
    });

    Ventajas:

    • Funciona con elementos creados dinámicamente
    • Mejor rendimiento
    • Menos código

    Esto es fundamental cuando el DOM se genera con JavaScript.


    11. Eliminar eventos

    function clickHandler() {
      console.log("Click");
    }
    
    btn.addEventListener("click", clickHandler);
    btn.removeEventListener("click", clickHandler);

    La función debe ser la misma referencia.


    12. Eventos en elementos creados dinámicamente

    Cuando creas un elemento con JavaScript, debes añadir el listener manualmente.

    const boton = document.createElement("button");
    boton.textContent = "Eliminar";
    
    boton.addEventListener("click", () => {
      console.log("Elemento eliminado");
    });

    Tabla de eventos en JavaScript

    🖱️ Eventos del ratón

    EventoCuándo ocurreUso típico
    clickClic normalBotones, enlaces
    dblclickDoble clicAcciones especiales
    mousedownPulsar botón ratónDrag, control
    mouseupSoltar botónFinalizar acción
    mousemoveMover ratónTracking, dibujo
    mouseoverEntrar en elementoHover
    mouseoutSalir de elementoQuitar hover
    mouseenterEntrar (sin bubbling)UI precisa
    mouseleaveSalir (sin bubbling)UI precisa
    contextmenuClic derechoMenú contextual

    ⌨️ Eventos del teclado

    EventoCuándo ocurreUso típico
    keydownTecla presionadaDetectar tecla
    keyupTecla liberadaConfirmar entrada
    keypressTecla mantenida (deprecated)Evitar usar

    Propiedad útil: event.key


    📝 Eventos de formularios / inputs

    EventoCuándo ocurreUso típico
    inputCambia el valor en tiempo realValidación viva
    changeValor confirmadoSelect, checkbox
    submitEnvío de formularioCapturar datos
    resetReset formularioRestaurar
    focusElemento recibe focoUX
    blurElemento pierde focoValidación
    invalidValidación HTML fallaFormularios

    📄 Eventos del documento / ventana

    EventoCuándo ocurreUso típico
    DOMContentLoadedDOM listoInicializar app
    loadPágina completamente cargadaRecursos
    resizeCambia tamaño ventanaResponsive
    scrollScroll de páginaLazy load
    beforeunloadAntes de salirConfirmación
    unloadPágina se cierraLimpieza

    🎯 Eventos de interacción avanzada

    EventoCuándo ocurreUso típico
    dragstartInicio dragDrag & Drop
    dragoverArrastrando encimaDrop zones
    dropSoltar elementoUpload
    touchstartTocar pantallaMóvil
    touchmoveDeslizarGestos
    touchendFin toqueMobile UI
    pointerdownInput universalMultiplataforma

    📡 Eventos de red y estado

    EventoCuándo ocurreUso típico
    onlineVuelve conexiónReintentar
    offlineSin conexiónAvisar
    errorError recursoDebug
    abortCancelaciónFetch

    🎬 Eventos multimedia

    EventoCuándo ocurreUso típico
    playReproducirVideo/audio
    pausePausarControl
    endedFinaliza mediaLoop
    timeupdateTiempo cambiaBarra progreso
    volumechangeVolumen cambiaUI

    🧠 Propiedades comunes del objeto event

    PropiedadQué indica
    event.targetElemento que disparó el evento
    event.currentTargetElemento con listener
    event.typeTipo de evento
    event.keyTecla
    event.clientX/YPosición ratón
    event.preventDefault()Evita comportamiento
    event.stopPropagation()Detiene bubbling

    Ejemplo guiado: cargar JSON por fetch al inicio y pintarlo en el DOM

    1. Al abrir la página, se ejecuta código al evento DOMContentLoaded
    2. Se llama a un endpoint con fetch()
    3. Se convierte la respuesta a JSON
    4. Se renderiza una tabla en el DOM
    5. Se manejan estados: cargando / error / sin datos

    1) HTML (estructura base)

    Crea un archivo index.html:

    <!doctype html>
    <html lang="es">
    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width,initial-scale=1" />
      <title>JSON → DOM</title>
      <style>
        body { font-family: system-ui, Arial; padding: 20px; }
        .status { padding: 10px; border-radius: 8px; margin: 10px 0; }
        .loading { background: #fff3cd; }
        .error { background: #f8d7da; }
        .ok { background: #d1e7dd; }
        table { border-collapse: collapse; width: 100%; margin-top: 10px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background: #f3f3f3; }
      </style>
    </head>
    
    <body>
      <h1>Listado de coches</h1>
    
      <div id="status" class="status loading">Cargando...</div>
    
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Marca</th>
            <th>Modelo</th>
            <th>Precio</th>
          </tr>
        </thead>
        <tbody id="rows"></tbody>
      </table>
    
      <script src="app.js"></script>
    </body>
    </html>

    Qué estamos preparando aquí

    • Un div#status para mostrar mensajes (cargando/error/vacío).
    • Un tbody#rows donde vamos a insertar filas dinámicamente.
    • Cargamos app.js al final.

    2) JavaScript (cargar y pintar)

    Crea un archivo app.js:

    // 1) Este evento se dispara cuando el DOM ya está construido.
    //    (Importante: aún puede que imágenes o recursos no hayan cargado, pero el HTML sí.)
    document.addEventListener("DOMContentLoaded", () => {
      loadCars();
    });
    
    // 2) Función principal: obtiene datos y renderiza
    async function loadCars() {
      try {
        setStatus("Cargando...", "loading");
    
        // Cambia esta URL por tu endpoint real (por ejemplo en JEE: /miApp/api/coches)
        const url = "coches.json"; // Para pruebas: archivo local con JSON
        const response = await fetch(url);
    
        // 3) Comprobamos si la respuesta HTTP es correcta
        if (!response.ok) {
          throw new Error(`HTTP ${response.status} - ${response.statusText}`);
        }
    
        // 4) Convertimos el body a JSON (array de objetos)
        const cars = await response.json();
    
        // 5) Si no hay datos, mostramos estado vacío
        if (!Array.isArray(cars) || cars.length === 0) {
          clearRows();
          setStatus("No hay datos disponibles.", "ok");
          return;
        }
    
        // 6) Pintamos los datos
        renderCars(cars);
        setStatus(`Cargados ${cars.length} registros.`, "ok");
    
      } catch (error) {
        clearRows();
        setStatus(`Error cargando datos: ${error.message}`, "error");
      }
    }
    
    // --- Renderizado ---
    
    function renderCars(cars) {
      const tbody = document.getElementById("rows");
      tbody.innerHTML = ""; // limpiamos antes de pintar
    
      // Creamos un fragmento para insertar todo de golpe (más eficiente)
      const fragment = document.createDocumentFragment();
    
      cars.forEach(car => {
        fragment.append(createRow(car));
      });
    
      tbody.append(fragment);
    }
    
    function createRow(car) {
      const tr = document.createElement("tr");
    
      const tdId = document.createElement("td");
      tdId.textContent = car.id;
    
      const tdMarca = document.createElement("td");
      tdMarca.textContent = car.marca;
    
      const tdModelo = document.createElement("td");
      tdModelo.textContent = car.modelo;
    
      const tdPrecio = document.createElement("td");
      tdPrecio.textContent = car.precio;
    
      tr.append(tdId, tdMarca, tdModelo, tdPrecio);
      return tr;
    }
    
    function clearRows() {
      document.getElementById("rows").innerHTML = "";
    }
    
    // --- Estado UI (mensajes) ---
    
    function setStatus(message, type) {
      const status = document.getElementById("status");
      status.textContent = message;
    
      // Reseteamos clases y aplicamos la nueva
      status.classList.remove("loading", "error", "ok");
      status.classList.add(type);
    }

    Qué has aprendido aquí

    • DOMContentLoaded asegura que el HTML existe antes de seleccionar nodos.
    • fetch() hace la petición.
    • response.ok comprueba si el HTTP fue bien.
    • response.json() parsea la respuesta.
    • renderCars() separa “pintar” de “cargar datos”.
    • setStatus() maneja estados.

    3) JSON de prueba (para que funcione sin backend)

    Crea coches.json en la misma carpeta:

    [
      { "id": 1, "marca": "Toyota", "modelo": "Corolla", "precio": 20000 },
      { "id": 2, "marca": "Ford", "modelo": "Focus", "precio": 18000 },
      { "id": 3, "marca": "Seat", "modelo": "Ibiza", "precio": 15500 }
    ]

    4) Nota importante (muy típica en clase)

    Si abres index.html haciendo doble clic (ruta file://), en algunos navegadores el fetch("coches.json") puede fallar por permisos.

    Soluciones sencillas para clase:

    • Servirlo desde un servidor local (ideal).
    • Si ya estás con JEE, sirve la página desde tu propio proyecto y el endpoint desde el servlet.

    5) Conectarlo a JEE

    Cuando tengas tu endpoint en JEE, normalmente será algo así:

    • URL: /miApp/api/coches
    • Devuelve JSON

    Solo cambias:

    consturl="/miApp/api/coches";

    Y listo.

  • 3.3 – DEL JSON AL DOM

    3.3 – DEL JSON AL DOM

    Este es el paso donde el frontend deja de ser estático y pasa a ser dinámico y conectado al backend.


    2. Qué es JSON

    JSON (JavaScript Object Notation) es el formato estándar para intercambiar datos entre backend y frontend.

    Ejemplo:

    [
      { "id": 1, "marca": "Toyota", "modelo": "Corolla", "precio": 20000 },
      { "id": 2, "marca": "Ford", "modelo": "Focus", "precio": 18000 }
    ]

    En JavaScript, JSON se convierte automáticamente en objetos reales:

    const coches = [
      { id: 1, marca: "Toyota", modelo: "Corolla", precio: 20000 },
      { id: 2, marca: "Ford", modelo: "Focus", precio: 18000 }
    ];

    3. Obtener datos desde el backend — fetch()

    El navegador permite hacer peticiones HTTP con fetch().

    Ejemplo básico

    fetch("/api/coches")
      .then(response => response.json())
      .then(data => {
        console.log(data);
      });

    Versión moderna con async/await:

    async function cargarDatos() {
    const response = await fetch("/api/coches");
    const data = await response.json();
    console.log(data);
    }

    cargarDatos();

    4. Patrón

    Todo frontend profesional sigue este patrón:

    1. Obtener datos
    2. Transformar datos
    3. Renderizar DOM

    Nunca mezclar todo en una sola función gigante.


    5. Separación de responsabilidades

    Estructura recomendada:

    • load() → obtiene datos del backend
    • render(data) → pinta el DOM
    • setLoading() → estado de carga
    • setError() → muestra errores

    Este patrón es equivalente a separar DAO / Service / Controller en Java.


    6. Renderizar datos en el DOM

    Supongamos este HTML:

    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Marca</th>
          <th>Modelo</th>
          <th>Precio</th>
        </tr>
      </thead>
      <tbody id="rows"></tbody>
    </table>

    Función para crear una fila

    function filaCoche(coche) {
      const tr = document.createElement("tr");
    
      const tdId = document.createElement("td");
      tdId.textContent = coche.id;
    
      const tdMarca = document.createElement("td");
      tdMarca.textContent = coche.marca;
    
      const tdModelo = document.createElement("td");
      tdModelo.textContent = coche.modelo;
    
      const tdPrecio = document.createElement("td");
      tdPrecio.textContent = coche.precio;
    
      tr.append(tdId, tdMarca, tdModelo, tdPrecio);
    
      return tr;
    }

    Función render

    function render(coches) {
      const tbody = document.getElementById("rows");
      tbody.innerHTML = "";
    
      coches.forEach(coche => {
        const fila = filaCoche(coche);
        tbody.append(fila);
      });
    }

    Conectar con fetch

    async function load() {
      try {
        setLoading(true);
    
        const response = await fetch("/api/coches");
        const coches = await response.json();
    
        render(coches);
    
      } catch (error) {
        setError("Error cargando datos");
      } finally {
        setLoading(false);
      }
    }
    
    load();

    7. Estados de la interfaz

    Toda aplicación debe manejar tres estados.

    Estado de carga

    function setLoading(estado) {
      const status = document.getElementById("status");
      status.textContent = estado ? "Cargando..." : "";
    }

    Estado de error

    function setLoading(estado) {
      const status = document.getElementById("status");
      status.textContent = estado ? "Cargando..." : "";
    }

    Estado vacío

    function setLoading(estado) {
      const status = document.getElementById("status");
      status.textContent = estado ? "Cargando..." : "";
    }

    8. Transformar datos antes de renderizar

    A veces el frontend necesita modificar los datos.

    Ejemplo:

    const cochesCaros = coches.filter(c => c.precio > 19000);
    render(cochesCaros);

    También puedes usar:

    • map() → transformar
    • filter() → filtrar
    • sort() → ordenar

    9. Eventos sobre elementos generados dinámicamente

    Si el DOM se crea con JavaScript, los eventos deben añadirse también dinámicamente.

    Ejemplo botón:

    const btn = document.createElement("button");
    btn.textContent = "Ver";
    
    btn.addEventListener("click", () => {
      console.log("Coche:", coche.id);
    });

    10. Delegación de eventos (concepto importante)

    En vez de poner un listener en cada fila:

    tbody.addEventListener("click", e => {
      if (e.target.classList.contains("ver")) {
        const id = e.target.dataset.id;
        console.log("Ver coche", id);
      }
    });

    Esto permite manejar eventos en elementos creados dinámicamente.

  • 3.4 ¿Qué es forEach() y para qué sirve?

    3.4 ¿Qué es forEach() y para qué sirve?

    forEach() es un método de los arrays que permite recorrer todos sus elementos y ejecutar una función (un bloque de código) para cada uno.

    Piensa en ello como:
    “Para cada elemento del array, haz esto…”

    Se usa mucho para:

    • Mostrar elementos por pantalla o en consola
    • Calcular totales o acumuladores
    • Transformar datos (aunque para transformar suele ser mejor map())
    • Buscar / filtrar (aunque para eso suelen ser mejores find() y filter())

    2) Sintaxis básica

    array.forEach(function(elemento) {
    // código que se ejecuta por cada elemento
    });

    Versión con función flecha (la más común hoy):

    array.forEach((elemento) => {
    // código por cada elemento
    });

    3) Parámetros de la función callback

    La función que pasas a forEach() puede recibir hasta 3 parámetros:

    array.forEach((elemento, indice, arrayCompleto) => {
    // elemento: valor actual
    // indice: posición del elemento
    // arrayCompleto: el array original
    });

    Ejemplo (elemento + índice)

    const nombres = ["Ana", "Luis", "Marta"];
    nombres.forEach((nombre, i) => {
      console.log(i, nombre);
    });

    Explicación: recorre el array y muestra el índice y el nombre de cada posición.


    4) Ejemplos típicos

    4.1 Mostrar cada elemento

    const frutas = ["manzana", "pera", "plátano"];
    frutas.forEach((fruta) => {
      console.log(fruta);
    });

    Explicación: imprime cada fruta en una línea.


    4.2 Sumar valores (acumulador)

    const precios = [10, 20, 5];
    let total = 0;
    precios.forEach((p) => {
      total += p;
    });
    console.log("Total:", total);

    Explicación: forEach() recorre los precios y vamos sumando cada uno en total.


    4.3 Construir un string con los elementos

    const letras = ["A", "B", "C"];
    let resultado = "";
    letras.forEach((l) => {
      resultado += l + "-";
    });
    console.log(resultado); // "A-B-C-"

    Explicación: concatenamos cada letra para crear una cadena final.


    4.4 Recorrer un array de objetos

    const alumnos = [
      { nombre: "Bea", nota: 8 },
      { nombre: "Dani", nota: 5 },
      { nombre: "Sara", nota: 9 }
    ];
    alumnos.forEach((a) => {
      console.log(`${a.nombre} tiene un ${a.nota}`);
    });

    Explicación: forEach() va pasando por cada objeto y accedemos a sus propiedades.


    4.5 Crear elementos en el DOM (lista HTML)

    HTML:

    <ul id="lista"></ul>

    JS:

    const tareas = ["Estudiar", "Entrenar", "Leer"];
    const ul = document.querySelector("#lista");
    tareas.forEach((tarea) => {
      const li = document.createElement("li");
      li.textContent = tarea;
      ul.appendChild(li);
    });

    Explicación: por cada tarea creamos un <li> y lo añadimos dentro del <ul>.


    5) Cosas IMPORTANTES que debes saber

    5.1 forEach() NO devuelve un array nuevo

    forEach() siempre devuelve undefined.

    const nums = [1, 2, 3];
    const r = nums.forEach((n) => n * 2);console.log(r); // undefined
    
    
    const nums = [1, 2, 3];
    const dobles = nums.map((n) => n * 2);console.log(dোবles); // [2, 4, 6]

    5.2 No puedes “parar” un forEach() con break

    Con forEach() no se usa break ni continue.

    Si necesitas parar cuando encuentras algo, mejor:

    • for...of
    • some() (para parar cuando se cumple una condición)
    • find() (para encontrar el primero que cumpla)

    Ejemplo con for...of:

    const nums = [2, 4, 7, 10];
    for (const n of nums) {
      if (n === 7) 
        console.log(n);
    }

    Explicación: aquí sí se puede detener el bucle con break.


    5.3 Puedes modificar el array (pero cuidado)

    Se puede, pero puede provocar efectos raros si borras/añades elementos mientras recorres.

    const a = [1, 2, 3];a.forEach((n, i) => {
    a[i] = n * 10;
    });console.log(a); // [10, 20, 30]

    Explicación: aquí funciona bien porque solo reemplazamos valores.


    6) forEach() vs otros métodos (mini guía)

    • forEach(): recorrer y “hacer algo” (mostrar, acumular, crear DOM…)
    • map(): crear un array nuevo transformado
    • filter(): crear un array nuevo filtrado
    • find(): obtener el primer elemento que cumpla
    • some(): comprobar si alguno cumple (y se puede “parar” antes)
    • every(): comprobar si todos cumplen

    7) Trabajando con el DOM

    Vamos a hacer algo muy realista: tienes un array con nombres en JavaScript y quieres que automáticamente se construya una lista <ul> en el HTML usando forEach().

    Dado este array:

    const nombres = ["Ana", "Luis", "Marta", "Carlos", "Beatriz"];

    Queremos generar dinámicamente esto en el HTML:

    <ul>
    <li>Ana</li>
    <li>Luis</li>
    <li>Marta</li>
    <li>Carlos</li>
    <li>Beatriz</li>
    </ul>

    Sin escribir los <li> a mano. Que lo haga JavaScript.


    HTML

    <!DOCTYPE html>
    <html lang="es">
    <head>
    <meta charset="UTF-8">
    <title>Ejemplo forEach</title>
    </head>
    <body> <h2>Lista de alumnos</h2>
    <ul id="listaNombres"></ul> <script src="script.js"></script>
    </body>
    </html>

    Observa el detalle importante:

    <ul id="listaNombres"></ul>

    Ese id es el punto de anclaje. Ahí vamos a insertar los <li>.


    JavaScript (script.js)

    // Array de nombres
    const nombres = ["Ana", "Luis", "Marta", "Carlos", "Beatriz"];
    
    // Seleccionamos el <ul>
    const ul = document.getElementById("listaNombres");
    
    // Recorremos el array
    nombres.forEach((nombre) => {
    
      // 1. Crear el elemento <li>
      const li = document.createElement("li");
    
      // 2. Añadir el texto
      li.textContent = nombre;
    
      // 3. Insertarlo dentro del <ul>
      ul.appendChild(li);
    
    });

    ¿Qué está pasando exactamente?

    1. forEach() recorre cada nombre del array.
    2. En cada vuelta:
      • Creamos un <li>
      • Le ponemos el texto del nombre actual
      • Lo añadimos al <ul>

    Es como una pequeña fábrica automática de etiquetas HTML.


    Versión con índice incluido

    Si quieres que aparezca numerado:

    nombres.forEach((nombre, indice) => {  
    const li = document.createElement("li");
      li.textContent = (indice + 1) + ". " + nombre;  
    ul.appendChild(li);
    });

    Resultado:

    1. Ana
    2. Luis
    3. Marta
    4. Carlos
    5. Beatriz

    ⚠️ Error común de principiante

    ul.innerHTML += "<li>" + nombre + "</li>";

    Funciona… pero:

    • Es menos eficiente
    • Puede romper eventos
    • Es menos seguro si el contenido viene del usuario

    Crear elementos con createElement() es la forma correcta y profesional.


    Idea para subir un poco el nivel

    Puedes hacer que si el nombre empieza por “B” se pinte en rojo:

    nombres.forEach((nombre) => {  
    const li = document.createElement("li");
      li.textContent = nombre;  
    if (nombre.startsWith("B")) {
        li.style.color = "red";
      }  
    ul.appendChild(li);});

    Ahora ya no solo recorremos. También tomamos decisiones dentro del recorrido.

    Eso ya es pensar como programador y no como copiador de código.


    Si quieres, podemos convertir esto en una pequeña práctica gamificada para tus alumnos tipo “constructor automático de lista dinámica”, donde luego tengan que añadir botones para borrar nombres o añadir nuevos desde un input. Ahí empiezan a entender realmente el poder del DOM y los arrays trabajando juntos.

    8. Ejemplo con un JSON

    amos a hacer el caso típico:

    Tenemos un JSON con productos y queremos generar automáticamente la ficha de cada producto en HTML.

    Nada de copiar y pegar tarjetas manualmente. Que la máquina trabaje.


    1️⃣ El JSON de productos

    Imaginemos que tenemos esto en un archivo llamado productos.json:

    [
    {
    "id": 1,
    "nombre": "Portátil Gaming",
    "precio": 1299,
    "imagen": "https://via.placeholder.com/200",
    "descripcion": "Portátil potente para desarrollo y juegos."
    },
    {
    "id": 2,
    "nombre": "Teclado Mecánico",
    "precio": 89,
    "imagen": "https://via.placeholder.com/200",
    "descripcion": "Teclado con switches mecánicos retroiluminado."
    },
    {
    "id": 3,
    "nombre": "Ratón Inalámbrico",
    "precio": 49,
    "imagen": "https://via.placeholder.com/200",
    "descripcion": "Ratón ergonómico con batería recargable."
    }
    ]

    Es simplemente un array de objetos. Nada místico.


    2️⃣ HTML

    <!DOCTYPE html>
    <html lang="es">
    <head>
    <meta charset="UTF-8">
    <title>Productos</title>
    <style>
    .contenedor {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
    } .producto {
    border: 1px solid #ccc;
    padding: 15px;
    width: 220px;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    } .producto img {
    width: 100%;
    } .precio {
    font-weight: bold;
    color: green;
    }
    </style>
    </head>
    <body> <h2>Catálogo de Productos</h2>
    <div id="contenedorProductos" class="contenedor"></div> <script src="script.js"></script>
    </body>
    </html>

    Observa el div vacío:

    <div id="contenedorProductos"></div>

    Ahí vamos a inyectar todas las fichas dinámicamente.


    3️⃣ JavaScript (leer JSON y generar fichas)

    // Seleccionamos el contenedor
    const contenedor = document.getElementById("contenedorProductos");
    
    // Leer el JSON
    fetch("productos.json")
      .then(response => response.json())
      .then(productos => {
    
        productos.forEach(producto => {
    
          // Crear tarjeta
          const tarjeta = document.createElement("div");
          tarjeta.classList.add("producto");
    
          tarjeta.innerHTML = `
            <img src="${producto.imagen}" alt="${producto.nombre}">
            <h3>${producto.nombre}</h3>
            <p>${producto.descripcion}</p>
            <p class="precio">${producto.precio} €</p>
            <button>Comprar</button>
          `;
    
          contenedor.appendChild(tarjeta);
    
        });
    
      })
      .catch(error => {
        console.error("Error al cargar los productos:", error);
      });

    ¿Qué está pasando aquí?

    1. fetch() pide el archivo JSON.
    2. .json() convierte la respuesta en objeto JavaScript.
    3. forEach() recorre cada producto.
    4. Para cada producto:
      • Creamos un div
      • Insertamos su contenido dinámicamente
      • Lo añadimos al contenedor

    Es literalmente una fábrica automática de fichas.

    Si mañana el JSON tiene 200 productos, se generarán 200 tarjetas.
    Eso es separar datos de presentación. Arquitectura básica pero poderosa.


    Ejercicios propuestos

    Ejercicio 1

    Dado el array:

    const nums = [3, 6, 9, 12];

    Muestra en consola cada número y su índice.


    Ejercicio 2

    Calcula la suma total de:

    const carrito = [12.99, 5.5, 3.25, 20];

    Ejercicio 3

    Dado:

    const alumnos = [
    { nombre: "Ana", nota: 4 },
    { nombre: "Luis", nota: 7 },
    { nombre: "Marta", nota: 9 }
    ];

    Muestra solo un mensaje por alumno con “APTO” si la nota es >= 5, y “NO APTO” si es < 5.


    Ejercicio 4 (DOM)

    Crea una lista <ul> en HTML y rellénala con un array de strings usando forEach().


    Métodos y propiedades utilizados

    Método / PropiedadTipo¿Dónde se usa?¿Para qué sirve?¿Devuelve algo?
    document.getElementById()DOMdocument.getElementById("contenedorProductos")Selecciona un elemento del HTML por su idEl elemento encontrado
    fetch()Web APIfetch("productos.json")Realiza una petición HTTP para obtener datosUna Promise
    .then()Promisefetch(...).then(...)Ejecuta código cuando la promesa se resuelveOtra Promise
    .json()Responseresponse.json()Convierte la respuesta HTTP en objeto JavaScriptUna Promise con el JSON convertido
    .forEach()Arrayproductos.forEach(...)Recorre cada elemento del arrayundefined
    document.createElement()DOMdocument.createElement("div")Crea un nuevo elemento HTMLEl elemento creado
    .classList.add()DOMtarjeta.classList.add("producto")Añade una clase CSS a un elementoundefined
    .appendChild()DOMcontenedor.appendChild(tarjeta)Inserta un elemento dentro de otroEl nodo añadido
    .append()DOMtarjeta.append(...)Añade uno o varios elementos hijosundefined
    .textContentPropiedad DOMtitulo.textContent = producto.nombreAsigna texto a un elementoNo devuelve valor (es asignación)
    .innerHTMLPropiedad DOMtarjeta.innerHTML = \…«Inserta HTML directamente dentro de un elementoNo devuelve valor (es asignación)
    .catch()Promise.catch(error => {...})Captura errores en promesasUna Promise

  • 1.1  – ¿Qué es JavaScript?

    1.1 – ¿Qué es JavaScript?

    A diferencia de los lenguajes como HTML o CSS, que podemos definir como lenguajes de marcas, JavaScript es un lenguaje de programación que usaremos para añadir interactividad a las páginas web. El código JavaScript puede estar dentro del propio archivo HTML utilizando las etiquetas script o mediante archivos externos con extensión .js

    JavaScript es un lenguaje interpretado y no genera ningún tipo de código compilado, sino que éste se interpreta en el navegador una vez que se descarga la página que lo contiene. Podemos usar cualquier editor de texto como el bloc de notas de Windows. Aunque sí es altamente aconsejable usar editores de código para facilitarnos el trabajo. La mezcla de JavaScript con HTML se suele conocer con el nombre de HTML Dinámico o DHTML.

    En la actualidad JavaScript ya no es opcional en una página web, dado que se exige que tengan dinamismos y una alta usabilidad. En HTML5 se considera que este lenguaje ya forma parte de la norma.

    Además, este lenguaje se está volviendo más popular entre el desarrollo web, y aunque su finalidad inicial es interactuar con la parte de cliente, dota de dinamismo al documento HTML. Existen ya, muchos Frameworks que utilizan la sintaxis JavaScript para trabajar del lado del servidor, como node.js, VUE, TypeScript y muchos más.

    Integración en Páginas HTML

    La integración de JavaScript y HTML es muy flexible, ya que existen al menos tres formas para incluir código JavaScript en las páginas web.

    JavaScript en el mismo documento con la etiqueta <script>

    <html>
    <head>
    <title>JavaScript en el propio documento</title>
     <script type=”text/javascript”>
             alert(“Esto es un mensaje en una ventana del navegador”);
    </ script>
    </head>
    <body>
    <p>Un párrafo de texto</p>
    </body>
    </html>

    Como se puede apreciar en el código con la etiqueta <script> indicamos la zona que va a contener la codificación JavaScript. Aunque lo más aconsejable es que este bloque de código JS se realice dentro del head de la página no siempre es imprescindible, pues en algunos casos nos veremos obligados a ponerlas dentro del body.

    JavaScript en documento externo

    Otra forma de incluir código JS en nuestras páginas, es en un documento externo con la extensión .js.

    En estos archivos se suelen poner las funciones, dado que al tratarse de un archivo externo, podemos reutilizarlo enlazándolo desde otros documentos.

    <html>
    <head>
    <title>Ejemplo de código JavaScript en el propio documento</title>
    <script type=”text/javascript” src=”archivoJavascript.js”></ script>
    </head>
    <body>
    <p>Un párrafo de texto.</p>
    </body>
    </html>

    Además del atributo type, este método requiere definir el atributo src, que es el que indica la URL correspondiente al archivo JavaScript que se quiere enlazar.

    *IMPORTANTE: Debemos tener en cuenta que no podremos usar el mismo bloque de script para llamar a un archivo e incluir código al mismo tiempo.

    Si utilizamos la etiqueta script para enlazar a un archivo y ponemos código dentro, este no se ejecutará.

    JavaScript en elemento HTML

    Este último método es el menos utilizado hoy en día y no es bueno abusar de su uso dado que resulta más complicado efectuar correcciones y/o cambios.

    La tendencia actual en la creación de páginas HTML, es dejar lo más limpio este código, separando tanto elementos CSS como JS fuera del bloque principal.

    <html>
    <head>
    <title>JavaScript en el propio documento</title>
    </head>
    <body>
    <p onclick=”alert(‘Un mensaje de prueba’)”>Un párrafo de texto.</p>
    </body>
    </html>

    Mediante atributos HTML, como los manejadores de eventos (onclick, onblur…), podemos insertar directamente código JS.

    Header

    Aunque podemos casi afirmar que cualquiera de nuestras páginas será interpretada con cualquier navegador, dado que está muy extendido, existe una etiqueta que nos da la opción de lanzar un mensaje o acción si se diese el caso de que no lo soportara. La etiqueta noscript.

    <body>
    <noscript>
    <p>La página que estás viendo requiere JavaScript. <br />
    Si lo has deshabilitado, por favor vuelve a activarlo.</p>
    </noscript>
    ....

    Sintaxis JavaScript

    A diferencia de los lenguajes anteriores JavaScript es mucho más estricto en la forma de escribir el código, haciendo más fácil que cometamos errores de escritura. A continuación enumeramos las reglas más importantes.

    • No se tienen en cuenta las líneas ni espacios en blanco.
    • Se distinguen las mayúsculas y minúsculas.
    • No se define el tipo de las variables: JavaScript es débilmente tipado por lo que una variable podrá pasar de contener un número a un texto o cualquier otro tipo de dato, como veremos más adelante.
    • Las sentencias no tienen que terminar con punto y coma (;): en la mayoría de lenguajes de programación, es obligatorio terminar cada sentencia con el carácter (;). Aunque no sea obligatorio se aconseja su uso para así adquirir buenas costumbres.
    • Se pueden incluir comentarios: los comentarios se utilizan para añadir información en el código fuente del programa. Aunque el contenido de los comentarios no se visualiza por pantalla, sí que se envía al navegador del usuario junto con el resto del script, por lo que es necesario extremar las precauciones sobre la información incluida en los comentarios.
    • Los nombres de variables siempre deben empezar por una letra del alfabeto (mayúscula o minúscula), un subrayado (_) o un signo de dólar ($), y solo pueden constar de letras, números y subrayados. Por supuesto, no pueden incluirse espacios en el nombre de una variable. Como veremos más adelante, determinados tipos se diferencia por estar en mayúsculas, por lo que se aconseja escribir siempre en minúsculas o escritura «Camello» (CamelCase)*.

    CamelCase

    CamelCase es un estilo de escritura que se aplica a frases o palabras compuestas. El nombre se debe a que las mayúsculas a lo largo de una palabra en CamelCase se asemejan a las jorobas de un camello. El nombre CamelCase se podría traducir como Mayúsculas/Minúsculas Camello. El término case se traduce como “caja tipográfica”, que a su vez implica si una letra es mayúscula o minúscula y tiene su origen en la disposición de los tipos móviles, en casilleros o cajas.

    Existen dos tipos de CamelCase:

    UpperCamelCase, cuando la primera letra de cada una de las palabras es mayúscula.

    Ejemplo: EjemploDeUpperCamelCase.

    lowerCamelCase, igual que la anterior con la excepción de que la primera letra es minúscula. Ejemplo: ejemploDeLowerCamelCase.

    <body>
    <noscript>
    <p>La página que estás viendo requiere JavaScript. <br />
    Si lo has deshabilitado, por favor vuelve a activarlo.</p>
    </noscript>
    ....

    .

    Ejercicios

    Para practicar lo aprendido en esta lección, vamos a hacer uso de una función de JavaScript, llamada alert.

    Dicha función, nos permite lanzar una ventana emergente del navegador con un mensaje de tipo texto que insertaremos como parámetro.

    Ejemplo función alert()


                    alert(«Hola Mundo);
                    …

    Ejercicio 1

    Crea un archivo HTML y dentro del bloque de las etiquetas head, inserta el código del ejemplo del alert, mostrado.

    Ejercicio 2

    Inserta el siguiente código en un archivo con extensión .js y pega el siguiente código. Abre el documento y mira qué pasa.

    window.onload = function(){
    alert("Soy un código de JavaScript dentro de un archivo externo al documento HTML"); 
    }