0% encontró este documento útil (0 votos)
3 vistas389 páginas

Manual Desarrollo Web JavaTemporal

El documento describe un curso de desarrollo web con Java, comenzando con una introducción a la arquitectura web y el protocolo HTTP. Se detallan los componentes esenciales de una aplicación Java web, incluyendo Servlets y JSP, así como la instalación del entorno de desarrollo necesario. Al final, se guía al estudiante para crear y ejecutar su primera aplicación web simple utilizando un Servlet.

Cargado por

yyurima
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
3 vistas389 páginas

Manual Desarrollo Web JavaTemporal

El documento describe un curso de desarrollo web con Java, comenzando con una introducción a la arquitectura web y el protocolo HTTP. Se detallan los componentes esenciales de una aplicación Java web, incluyendo Servlets y JSP, así como la instalación del entorno de desarrollo necesario. Al final, se guía al estudiante para crear y ejecutar su primera aplicación web simple utilizando un Servlet.

Cargado por

yyurima
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Docente : Doninger Casanova Casanova

IFCD0182 — Desarrollo Web con Java


IFCD0182 — Desarrollo Web con Java
IFCD0182 — Desarrollo Web con Java

MÓDULO 1
Introducción al Desarrollo Web con Java
UNIDAD 1

¿Cómo funciona la web con Java?


Arquitectura web, el protocolo HTTP y cómo Java procesa las peticiones

Duración estimada 6 horas (teoría + prácticas)

Módulo 1 — Introducción al Desarrollo Web con Java

Nivel de entrada Sin experiencia previa en desarrollo web con Java

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Cómo funciona la web, qué papel juega Java y cómo el servidor procesa las
peticiones del navegador

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué ocurre desde que escribes una dirección web hasta que ves la página en
pantalla.
• Diferenciar cliente y servidor, y entender el papel de Java en ese proceso.
• Describir el protocolo HTTP y sus métodos principales (GET, POST, PUT, DELETE).
• Reconocer los componentes de una aplicación Java Web (Servlet, JSP, Tomcat).
• Instalar y configurar el entorno de desarrollo completo.
• Crear y ejecutar tu primera aplicación Java Web en local.
IFCD0182 — Desarrollo Web con Java

1. Introducción: ¿De qué va esta unidad?

Antes de escribir la primera línea de código, necesitas entender el terreno en el que vas a trabajar. Esta
unidad responde a la pregunta más básica y más importante del curso:

👁 FÍJATE
¿Qué ocurre exactamente cuando alguien abre una página web?

La respuesta no es magia. Es un proceso concreto, con pasos definidos, en el que Java puede jugar un papel
central. Entender ese proceso es lo que distingue a alguien que simplemente copia código de alguien que
entiende lo que está construyendo.

En esta unidad verás cómo funciona la web por dentro, qué es un servidor de aplicaciones, qué significa que
Java se ejecute en el servidor y cómo se conectan todas las piezas. Al final instalarás el entorno y harás
funcionar tu primera aplicación.

💼 EN EL TRABAJO REAL
Por qué esto importa en el mercado laboral
Cualquier empresa que te contrate como desarrollador Java esperará que entiendas cómo fluye una
petición web de principio a fin. En una entrevista técnica es una de las primeras preguntas. En el día
a día, es lo que te permite depurar errores, diseñar mejor tu código y comunicarte con el resto del
equipo.
IFCD0182 — Desarrollo Web con Java

2. Cómo funciona la web: el viaje de una petición

2.1 El modelo cliente-servidor


Toda aplicación web funciona con dos actores principales: el cliente y el servidor.

Actor Qué es Qué hace Ejemplos

Cliente El programa que usa el Envía peticiones y muestra Chrome, Firefox, Edge,
usuario final las respuestas aplicación móvil

Servidor El ordenador (o conjunto de Recibe, procesa y responde Tomcat, Nginx, servidor en


ellos) que procesa las la nube
peticiones

Esta separación es fundamental. El cliente nunca accede directamente a la base de datos ni al código Java.
Todo pasa por el servidor, que actúa como intermediario. Esto es lo que hace que las aplicaciones web sean
seguras y escalables.

👁 FÍJATE
Una analogía que funciona:
Piensa en un restaurante. El cliente (tú) hace un pedido al camarero. El camarero (servidor) lleva ese
pedido a la cocina, la cocina lo prepara y el camarero te trae el resultado. Tú nunca entras a la
cocina. La cocina es tu base de datos y tu código Java.

2.2 El protocolo HTTP: el idioma de la web


Para que cliente y servidor se entiendan, necesitan hablar el mismo idioma. Ese idioma es HTTP (HyperText
Transfer Protocol). Cuando tu navegador quiere algo del servidor, le manda un mensaje llamado petición
HTTP. El servidor responde con otro mensaje llamado respuesta HTTP.

Los métodos HTTP principales

Cada petición HTTP indica qué tipo de acción quiere realizar. Los métodos más importantes son:

Método ¿Qué significa? Cuándo se usa Ejemplo real

GET "Dame información" Leer datos sin modificarlos Ver una lista de productos

POST "Toma estos datos" Crear algo nuevo o enviar un Registrarse en una web
formulario

PUT "Reemplaza esto" Actualizar un dato existente Editar tu perfil


completamente

PATCH "Modifica solo esto" Actualizar solo parte de un Cambiar solo tu contraseña
dato

DELETE "Elimina esto" Borrar un recurso Eliminar una cuenta


IFCD0182 — Desarrollo Web con Java

Los códigos de respuesta HTTP


El servidor siempre responde con un número que indica el resultado. Hay que conocerlos porque aparecerán
constantemente:

Código Significado Cuándo aparece

200 OK Todo fue bien Petición procesada correctamente

201 Created Recurso creado Tras un POST que crea algo nuevo

400 Bad Request La petición tiene errores Datos mal enviados por el cliente

401 Unauthorized No estás identificado Intentas acceder sin haber iniciado sesión

403 Forbidden No tienes permiso Estás identificado pero sin acceso

404 Not Found No existe lo que pides URL incorrecta o recurso eliminado

500 Internal Server El servidor falló Error en el código del servidor


Error

🤖 CON AYUDA DE LA IA
Practica con IA — Comprende HTTP sin memorizar
Abre Claude o ChatGPT y escribe este prompt:

"Explícame qué ocurre técnicamente, paso a paso, cuando escribo [Link] en mi navegador
y pulso Enter. Usa términos de HTTP y explica cada fase como si yo fuera desarrollador junior."
Compara la respuesta con lo que has leído aquí. Si algo no coincide o no entiendes, vuelve a
preguntar con más detalle. Usar la IA para contrastar y ampliar conceptos —no para copiar
respuestas— es una habilidad muy valorada en el sector.
IFCD0182 — Desarrollo Web con Java

2.3 El viaje completo de una petición web con Java


Ahora veamos exactamente qué ocurre cuando alguien visita una aplicación web hecha con Java:

El usuario escribe la URL o hace clic en un enlace


El navegador analiza la URL para saber a qué servidor tiene que conectarse y qué recurso está
1 pidiendo.

Ejemplo: [Link] → servidor [Link], recurso /productos/12

El navegador construye una petición HTTP y la envía


La petición incluye: el método (GET, POST...), la URL, cabeceras con información extra, y a
2
veces un cuerpo con datos (por ejemplo, en un formulario).

El servidor recibe la petición — aquí entra Java


El servidor de aplicaciones Java (Tomcat, en nuestro caso) recibe la petición. Tomcat la analiza
3
y decide qué componente Java debe procesarla. Ese componente se llama Servlet.

El Servlet procesa la lógica


El Servlet es código Java puro. Puede consultar una base de datos, hacer cálculos, llamar a
4
otros servicios... hace todo el trabajo de negocio.

Se genera la respuesta
El Servlet construye la respuesta: puede ser HTML generado dinámicamente (mediante JSP),
5
datos en formato JSON para una API, un archivo PDF, una imagen...

El navegador recibe y muestra la respuesta


El navegador interpreta el HTML/CSS/JS recibido y lo muestra al usuario. El ciclo se ha
6
completado. Todo en milisegundos.

ℹ NOTA
Este ciclo se llama ciclo petición-respuesta (request-response). Cada vez que el usuario hace algo en
la web que implica datos del servidor, este ciclo se repite. Tu trabajo como desarrollador Java Web
es programar lo que ocurre en los pasos 3, 4 y 5.
IFCD0182 — Desarrollo Web con Java

3. Los componentes de una aplicación Java Web

Una aplicación web hecha con Java está formada por varios componentes que trabajan juntos. Conviene
conocerlos antes de instalar nada:

Componente Qué es Para qué sirve Dónde lo verás en el curso

JDK (Java Development El kit completo para Compilar y ejecutar Siempre, en todo el curso
Kit) desarrollar en Java código Java

Tomcat Servidor de aplicaciones Ejecutar Servlets y JSPs Módulos 1, 2 y 3


ligero

Servlet Clase Java que responde Procesar la lógica de Módulos 1 y 2


a peticiones HTTP negocio del servidor

JSP (JavaServer Pages) Páginas HTML con Generar HTML dinámico Módulo 1
código Java incrustado

Maven Gestor de dependencias Descargar librerías y Todo el curso


y construcción compilar el proyecto

IntelliJ IDEA Entorno de desarrollo Escribir, ejecutar y Todo el curso


(IDE) depurar código

3.1 Apache Tomcat: tu primer servidor Java


Tomcat es el servidor donde vas a desplegar tus aplicaciones durante el curso. No es el único servidor Java,
pero es el más usado para aprender y muy habitual en empresas pequeñas y medianas.

Cuando hablamos de "desplegar" una aplicación, significa copiarla al servidor para que Tomcat la pueda
ejecutar cuando lleguen peticiones. El resultado final es una aplicación accesible desde el navegador.

⚙ CÓMO FUNCIONA
Cómo trabaja Tomcat internamente
• Escucha en un puerto de red (por defecto el 8080).
• Cuando llega una petición HTTP, la analiza y busca qué Servlet debe procesarla.
• Crea una instancia del Servlet (si no existe ya) y le pasa la petición.
• El Servlet devuelve la respuesta y Tomcat la envía al cliente.
• Tomcat gestiona múltiples peticiones simultáneas usando hilos (threads).
IFCD0182 — Desarrollo Web con Java

3.2 El Servlet: el corazón del procesamiento


Un Servlet es simplemente una clase Java que extiende HttpServlet. Cuando Tomcat recibe una petición,
busca qué Servlet debe manejarla (basándose en la URL) y ejecuta el método correspondiente.

Cada método HTTP tiene su correspondiente método Java:

Petición HTTP Método Java que se ejecuta Qué debes implementar ahí

GET doGet(request, response) Lógica para leer y devolver datos

POST doPost(request, response) Lógica para crear o procesar datos


enviados

PUT doPut(request, response) Lógica para actualizar datos

DELETE doDelete(request, response) Lógica para eliminar datos


IFCD0182 — Desarrollo Web con Java

4. Instalación del entorno de desarrollo

Antes de poder programar, necesitas tener todo el software instalado y funcionando. Sigue estos pasos en
orden. Si algo no funciona, no pases al siguiente hasta resolverlo.

Paso 1 — Instalar el JDK 17


El JDK (Java Development Kit) incluye todo lo necesario para compilar y ejecutar código Java. Usaremos la
versión 17, que es LTS (soporte a largo plazo) y la más extendida actualmente en el mercado laboral.

1. Abre tu navegador y ve a [Link]


2. Descarga el instalador de Temurin 17 LTS para tu sistema operativo (Windows/macOS/Linux).
3. Ejecuta el instalador y acepta las opciones por defecto. Importante: marca la opción "Set
JAVA_HOME" si te la ofrece.
4. Abre una terminal (cmd en Windows, Terminal en macOS/Linux) y verifica la instalación:

java -version

Deberías ver algo similar a:

openjdk version "17.0.x" 2024-xx-xx

⚠ ATENCIÓN
Si el comando java -version no funciona, la instalación no se ha completado correctamente o Java no
está en el PATH del sistema. Consulta al formador antes de continuar.

Paso 2 — Instalar IntelliJ IDEA Community Edition


IntelliJ IDEA es el IDE más usado por desarrolladores Java en el mercado. La edición Community es gratuita y
tiene todo lo que necesitamos.

5. Ve a [Link] y descarga la versión Community.


6. Instala con las opciones por defecto. Marca "Create Desktop Shortcut" y "Add to PATH".
7. Al abrirlo por primera vez, selecciona el tema de colores que prefieras y continúa.
IFCD0182 — Desarrollo Web con Java

Paso 3 — Instalar Apache Tomcat 10


Tomcat 10 es compatible con Jakarta EE 9+, que es el estándar actual del sector.

8. Ve a [Link] y descarga la versión 10.x → "Binary Distributions" → "Core" → zip.


9. Descomprime el archivo en una carpeta sencilla, por ejemplo C:\Tomcat10 (Windows) o ~/tomcat10
(macOS/Linux).
10. En Linux/macOS, da permisos de ejecución a los scripts:
chmod +x ~/tomcat10/bin/*.sh
11. Para verificar que funciona, ejecuta el script de inicio:
# Windows
C:\Tomcat10\bin\[Link]

# macOS / Linux
~/tomcat10/bin/[Link]
12. Abre el navegador y ve a [Link] Si ves la página de bienvenida de Tomcat, todo está
correcto.

Paso 4 — Configurar Tomcat en IntelliJ IDEA


13. En IntelliJ IDEA, ve a File → Settings → Build, Execution, Deployment → Application Servers.
14. Haz clic en el botón + → Tomcat Server.
15. En "Tomcat Home", selecciona la carpeta donde descomprimiste Tomcat.
16. Haz clic en OK. IntelliJ ya puede usar Tomcat para ejecutar tus proyectos.

✔ BUENAS PRÁCTICAS
Con esto tienes el entorno completo: JDK 17 + IntelliJ IDEA + Tomcat 10. Es exactamente la
combinación que se usa en muchas empresas de desarrollo Java.
IFCD0182 — Desarrollo Web con Java

5. Tu primera aplicación Java Web

Vamos a crear la aplicación web más sencilla posible: un Servlet que responde a una petición GET con un
mensaje. Es simple a propósito: el objetivo es ver todo el ciclo funcionando por primera vez.

5.1 Crear el proyecto en IntelliJ IDEA


17. File → New → Project.
18. Selecciona "Java Enterprise" (o "Jakarta EE" en versiones recientes).
19. Elige: Project SDK → JDK 17, Application Server → el Tomcat que configuraste.
20. En "Dependencies", marca "Servlet". Haz clic en Next.
21. Nombra el proyecto HolaMundo y haz clic en Finish.

5.2 Crear el Servlet


IntelliJ habrá creado la estructura básica del proyecto. Ahora crea el Servlet:

22. En el panel de la izquierda, haz clic derecho sobre la carpeta src/main/java → New → Java Class.
23. Nómbrala HolaMundoServlet.
24. Escribe el siguiente código:

package [Link];

// Importamos las clases necesarias de Jakarta EE (sustituyó a javax en versiones


modernas)
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];

// @WebServlet indica a Tomcat que esta clase responde a la URL /hola


@WebServlet("/hola")
public class HolaMundoServlet extends HttpServlet {

// Este método se ejecuta cuando llega una petición GET a /hola


@Override
IFCD0182 — Desarrollo Web con Java
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {

// Indicamos al navegador que la respuesta será texto HTML en UTF-8


[Link]("text/html; charset=UTF-8");

// Obtenemos el flujo de salida para escribir la respuesta


PrintWriter out = [Link]();

// Generamos el HTML que verá el usuario


[Link]("<!DOCTYPE html>");
[Link]("<html lang='es'>");
[Link](" <head><title>Mi primera app Java Web</title></head>");
[Link](" <body>");
[Link](" <h1>¡Hola desde Java!</h1>");
[Link](" <p>Este texto lo generó un Servlet en el servidor.</p>");
[Link](" </body>");
[Link]("</html>");
}
}

5.3 Ejecutar y probar la aplicación


25. En IntelliJ, haz clic en Run → Edit Configurations.
26. Selecciona el Tomcat que configuraste y en "Deployment" añade el artefacto de tu proyecto (war
exploded).
27. Haz clic en el botón Run (triángulo verde). IntelliJ compilará el proyecto y arrancará Tomcat.
28. Cuando en la consola veas "Server startup in X ms", abre el navegador.
29. Ve a la URL: [Link]

✔ BUENAS PRÁCTICAS
Si ves "¡Hola desde Java!" en el navegador, enhorabuena. Acabas de crear y ejecutar tu primera
aplicación Java Web. El texto que ves lo generó el Servlet en el servidor y lo envió a tu navegador.

5.4 Entender qué ha ocurrido


Repasemos el ciclo completo con lo que acabas de hacer:

• Tu navegador envió una petición GET a [Link]


IFCD0182 — Desarrollo Web con Java

• Tomcat recibió esa petición y buscó qué Servlet tiene mapeada la URL /hola.
• Encontró HolaMundoServlet (gracias a la anotación @WebServlet).
• Tomcat llamó al método doGet() de ese Servlet pasándole el request y el response.
• Tu código Java generó HTML y lo escribió en el response.
• Tomcat envió ese HTML al navegador, que lo renderizó y mostró el texto.

🤖 CON AYUDA DE LA IA
Experimenta con IA — Modifica y comprende el código
Una vez que el Servlet funciona, prueba este prompt en Claude o ChatGPT:

"Tengo este Servlet Java funcionando [pega el código]. Explícame qué hace cada línea y qué pasaría
si elimino el setContentType. También dime cómo puedo hacer que muestre la fecha y hora actuales
en la respuesta."
Implementa los cambios sugeridos por la IA en tu proyecto y comprueba que funcionan. Si algo falla,
pega el error en la IA y pídele que te explique qué lo causó.

Importante: no copies código sin leerlo. Pide siempre que te lo explique línea a línea.
IFCD0182 — Desarrollo Web con Java

6. Errores frecuentes y cómo resolverlos

Los siguientes errores son los más habituales cuando se empieza con Java Web. Aprende a reconocerlos para
no perder tiempo:

Error / Síntoma Causa más probable Cómo resolverlo

java -version no funciona en la Java no está instalado o no está Reinstala el JDK y asegúrate de marcar la
terminal en el PATH opción de PATH en el instalador

[Link] no Tomcat no está arrancado o usa Comprueba la consola de IntelliJ. Verifica


carga otro puerto que el puerto 8080 no lo usa otro
programa

Error 404 al ir a /hola La URL es incorrecta o el proyecto Revisa la anotación @WebServlet y el


no está desplegado nombre del artefacto en la configuración
de Tomcat

Error de compilación: cannot La dependencia de Servlet no está Añade la dependencia [Link]-api


find symbol HttpServlet en el [Link] al archivo [Link] de Maven

El navegador muestra el Falta [Link] o Asegúrate de que la primera línea del


código fuente en lugar del está mal configurado doGet sea
HTML renderizado [Link]("text/html")

ClassNotFoundException al El .war o el .class no están Haz Build → Rebuild Project en IntelliJ y


arrancar Tomcat compilados correctamente vuelve a desplegar

⚠ ATENCIÓN
El error más común de todos:
Muchos estudiantes pasan a la siguiente parte sin haber resuelto un error anterior, acumulando
problemas. Si algo no funciona, detente y resuélvelo. Un entorno roto es peor que no tener entorno.
IFCD0182 — Desarrollo Web con Java

7. Buenas prácticas actuales

Estas son las prácticas que distinguen el código de un profesional del código de alguien que solo sabe que
funciona:

✔ BUENAS PRÁCTICAS
Sigue estas prácticas desde el principio:
• Usa siempre UTF-8: Llama a [Link]("text/html; charset=UTF-8") en
todos tus Servlets para evitar problemas con caracteres especiales del español.
• No generes HTML directamente en el Servlet: Es lo que haremos al principio para
aprender, pero en producción el HTML va en JSP o en templates. El Servlet debe
ocuparse solo de la lógica.
• Gestiona siempre las excepciones: No dejes que los errores lleguen sin capturar al
usuario. Usa try-catch o throws con criterio.
• Nombres claros en las URLs: /productos, /usuarios, /pedidos. Evita /pagina1, /nuevo2,
/hacerCosa.
• Cierra los recursos: Los PrintWriter y otros objetos de I/O deben cerrarse. IntelliJ te
avisará si lo olvidas.
• Un Servlet, una responsabilidad: No hagas un único Servlet gigante que haga de todo.
Cada Servlet debe gestionar un recurso o funcionalidad concreta.

ℹ NOTA
Nota sobre versiones: verás mucho código en internet que usa [Link] en lugar de
[Link]. Ambas hacen lo mismo, pero [Link] es la versión moderna (Tomcat 10+). Si
sigues ejemplos de internet y no compilan, lo más probable es que usen la versión antigua.
IFCD0182 — Desarrollo Web con Java

8. Actividad práctica guiada

Título Servlet informativo con datos dinámicos

Duración 60 minutos

Objetivo Ampliar el primer Servlet para que muestre información dinámica generada en el
servidor

Nivel Guiado — el formador explica cada paso antes de que lo implementes

Enunciado
Modifica el Servlet HolaMundo para que muestre en el navegador:

• Tu nombre (guardado en una variable Java).


• La fecha y hora actuales del servidor.
• El número de veces que se ha visitado la página desde que arrancó Tomcat (usa un contador
estático).
• El método HTTP con el que se realizó la petición (GET, POST...).
• Un párrafo que explique brevemente qué es un Servlet (escrito por ti, no copiado).

Guía paso a paso

Añade las importaciones necesarias


1 import [Link];
import [Link];

Añade el contador de visitas como atributo estático de la clase


Un atributo estático mantiene su valor mientras Tomcat está activo.
2
private static int contadorVisitas = 0;

Dentro del doGet, incrementa el contador y obtén los datos dinámicos


contadorVisitas++;

3 String fechaHora = [Link]()


.format([Link]("dd/MM/yyyy HH:mm:ss"));
String metodo = [Link]();

Genera el HTML con todos los datos


4 [Link]("<h1>Bienvenido, TU_NOMBRE</h1>");
[Link]("<p>Fecha y hora en el servidor: " + fechaHora + "</p>");
IFCD0182 — Desarrollo Web con Java

[Link]("<p>Visitas desde que arrancó Tomcat: " + contadorVisitas


+ "</p>");
[Link]("<p>Método HTTP de esta petición: " + metodo + "</p>");

Ejecuta, prueba y reflexiona


Recarga la página varias veces. Observa cómo cambia el contador y la hora. Eso es contenido
5
dinámico generado por Java en el servidor.

🤖 CON AYUDA DE LA IA
Usa la IA como tutor de depuración
Si algo no funciona como esperas, describe el problema en Claude o ChatGPT así:

"Tengo un Servlet Java en Tomcat 10. Cuando recargo la página el contador no sube. Aquí está mi
código: [pega el código]. ¿Qué puede estar fallando?"
La IA te ayudará a identificar el error. Pero debes entender la solución antes de aplicarla. Pregúntale
'¿por qué ese es el problema?' si no lo ves claro.
IFCD0182 — Desarrollo Web con Java

9. Actividad práctica autónoma

Título Mini-servidor de información del navegador

Duración 90 minutos

Objetivo Desarrollar un Servlet de forma autónoma aplicando lo aprendido

Nivel Autónomo — resuelves los problemas por tu cuenta (con acceso a apuntes y a la IA)

Entrega Proyecto IntelliJ comprimido en .zip con el código fuente

Enunciado
Crea un nuevo Servlet (en el mismo proyecto o en uno nuevo) mapeado a la URL /info-navegador. Cuando se
acceda a esa URL, debe mostrar una página HTML con:

• El título: "Información de tu petición"


• La IP del cliente que hace la petición. Investiga qué método de request devuelve esto.
• El nombre y versión del navegador (se obtiene de una cabecera HTTP). Investiga cuál es esa cabecera.
• El idioma preferido del navegador (también viene en una cabecera HTTP).
• Una tabla HTML con todas las cabeceras de la petición y sus valores. Investiga cómo iterar sobre
todas las cabeceras disponibles en el request.
• Un pie de página con la hora exacta de la petición.

Criterios de evaluación

Criterio Descripción Puntuación

Funcionalidad La aplicación arranca en Tomcat y responde sin errores 30%

Datos correctos Muestra IP, User-Agent, idioma y cabeceras de forma 30%


correcta

Código limpio Código legible, con comentarios y nombres de variable 20%


claros

HTML válido La respuesta es HTML bien estructurado y se ve 10%


correctamente

Iniciativa Añade algún elemento propio no pedido en el 10%


enunciado (bono)

⚠ ATENCIÓN
IFCD0182 — Desarrollo Web con Java

Pistas permitidas: puedes usar la documentación oficial de Jakarta EE, los apuntes de esta unidad y
preguntar a la IA. Lo que no está permitido es que la IA escriba el código completo por ti. Si la usas,
documenta en un comentario qué parte te ayudó a resolver.

10. Resumen de la unidad

Estos son los conceptos clave que has aprendido en esta unidad. Si puedes explicar cada uno con tus propias
palabras, estás listo para la siguiente:

Concepto En pocas palabras

Modelo cliente-servidor Cliente pide, servidor responde. Java vive en el servidor.

HTTP El protocolo de comunicación de la web. Define cómo se hacen las


peticiones y las respuestas.

Métodos HTTP GET (leer), POST (crear), PUT (actualizar), DELETE (borrar). Son los
verbos del lenguaje HTTP.

Códigos de respuesta Números que indican el resultado: 200 éxito, 404 no encontrado, 500
error del servidor.

Tomcat El servidor de aplicaciones donde se ejecutan los programas Java Web.


Escucha en el puerto 8080.

Servlet Clase Java que procesa peticiones HTTP. Extiende HttpServlet y tiene
métodos doGet, doPost, etc.

@WebServlet Anotación que indica a Tomcat qué URL debe mapear a cada Servlet.

Ciclo petición-respuesta Navegador → petición HTTP → Tomcat → Servlet → genera respuesta


→ Tomcat → navegador.

JDK 17 El kit de desarrollo Java. Versión LTS actual y la más extendida en el


mercado laboral.

IntelliJ IDEA El IDE más utilizado para Java. La edición Community es gratuita y
suficiente para el curso.

✔ BUENAS PRÁCTICAS
Comprueba que puedes responder estas preguntas sin mirar los apuntes:
30. ¿Qué ocurre entre que escribes una URL en el navegador y ves la página?
31. ¿Qué diferencia hay entre GET y POST?
32. ¿Qué hace Tomcat exactamente cuando llega una petición?
33. ¿Para qué sirve la anotación @WebServlet?
34. ¿Qué código de respuesta indica que todo fue bien? ¿Y que el recurso no existe?

🤖 CON AYUDA DE LA IA
Consolida lo aprendido con IA
IFCD0182 — Desarrollo Web con Java

Usa este prompt para afianzar los conceptos antes de la próxima clase:

"Actúa como formador técnico de Java Web. Hazme 5 preguntas cortas sobre HTTP, Servlets y
Tomcat. Después de que yo responda cada una, corrígeme y explícame si me he equivocado."
Este tipo de práctica —preguntas y respuestas con corrección inmediata— es una de las formas más
eficaces de estudiar. La IA es un tutor disponible en todo momento.

MÓDULO 1 · Bloque 1 (10h)


Arquitectura Java Web · JSP · Servlets
UNIDAD 2

JavaServer Pages (JSP)


Páginas web dinámicas generadas desde el servidor con Java

Duración estimada 4–5 horas (dentro del bloque de 10h con la Unidad 1)

Prerrequisito Unidad 1 completada: entorno instalado y primer Servlet funcionando

Nivel de entrada Grupo heterogéneo — se explica desde cero con ritmo progresivo

Modalidad Presencial · Actividades cortas intercaladas · Apoyo con IA

Lo que aprenderás Crear páginas web dinámicas con JSP, usar objetos implícitos, JSTL y conectar JSP
con Servlets

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué es JSP y en qué se diferencia de un Servlet.
• Usar la sintaxis básica de JSP: scriptlets, expresiones, directivas y Expression Language.
• Trabajar con los objetos implícitos de JSP (request, response, session, out...).
• Pasar datos entre un Servlet y una página JSP.
• Usar las etiquetas JSTL para evitar código Java dentro del HTML.
• Completar mini-actividades prácticas al final de cada bloque conceptual.
IFCD0182 — Desarrollo Web con Java

1. Introducción: el problema que resuelve JSP

En la Unidad 1 creaste un Servlet que respondía con HTML. Funcionaba, pero algo resultaba incómodo: para
mostrar una página web completa había que escribir cada línea de HTML dentro de un [Link](). Con una
página real de cien líneas de HTML, ese código se convierte en algo difícil de leer, mantener y modificar.

👁 FÍJATE
Imagina que tienes que escribir una página de inicio con menú, cabecera, tres columnas de
contenido y un pie de página. Todo eso dentro de [Link](). El código Java y el HTML se mezclan
de una forma que nadie quiere mantener.

JSP (JavaServer Pages) resuelve exactamente este problema. En lugar de poner HTML dentro de Java, puedes
poner Java dentro de HTML. El archivo .jsp se parece mucho a un archivo .html normal, con la diferencia de
que en ciertos lugares puedes insertar lógica Java o datos dinámicos.

El resultado es el mismo: el servidor genera HTML y lo manda al navegador. Pero el código es mucho más
fácil de leer y de mantener.

💼 EN EL TRABAJO REAL
Por qué JSP sigue siendo relevante
JSP es una tecnología con muchos años de historia, lo que significa que hay miles de aplicaciones en
empresas que todavía la usan. Bancos, administraciones públicas, sistemas de gestión interna...
muchos de ellos siguen teniendo JSP. Además, entender JSP es imprescindible para comprender
después frameworks más modernos como Thymeleaf o los motores de plantillas de Spring MVC, que
funcionan con una filosofía muy parecida.
IFCD0182 — Desarrollo Web con Java

2. Qué es JSP y cómo funciona por dentro

2.1 La idea fundamental


Un archivo JSP es un archivo de texto con extensión .jsp que contiene HTML normal y, en algunos puntos,
código Java o expresiones especiales. Cuando Tomcat recibe una petición a ese archivo, hace algo que
conviene entender bien:

1 Tomcat lee el archivo .jsp


Lo analiza y busca los fragmentos de código Java o expresiones especiales mezcladas con el
HTML.

2 Lo traduce a un Servlet Java


Tomcat convierte automáticamente el archivo .jsp en una clase Java que extiende HttpServlet.
Todo el HTML se convierte en instrucciones [Link](). Todo el código Java que habías
escrito en el JSP se queda tal cual.

3 Compila ese Servlet y lo ejecuta


A partir de aquí, el proceso es idéntico al que ya conoces de la Unidad 1: el Servlet genera la
respuesta y Tomcat la envía al navegador.

ℹ NOTA
Esto significa que JSP y Servlet son la misma tecnología por dentro. JSP es simplemente una forma
más cómoda de escribir código que al final se convierte en un Servlet. Tomcat hace esa conversión
automáticamente la primera vez que alguien accede al archivo.
IFCD0182 — Desarrollo Web con Java

2.2 Comparación directa: Servlet vs JSP


Aquí está la misma página generada de dos formas. El resultado en el navegador es idéntico:

Con Servlet puro (lo que hacías en la Unidad 1):

@WebServlet("/bienvenida")
public class BienvenidaServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException {
[Link]("text/html;charset=UTF-8");
PrintWriter out = [Link]();
[Link]("<!DOCTYPE html>");
[Link]("<html><body>");
[Link](" <h1>Bienvenido</h1>");
[Link](" <p>Hoy es: " + new [Link]() + "</p>");
[Link]("</body></html>");
}
}

Con JSP (lo que aprenderás ahora):

<%-- [Link] --%>


<!DOCTYPE html>
<html>
<body>
<h1>Bienvenido</h1>
<p>Hoy es: <%= new [Link]() %></p>
</body>
</html>

✔ BUENAS PRÁCTICAS
El segundo archivo es mucho más legible. Un diseñador web puede entender la estructura HTML sin
saber Java. Un desarrollador Java puede ver fácilmente dónde está la lógica. Esta separación es
exactamente lo que se busca en desarrollo profesional.

⏱ ACTIVIDAD RÁPIDA — Reflexión rápida — 5 min


Mira el archivo .jsp del ejemplo anterior. Localiza:

• ¿Dónde está el código Java?


• ¿Cómo se distingue del HTML?
• ¿Qué crees que genera Tomcat cuando traduce ese .jsp a Servlet?
Comenta las respuestas en voz alta con el compañero de al lado. El formador las revisará antes
de continuar.
IFCD0182 — Desarrollo Web con Java
IFCD0182 — Desarrollo Web con Java

3. La sintaxis de JSP: cómo mezclar Java con HTML

JSP tiene varios mecanismos para insertar contenido dinámico en una página. Veremos cada uno con un
ejemplo claro y su equivalente en el Servlet generado, para que entiendas exactamente qué hace Tomcat
con cada uno.
3.1 Expresiones JSP <%= %>
Una expresión evalúa código Java y coloca el resultado directamente en la salida HTML. Es el mecanismo
más habitual para mostrar datos.

<%-- Sintaxis: <%= expresion_java %> --%>

<p>La hora actual es: <%= new [Link]() %></p>


<p>Dos más dos es: <%= 2 + 2 %></p>
<p>Usuario: <%= [Link]("nombre") %></p>

⚙ CÓMO FUNCIONA
Tomcat convierte cada expresión en una instrucción [Link](). Por eso la expresión no debe
terminar en punto y coma: no es una sentencia completa, es un valor.

3.2 Scriptlets <% %>


Un scriptlet contiene código Java que se ejecuta cuando se procesa la página, pero no produce salida directa
por sí mismo. Se usa para lógica: bucles, condiciones, cálculos.

<%-- Scriptlet: código Java que se ejecuta en el servidor --%>


<%
int contador = 10;
String mensaje = contador > 5 ? "Mayor que 5" : "Menor o igual a 5";
%>

<p>El contador vale: <%= contador %></p>


<p><%= mensaje %></p>

<%-- Scriptlet con bucle para generar HTML dinámico --%>


<ul>
<%
String[] frutas = {"Manzana", "Naranja", "Mango"};
for (String fruta : frutas) {
%>
<li><%= fruta %></li>
<%
}
IFCD0182 — Desarrollo Web con Java
%>
</ul>

✘ ERROR FRECUENTE
Los scriptlets mezclan Java con HTML de una forma que puede volverse caótica rápidamente. Úsalos
solo mientras aprendes. En código profesional se sustituyen por JSTL (lo verás en la sección 5 de esta
unidad) o por datos enviados desde el Servlet.

3.3 Declaraciones <%! %>


Permiten declarar variables o métodos que forman parte de la clase Servlet generada (no del método de
servicio). Son poco frecuentes en la práctica.

<%-- Declaración de un método en la clase Servlet generada --%>


<%!
private String saludar(String nombre) {
return "Hola, " + nombre + "!";
}
%>

<p><%= saludar("María") %></p>

3.4 Directivas <%@ %>


Las directivas dan instrucciones al motor JSP sobre cómo debe traducir la página. No generan salida, son
configuración.

Directiva Para qué sirve Ejemplo

page Configurar la página JSP: codificación, <%@ page contentType="text/html;charset=UTF-


importaciones, gestión de errores 8" language="java" %>

include Incluir el contenido de otro archivo en <%@ include file="[Link]" %>


tiempo de compilación

taglib Declarar una biblioteca de etiquetas <%@ taglib prefix="c" uri="[Link]" %>
(como JSTL)

ℹ NOTA
La directiva page con contentType y charset=UTF-8 es obligatoria en todas tus páginas JSP. Sin ella
los caracteres especiales del español (tildes, ñ) se mostrarán mal.
IFCD0182 — Desarrollo Web con Java

3.5 Expression Language (EL) ${ }


Expression Language es la forma moderna y limpia de mostrar datos en JSP. Es más concisa que las
expresiones <%%> y mucho más segura porque no permite ejecutar código arbitrario.

<%-- EL accede a atributos del request, session, etc. --%>

<%-- Equivale a: <%= [Link]("usuario") %> --%>


<p>Usuario: ${usuario}</p>

<%-- Acceder a propiedades de un objeto --%>


<p>Nombre: ${[Link]}</p>
<p>Email: ${[Link]}</p>

<%-- Parámetros de la petición --%>


<p>Búsqueda: ${param.q}</p>

<%-- Operaciones simples --%>


<p>Total con IVA: ${precio * 1.21}</p>

✔ BUENAS PRÁCTICAS
Regla práctica: usa siempre EL para mostrar datos. Usa scriptlets solo cuando necesites lógica
compleja que no puedas resolver con JSTL. El código resultante es más limpio y más fácil de
mantener.

⏱ ACTIVIDAD RÁPIDA — Ejercicio en papel — 10 min


Sin abrir el ordenador, escribe cómo mostrarías en JSP cada uno de estos valores:

• La cadena "Hola mundo" directamente.


• El resultado de sumar 15 + 27.
• Un parámetro llamado ciudad que viene de la URL.
• Un atributo llamado producto guardado en el request por un Servlet.
Cuando acabes, compara con el compañero de al lado. El formador corregirá en la pizarra.
IFCD0182 — Desarrollo Web con Java

4. Los objetos implícitos: lo que JSP te da gratis

Cuando Tomcat traduce un JSP a Servlet, declara automáticamente una serie de objetos que puedes usar
directamente en tus scriptlets y expresiones sin necesidad de crearlos. Se llaman objetos implícitos porque
están disponibles sin haberlos declarado.

Objeto Tipo Java Para qué lo usarás Ámbito

request HttpServletRequest Leer parámetros de la petición, La petición actual


atributos enviados por el Servlet

response HttpServletResponse Configurar la respuesta (rara vez en La petición actual


JSP)

session HttpSession Guardar y leer datos del usuario entre La sesión del
peticiones usuario

application ServletContext Datos compartidos por toda la Toda la aplicación


aplicación para todos los usuarios

out JspWriter Escribir directamente en la respuesta La petición actual


HTML

pageContext PageContext Acceso a todos los ámbitos y objetos La página actual

config ServletConfig Parámetros de configuración del La página actual


Servlet generado

page Object Referencia a la instancia actual del La página actual


Servlet (raramente usado)

exception Throwable El error producido (solo en páginas de La página de error


error)

4.1 Los más importantes en detalle


request — Leer lo que el usuario envía

<%-- Leer parámetros enviados por un formulario o la URL --%>


<%
String nombre = [Link]("nombre");
String edad = [Link]("edad");
%>
<p>Nombre: <%= nombre %></p>
<p>Edad: <%= edad %></p>

<%-- Leer un atributo puesto por un Servlet con setAttribute() --%>


<p>Producto: ${[Link]}</p>
IFCD0182 — Desarrollo Web con Java

session — Guardar datos entre páginas

<%-- Guardar un valor en la sesión --%>


<%
[Link]("usuarioLogueado", "carlos@[Link]");
%>

<%-- Leer ese valor en otra página (mismo usuario, otra petición) --%>
<p>Usuario activo: ${[Link]}</p>

ℹ NOTA
Diferencia clave entre request y session: los atributos de request desaparecen al terminar la
petición. Los de session persisten mientras el usuario esté activo. Usa request para datos de una sola
página, session para datos que deben estar disponibles durante toda la navegación (como el usuario
logueado).

⏱ ACTIVIDAD RÁPIDA — Código en el IDE — 15 min


En el proyecto de la Unidad 1, crea un nuevo archivo [Link] en la carpeta src/main/webapp.
Escribe una página que muestre:

• Un título con tu nombre (usando una expresión JSP).


• La fecha y hora actuales del servidor.
• Un parámetro llamado ciudad de la URL (prueba: /[Link]?ciudad=Madrid).
Accede a ella desde el navegador. Recuerda que los archivos .jsp en la carpeta webapp son
accesibles directamente, sin necesidad de configurar un Servlet.
IFCD0182 — Desarrollo Web con Java

5. JSTL: lógica en JSP sin código Java

JSTL (JSP Standard Tag Library) es una biblioteca de etiquetas que permite hacer en JSP lo que normalmente
harías con scriptlets —bucles, condiciones, formateo— pero usando una sintaxis similar a HTML. El resultado
es un código mucho más limpio que mezclar Java con HTML.

ℹ NOTA
Para usar JSTL, primero tienes que añadir la dependencia en tu [Link] y declarar la biblioteca al
principio del JSP.

5.1 Configuración: añadir JSTL al proyecto


Paso 1 — Añadir la dependencia en [Link]:

<dependency>
<groupId>[Link]</groupId>
<artifactId>[Link]-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>[Link]</artifactId>
<version>3.0.1</version>
</dependency>

Paso 2 — Declarar JSTL al principio del JSP:

<%@ taglib prefix="c" uri="[Link]" %>

5.2 Las etiquetas más usadas


c:if — Condición simple

<%-- Muestra el contenido solo si la condición es verdadera --%>


<c:if test="${edad >= 18}">
<p>Eres mayor de edad.</p>
</c:if>

<%-- Con atributo enviado desde el Servlet --%>


<c:if test="${not empty usuario}">
<p>Bienvenido, ${[Link]}!</p>
IFCD0182 — Desarrollo Web con Java
</c:if>

c:choose — Condición múltiple (como un if-else)

<c:choose>
<c:when test="${puntuacion >= 90}">
<p class="excelente">Excelente</p>
</c:when>
<c:when test="${puntuacion >= 60}">
<p class="aprobado">Aprobado</p>
</c:when>
<c:otherwise>
<p class="suspendido">Suspendido</p>
</c:otherwise>
</c:choose>

c:forEach — Bucle para recorrer listas

<%-- Recorre una lista de productos enviada por el Servlet --%>


<table>
<tr><th>Nombre</th><th>Precio</th></tr>
<c:forEach var="prod" items="${listaProductos}">
<tr>
<td>${[Link]}</td>
<td>${[Link]} €</td>
</tr>
</c:forEach>
</table>

c:out — Mostrar texto de forma segura (evita XSS)

<%-- INSEGURO: podría ejecutar scripts maliciosos --%>


<p>${[Link]}</p>

<%-- SEGURO: escapa caracteres especiales --%>


<p><c:out value="${[Link]}"/></p>

⚠ ATENCIÓN
Usa siempre c:out cuando muestres datos que vienen del usuario (parámetros, formularios). Si no lo
haces, tu aplicación es vulnerable a ataques XSS (Cross-Site Scripting), uno de los más habituales en
aplicaciones web.
IFCD0182 — Desarrollo Web con Java

c:url y c:redirect — Gestión de URLs

<%-- Crear una URL correcta con el contexto de la aplicación --%>


<a href="<c:url value='/productos'/>">Ver productos</a>

<%-- Redirigir al usuario a otra página --%>


<c:redirect url="/login"/>

✔ BUENAS PRÁCTICAS
Comparación: scriptlet vs JSTL para el mismo resultado
Scriptlet (evitar en producción):

<% for(Producto p : lista) { %>


<li><%= [Link]() %></li>
<% } %>

JSTL (forma correcta):

<c:forEach var="p" items="${lista}">


<li>${[Link]}</li>
</c:forEach>

El segundo es más legible, más seguro y es el estándar esperado en código profesional.


IFCD0182 — Desarrollo Web con Java

6. Servlet + JSP: el patrón correcto de trabajo

Ya sabes usar JSP por separado. Pero en una aplicación real, JSP nunca trabaja solo. La combinación correcta
es:

Componente Su responsabilidad Lo que NO debe hacer

Servlet Recibir la petición, ejecutar la lógica, preparar Generar HTML directamente


los datos

JSP Mostrar los datos que le preparó el Servlet Contener lógica de negocio ni acceder a
la base de datos

6.1 Cómo pasar datos de un Servlet a un JSP


El Servlet guarda los datos en el objeto request con setAttribute(), y luego reenvía la petición al JSP con
RequestDispatcher. El JSP lee esos datos con EL o JSTL.

El Servlet — prepara los datos y reenvía al JSP:

@WebServlet("/catalogo")
public class CatalogoServlet extends HttpServlet {

protected void doGet(HttpServletRequest request, HttpServletResponse response)


throws ServletException, IOException {

// 1. Preparar los datos (en la práctica vendrían de una base de datos)


List<String> productos = new ArrayList<>();
[Link]("Teclado mecánico");
[Link]("Monitor 27 pulgadas");
[Link]("Ratón inalámbrico");

// 2. Guardar los datos en el request


[Link]("productos", productos);
[Link]("titulo", "Catálogo de productos");

// 3. Reenviar al JSP (forward)


RequestDispatcher rd = [Link]("/WEB-
INF/vistas/[Link]");
[Link](request, response);
}
}
IFCD0182 — Desarrollo Web con Java

El JSP — muestra los datos sin lógica:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>


<%@ taglib prefix="c" uri="[Link]" %>
<!DOCTYPE html>
<html lang="es">
<head>
<title>${titulo}</title>
</head>
<body>
<h1>${titulo}</h1>
<ul>
<c:forEach var="producto" items="${productos}">
<li><c:out value="${producto}"/></li>
</c:forEach>
</ul>
</body>
</html>

⚙ CÓMO FUNCIONA
¿Por qué los JSP van en /WEB-INF/vistas/?
La carpeta WEB-INF es especial: Tomcat no permite acceder a su contenido directamente desde el
navegador. Si pusieras el JSP fuera de WEB-INF, cualquier usuario podría ir a /[Link] y ver la
página sin pasar por el Servlet (sin datos, con errores o con código a medias). Guardando los JSPs
dentro de WEB-INF obligas a que toda petición pase primero por el Servlet.

⏱ ACTIVIDAD RÁPIDA — Conectar Servlet y JSP — 20 min


Amplía el proyecto de la Unidad 1 o crea uno nuevo:

1. Crea un Servlet mapeado a /lista-colores.


2. Dentro del doGet, crea una lista con al menos 4 colores y guárdala con setAttribute.
3. Crea un JSP en /WEB-INF/vistas/[Link] que los muestre en una lista HTML
usando c:forEach.
4. El Servlet debe hacer forward al JSP.
5. Accede desde el navegador a /lista-colores y comprueba que los colores aparecen.
Pista: si el JSP muestra la lista vacía o da error, comprueba que el setAttribute y el nombre del
atributo en el JSP coinciden exactamente.
IFCD0182 — Desarrollo Web con Java

7. Errores frecuentes y cómo resolverlos

Error / Síntoma Causa más probable Solución

La página muestra ${variable} EL está desactivado o falta la Añade isELIgnored="false" en la directiva


como texto literal directiva page page o elimina cualquier configuración
que lo desactive

NullPointerException al cargar Se intenta usar un atributo que el Comprueba que el setAttribute en el


el JSP Servlet no guardó Servlet usa exactamente el mismo
nombre que el EL en el JSP

El JSP se muestra sin datos El forward se hace antes de Asegúrate de que todos los setAttribute
(lista vacía) guardar los atributos van antes de la línea [Link]()

Error 404 al intentar acceder El JSP está en WEB-INF (correcto). Es el comportamiento esperado. Accede
al JSP directamente El usuario intentó acceder sin pasar siempre a través de la URL del Servlet
por el Servlet

Caracteres especiales mal Falta charset=UTF-8 en la directiva Añade <%@ page


mostrados (tildes, ñ) page contentType="text/html;charset=UTF-8"
%> en la primera línea del JSP

ClassNotFoundException: Usando la dependencia antigua de Cambia a la dependencia


[Link] JSTL con Tomcat 10 [Link] (versión 3.x)

🤖 CON AYUDA DE LA IA
Usa la IA para depurar errores de JSP
Cuando tengas un error en un JSP, copia el stack trace completo de la consola y usa este prompt:

"Tengo este error en una aplicación JSP con Tomcat 10 y Jakarta EE: [pega el error]. Mi JSP hace esto:
[pega el código]. ¿Qué está causando el error y cómo lo soluciono?"
La IA es especialmente útil con errores de JSP porque los stack traces de Tomcat incluyen referencias
a código generado automáticamente que puede ser confuso al principio.
IFCD0182 — Desarrollo Web con Java

8. Buenas prácticas en JSP

✔ BUENAS PRÁCTICAS
Sigue estas reglas en todo el código que escribas:
• Cero scriptlets en código de producción: usa JSTL y EL. Los scriptlets son aceptables
mientras aprendes, pero acostúmbrate cuanto antes a no usarlos.
• Siempre charset=UTF-8: en la directiva page de todos tus JSPs sin excepción.
• JSPs dentro de WEB-INF: para que no sean accesibles directamente desde el navegador.
• Usa c:out para datos del usuario: cualquier dato que venga de un parámetro o
formulario debe mostrarse con c:out para evitar XSS.
• El JSP no accede a la base de datos: eso es responsabilidad del Servlet o de la capa de
servicio. El JSP solo muestra lo que le pasan.
• Nombres descriptivos para los atributos: setAttribute("listaProductos", ...) es mejor que
setAttribute("datos", ...). El código del JSP será mucho más legible.
• Un JSP, una responsabilidad: un JSP para listar productos, otro para el formulario de
creación, otro para el detalle. No pongas todo en un único archivo gigante.
IFCD0182 — Desarrollo Web con Java

9. Actividad práctica guiada

Título Tienda de tecnología — listado dinámico de productos

Duración 60 minutos

Objetivo Crear un flujo Servlet → JSP completo con lista de objetos y JSTL

Nivel Guiado paso a paso con el formador

Enunciado
Vamos a construir una pequeña tienda de tecnología con tres páginas conectadas:

• Página de inicio (/tienda): muestra el nombre de la tienda y un enlace para ver el catálogo.
• Catálogo (/tienda/catalogo): lista todos los productos con nombre, precio y categoría.
• Detalle (/tienda/producto?id=X): muestra los datos completos de un producto concreto.

Paso 1 — Crear la clase Producto

public class Producto {


private int id;
private String nombre;
private double precio;
private String categoria;

public Producto(int id, String nombre, double precio, String categoria) {


[Link] = id; [Link] = nombre;
[Link] = precio; [Link] = categoria;
}
// Getters (genera IntelliJ: clic derecho → Generate → Getter)
public int getId() { return id; }
public String getNombre() { return nombre; }
public double getPrecio() { return precio; }
public String getCategoria() { return categoria; }
}

Paso 2 — Crear el Servlet del catálogo

@WebServlet("/tienda/catalogo")
public class CatalogoServlet extends HttpServlet {

protected void doGet(HttpServletRequest req, HttpServletResponse res)


throws ServletException, IOException {

// Datos de ejemplo (más adelante vendrán de una base de datos)


IFCD0182 — Desarrollo Web con Java
List<Producto> catalogo = new ArrayList<>();
[Link](new Producto(1, "Teclado mecánico RGB", 89.99,
"Periféricos"));
[Link](new Producto(2, "Monitor 27'' 144Hz", 349.00,
"Pantallas"));
[Link](new Producto(3, "Ratón gaming 12000 DPI", 45.50,
"Periféricos"));
[Link](new Producto(4, "Auriculares con micro", 69.99, "Audio"));

[Link]("catalogo", catalogo);
[Link]("totalProductos", [Link]());

[Link]("/WEB-INF/vistas/[Link]")
.forward(req, res);
}
}

Paso 3 — Crear el JSP del catálogo

<%@ page contentType="text/html;charset=UTF-8" language="java" %>


<%@ taglib prefix="c" uri="[Link]" %>
<!DOCTYPE html>
<html lang="es">
<head>
<title>Catálogo — Tienda Tech</title>
<style>
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background-color: #2E5FA3; color: white; }
tr:nth-child(even) { background-color: #f5f5f5; }
</style>
</head>
<body>
<h1>Catálogo de productos</h1>
<p>Total disponible: ${totalProductos} artículos</p>
<table>
<tr>
<th>ID</th><th>Nombre</th><th>Categoría</th><th>Precio</th>
</tr>
<c:forEach var="p" items="${catalogo}">
<tr>
<td>${[Link]}</td>
<td><c:out value="${[Link]}"/></td>
<td><c:out value="${[Link]}"/></td>
<td>${[Link]} €</td>
</tr>
</c:forEach>
</table>
</body>
</html>
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Amplía la práctica con IA
Una vez que el catálogo funciona, usa este prompt para añadir el filtro por categoría:

"Tengo un Servlet Java que pasa una lista de productos a un JSP. Quiero añadir un filtro: si la URL
contiene el parámetro categoria, solo se muestran los productos de esa categoría. Muéstrame cómo
modificar el Servlet para filtrar la lista antes de pasarla al JSP. Explícame cada cambio."
Implementa el filtro y prueba en el navegador: /tienda/catalogo?categoria=Periféricos
IFCD0182 — Desarrollo Web con Java

10. Actividad práctica autónoma

Título Sistema de noticias dinámico

Duración 90 minutos

Objetivo Desarrollar de forma autónoma una aplicación Servlet + JSP + JSTL completa

Modalidad Autónomo — puedes consultar apuntes, documentación y la IA

Entrega Proyecto IntelliJ comprimido en .zip con código fuente

Enunciado
Desarrolla un sistema de noticias para una empresa. La aplicación debe tener:

• Clase Noticia con los atributos: id (int), titulo (String), resumen (String), autor (String), fecha
(LocalDate), categoria (String).
• Servlet /noticias que prepare una lista de al menos 5 noticias y las pase al JSP.
• JSP de listado que muestre las noticias en tarjetas o tabla. Cada noticia debe tener un enlace a su
detalle.
• Servlet /noticias/detalle?id=X que busque la noticia por ID y la pase a un JSP de detalle.
• JSP de detalle que muestre todos los campos de la noticia seleccionada con un botón para volver al
listado.
• Filtro por categoría (bono): añade un selector en la página de listado que filtre noticias por categoría.

Criterios de evaluación
Criterio Descripción Puntuación

Funcionalidad completa Listado, detalle y navegación entre páginas 35%


sin errores

Uso correcto de JSTL Sin scriptlets, datos mostrados con 25%


c:forEach, c:if y c:out

Separación Servlet-JSP El JSP no contiene lógica. El Servlet no 20%


genera HTML

Código limpio Nombres claros, comentarios donde hace 10%


falta, UTF-8 correcto

Filtro por categoría Funciona correctamente y el JSP lo muestra 10% bono


con JSTL

⚠ ATENCIÓN
Reglas de uso de la IA en esta actividad: puedes usarla para depurar errores, entender conceptos
que no recuerdas o buscar la sintaxis de una etiqueta JSTL. No puedes pedirle que escriba el código
completo de ninguna de las clases o archivos. Si la usas, escribe un comentario en el código
indicando qué parte te ayudó a resolver.
IFCD0182 — Desarrollo Web con Java

11. Resumen de la unidad

Concepto Lo esencial

¿Qué es JSP? Un archivo .jsp con HTML normal más zonas de Java. Tomcat lo convierte en
Servlet automáticamente.

Expresión <%= %> Evalúa código Java y pone el resultado en el HTML. Sin punto y coma.

Scriptlet <% %> Código Java que se ejecuta pero no produce salida directa. Evitar en
producción.

Directiva <%@ %> Configuración del motor JSP: page (charset), taglib (JSTL), include.

Expression Language ${} Forma limpia y segura de mostrar atributos y datos. Preferida sobre
scriptlets.

Objetos implícitos request, response, session, application, out... disponibles sin declararlos.

JSTL Etiquetas para lógica en JSP: c:if, c:choose, c:forEach, c:out. Sustituyen a los
scriptlets.

Patrón Servlet→JSP El Servlet hace setAttribute() con los datos y forward() al JSP. El JSP solo
muestra.

WEB-INF Carpeta protegida. Los JSPs dentro de ella no son accesibles directamente
desde el navegador.

c:out Usar siempre para mostrar datos del usuario. Previene ataques XSS.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación — respóndelas sin mirar los apuntes:
6. ¿Qué diferencia hay entre <%= %> y <% %>?
7. ¿Por qué es preferible EL a los scriptlets?
8. ¿Qué hace [Link]()?
9. ¿Qué ventaja tiene guardar los JSPs en WEB-INF?
10. ¿Cuándo usarías session en lugar de request para guardar un atributo?
11. ¿Qué etiqueta JSTL usarías para recorrer una lista? ¿Y para mostrar texto de forma
segura?

🤖 CON AYUDA DE LA IA
Repasa y consolida con IA antes de la próxima clase
Usa este prompt para repasar con un tutor interactivo:

"Actúa como formador técnico de Java Web. Tengo que saber JSP, JSTL, objetos implícitos y el patrón
Servlet→JSP. Hazme 6 preguntas, una a la vez. Después de cada respuesta mía, corrígeme, explícame
si me equivoqué y dame un ejemplo rápido. Empieza con la primera pregunta."
IFCD0182 — Desarrollo Web con Java

MÓDULO 1 — Introducción al Desarrollo Web con Java


UNIDAD 3

JavaBeans: separar datos de lógica


El principio que hace que el código sea mantenible, reutilizable y profesional

Duración estimada 6 horas (teoría + prácticas)

Módulo 1 — Introducción al Desarrollo Web con Java

Unidad anterior Unidad 1 — ¿Cómo funciona la web con Java?

Nivel de entrada Instalación completada. El Servlet básico puede funcionar con algún problema
pendiente

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Qué son los JavaBeans, por qué existen, cómo se crean y cómo se usan para
mantener el código limpio y organizado

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué es un JavaBean y para qué sirve en una aplicación web.
• Identificar las tres reglas que debe cumplir cualquier JavaBean.
• Crear JavaBeans correctamente siguiendo el estándar.
• Conectar un JavaBean con una página JSP usando las etiquetas estándar.
• Distinguir cuándo usar el Bean y cuándo no es necesario.
• Aplicar el concepto de separación de responsabilidades en tu código.
• Refactorizar código mal organizado aplicando lo aprendido.

⚠ ATENCIÓN
Si aún tienes problemas con el entorno de la Unidad 1:
No pasa nada. Puedes seguir esta unidad leyendo el código y los ejemplos aunque Tomcat no
arranque en tu equipo. Avisa al formador antes de empezar las actividades prácticas para que te
ayude a resolver la instalación en paralelo.
IFCD0182 — Desarrollo Web con Java

1. Introducción: el problema que resuelven los JavaBeans

Cuando empiezas a programar páginas JSP o Servlets, es muy tentador meter todo en el mismo sitio: los
datos, los cálculos, la conexión a base de datos y el HTML de salida. El código funciona, pero tiene un
problema grave.

✘ ERROR FRECUENTE
El código que mezcla datos y presentación es:
• Difícil de leer a las dos semanas de haberlo escrito.
• Imposible de mantener cuando la aplicación crece.
• Imposible de probar de forma automatizada.
• Incompatible con trabajar en equipo (el diseñador no puede tocar el HTML sin romper el
Java).

Los JavaBeans son la solución más básica y más antigua a este problema: una forma estandarizada de crear
objetos Java que representen datos y que cualquier tecnología Java (JSP, JSF, Spring...) pueda usar de la
misma manera.

💼 EN EL TRABAJO REAL
Por qué te van a preguntar esto en una entrevista:
El concepto detrás de los JavaBeans —separar la representación de datos de la lógica de
presentación— es uno de los principios más fundamentales del desarrollo de software. Aunque los
JavaBeans clásicos han evolucionado hacia otras formas (como las entidades JPA de Spring), el
principio es el mismo. Cualquier desarrollador Java debe saber de dónde viene y por qué existe.
IFCD0182 — Desarrollo Web con Java

2. ¿Qué es un JavaBean?

2.1 Definición
Un JavaBean es una clase Java que sigue tres reglas muy concretas. No es una clase especial de Java, no
necesita importar ninguna librería extra ni extender ninguna clase. Solo necesita respetar esas tres reglas:

# Regla Por qué existe esta regla

1 Constructor sin argumentos (público) Los frameworks como JSP, JSF o Spring necesitan poder crear el
objeto sin saber de antemano qué datos tendrá. Si el
constructor exige parámetros, el framework no puede
instanciarlo automáticamente.

2 Atributos privados Los datos del Bean no deben ser accesibles directamente desde
fuera. Esto protege la integridad del objeto y permite validar los
valores antes de asignarlos.

3 Métodos getters y setters públicos Son el único acceso controlado a los datos del Bean. JSP, JSF y
Spring los usan automáticamente mediante reflexión para leer y
escribir los atributos.

👁 FÍJATE
Un JavaBean no es más que una clase Java normal que sigue esas tres reglas. No tiene magia. Su
valor está en el acuerdo: cuando todo el mundo sigue el mismo patrón, los frameworks saben cómo
trabajar con los objetos sin que tú tengas que explicarles nada.

2.2 Una analogía para entenderlo


Imagina un formulario de papel para registrar un empleado. El formulario tiene campos en blanco: nombre,
apellido, fecha de nacimiento, departamento. Cualquiera que conozca ese formulario sabe dónde están los
datos y cómo rellenarlos.

Un JavaBean es como ese formulario: una estructura conocida, con campos accesibles de forma estándar.
Cualquier tecnología Java que conozca las reglas del Bean sabe cómo leerlo y rellenarlo.
IFCD0182 — Desarrollo Web con Java

3. Crear un JavaBean paso a paso

3.1 El Bean más sencillo posible


Vamos a crear un Bean que represente un producto de una tienda. Empezamos con lo mínimo necesario y
luego lo vamos ampliando.

package [Link];

// Esta clase representa un producto. Es un JavaBean porque cumple las tres reglas.
public class Producto {

// REGLA 2: atributos privados


private String nombre;
private double precio;
private int stock;

// REGLA 1: constructor sin argumentos


public Producto() {
}

// REGLA 3: getters y setters públicos


// Getter: devuelve el valor del atributo
public String getNombre() {
return nombre;
}

// Setter: asigna el valor al atributo


public void setNombre(String nombre) {
[Link] = nombre;
}

public double getPrecio() {


return precio;
}

public void setPrecio(double precio) {


// Podemos validar antes de asignar
if (precio < 0) throw new IllegalArgumentException("El precio no puede ser
negativo");
[Link] = precio;
}

public int getStock() {


return stock;
}

public void setStock(int stock) {


[Link] = stock;
}
IFCD0182 — Desarrollo Web con Java
}

⚙ CÓMO FUNCIONA
Cosas a observar en este código:
• El paquete [Link] indica que este Bean pertenece a la capa de modelo.
Organizar los Beans en un paquete separado es una buena práctica desde el principio.
• El constructor vacío public Producto() {} es el que exige la especificación JavaBean. Si no
lo pones, Java crea uno automáticamente; pero si añades otro constructor con
parámetros, debes declararlo explícitamente.
• El setter de precio valida el dato antes de asignarlo. Esto es lo que hace que los setters
sean mejores que dar acceso directo al atributo.
• Los nombres de los getters/setters siguen el patrón exacto: get + NombreAtributo (con
mayúscula). Esto no es opcional: los frameworks buscan esos métodos por nombre.

3.2 El convenio de nombres: getter y setter


El nombre de los métodos getter y setter sigue una regla fija que nunca puedes saltarte. Si el atributo se
llama nombreCompleto, los métodos deben ser:

Tipo de método Atributo Nombre del método Qué devuelve / recibe

Getter nombre getNombre() String — el valor del atributo

Setter nombre setNombre(String nombre) void — no devuelve nada

Getter precio getPrecio() double — el valor del atributo

Setter precio setPrecio(double precio) void — no devuelve nada

Getter (bool) disponible isDisponible() boolean — usa is en lugar de get

Setter (bool) disponible setDisponible(boolean d) void — no devuelve nada

ℹ NOTA
Los atributos de tipo boolean usan is en lugar de get en el nombre del getter: isDisponible(),
isActivo(), isVisible(). El setter sigue siendo set normalmente: setDisponible(boolean d).
IFCD0182 — Desarrollo Web con Java

3.3 Añadir un constructor con parámetros (opcional)


Aunque el constructor vacío es obligatorio, también puedes añadir constructores con parámetros para
facilitar la creación del objeto en código Java. En JSP normalmente no se usan, pero en el código Java del
Servlet sí son muy cómodos.

public class Producto {


private String nombre;
private double precio;
private int stock;

// OBLIGATORIO: constructor sin argumentos


public Producto() {
}

// OPCIONAL: constructor con parámetros para crear objetos cómodamente


public Producto(String nombre, double precio, int stock) {
[Link] = nombre;
[Link](precio); // usamos el setter para aprovechar la validación
[Link] = stock;
}

// ... getters y setters igual que antes


}

✔ BUENAS PRÁCTICAS
Fíjate que en el constructor con parámetros usamos [Link](precio) en lugar de [Link] =
precio. Esto es importante: así la validación del setter se aplica también cuando se crea el objeto con
parámetros.
IFCD0182 — Desarrollo Web con Java

4. Usar JavaBeans en páginas JSP

Una vez que tienes el Bean creado, JSP tiene tres etiquetas estándar para trabajar con él sin escribir código
Java directamente en la página. Es la forma correcta de usar Beans en JSP.

4.1 Las tres etiquetas de JavaBean en JSP

Etiqueta Para qué sirve Parámetro clave

<jsp:useBean> Declara o crea un Bean en un ámbito id (nombre), class (clase completa),


determinado scope (ámbito)

<jsp:setProperty> Asigna un valor a un atributo del Bean name (id del Bean), property
(atributo), value (valor)

<jsp:getProperty> Lee e inserta en el HTML el valor de un name (id del Bean), property
atributo del Bean (atributo a leer)

4.2 Ejemplo completo: mostrar un producto con JSP y Bean


Primero el Bean (ya lo tenemos). Ahora la página JSP que lo usa:

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<!-- 1. Declarar el Bean. scope="request" significa que vive durante esta petición. -
->
<jsp:useBean id="producto" class="[Link]" scope="request"/>

<!-- 2. Asignar valores al Bean desde la JSP (para pruebas; normalmente lo hace el
Servlet) -->
<jsp:setProperty name="producto" property="nombre" value="Teclado mecánico"/>
<jsp:setProperty name="producto" property="precio" value="89.99"/>
<jsp:setProperty name="producto" property="stock" value="15"/>

<!DOCTYPE html>
<html lang="es">
<head><title>Ficha de producto</title></head>
<body>
<h1>Ficha del producto</h1>
<p>
<!-- 3. Leer valores del Bean e insertarlos en el HTML -->
Nombre: <strong><jsp:getProperty name="producto" property="nombre"/></strong>
</p>
<p>Precio: <jsp:getProperty name="producto" property="precio"/> €</p>
<p>Stock disponible: <jsp:getProperty name="producto" property="stock"/>
unidades</p>
</body>
</html>
IFCD0182 — Desarrollo Web con Java

👁 FÍJATE
Observa que en la JSP no hay ni una línea de código Java entre <% %>. Solo etiquetas JSP estándar.
Esto es exactamente lo que buscamos: la vista no contiene lógica.

4.3 Los ámbitos del Bean (scope): cuánto tiempo vive


El parámetro scope de <jsp:useBean> determina cuánto tiempo está disponible el Bean y quién puede
acceder a él. Es uno de los conceptos más importantes de JSP:

scope Dónde vive el Bean Cuánto dura Cuándo usarlo

page Solo en la página actual Hasta que la página termina Datos que solo necesita esta
de procesarse JSP

request En la petición HTTP actual Hasta que el servidor envía la Pasar datos del Servlet a la JSP
respuesta (el más común)

session En la sesión del usuario Mientras el usuario tenga la Datos del usuario logueado
sesión abierta (carrito, perfil)

application En toda la aplicación Mientras Tomcat esté Configuración global,


arrancado contadores de visitas

💡 CONSEJO
Regla práctica para elegir el scope:
• Si el Bean solo lo necesita la JSP que lo crea → scope="page".
• Si el Servlet crea el Bean y lo pasa a la JSP → scope="request" (el más habitual en el
patrón MVC).
• Si necesitas guardarlo entre peticiones del mismo usuario → scope="session".
• Si es un dato global de la aplicación → scope="application". Úsalo con mucho cuidado
(problemas de concurrencia).
IFCD0182 — Desarrollo Web con Java

5. El patrón correcto: Servlet + Bean + JSP

Usar el Bean directamente en JSP con <jsp:setProperty> funciona, pero no es la forma correcta en una
aplicación real. El flujo profesional es:

Quién Qué hace Tecnología

Servlet Recibe la petición, obtiene o construye el Bean, lo mete en el request y redirige a Java / Servlet
la JSP

Bean Contiene y transporta los datos entre el Servlet y la JSP de forma estructurada Java / POJO

JSP Recoge el Bean del request y lo muestra al usuario. No hace ningún cálculo. JSP / HTML

5.1 Código completo: Servlet → Bean → JSP


El Servlet ([Link])

package [Link];

import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];

@WebServlet("/producto")
public class ProductoServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, [Link] {

// 1. Creamos el Bean y lo rellenamos con datos


// (en una app real estos datos vendrían de una base de datos)
Producto p = new Producto("Teclado mecánico", 89.99, 15);

// 2. Guardamos el Bean en el request para que la JSP lo encuentre


[Link]("producto", p);

// 3. Redirigimos a la JSP que mostrará los datos


[Link]("/WEB-INF/vistas/[Link]")
.forward(request, response);
}
}
IFCD0182 — Desarrollo Web con Java

La vista JSP (WEB-INF/vistas/[Link])

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<!-- Recuperamos el Bean que el Servlet guardó en el request -->


<jsp:useBean id="producto" class="[Link]" scope="request"/>

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Producto</title>
</head>
<body>
<h1><jsp:getProperty name="producto" property="nombre"/></h1>
<p>Precio: <jsp:getProperty name="producto" property="precio"/> €</p>
<p>Unidades disponibles: <jsp:getProperty name="producto" property="stock"/></p>
</body>
</html>

⚙ CÓMO FUNCIONA
¿Por qué la JSP está en WEB-INF/vistas/?
Las páginas dentro de WEB-INF no son accesibles directamente desde el navegador. Nadie puede
escribir [Link] y verla. Esto fuerza a que todas las
peticiones pasen siempre por el Servlet (el Controlador), que es exactamente lo que queremos en el
patrón MVC.

🤖 CON AYUDA DE LA IA
Comprende el flujo con IA
Si el flujo Servlet → Bean → JSP no te queda claro, prueba este prompt:

"Tengo este Servlet y esta JSP que usan un JavaBean Producto. Traza el flujo completo de lo que
ocurre cuando el usuario accede a /producto: qué línea de código se ejecuta primero, cuándo se
crea el Bean, cómo llega el Bean a la JSP y qué ve el usuario al final. Explícamelo paso a paso
como si fuera la primera vez que lo veo."
Pega el código del Servlet y la JSP junto con el prompt. La IA te trazará el flujo completo y podrás
compararlo con tu propia comprensión.
IFCD0182 — Desarrollo Web con Java

6. Errores frecuentes con JavaBeans

Error Causa Solución

ClassNotFoundException al El nombre de la clase en class= es Verifica que el package y el nombre de la


usar <jsp:useBean> incorrecto o el Bean no está clase coincidan exactamente.
compilado Reconstruye el proyecto (Build →
Rebuild).

El Bean se crea vacío (todos El scope no coincide: el Servlet Asegúrate de que el scope en
los valores null) guarda en request pero la JSP <jsp:useBean> coincide con el que usó el
busca en session Servlet en setAttribute.

Error: No getter for property X El nombre del atributo en Verifica que el nombre del atributo en la
property= no coincide con el getter etiqueta JSP sea exactamente igual al
del Bean nombre del atributo privado del Bean
(sin mayúsculas).

El setter de precio acepta un No hay validación en el setter Añade validación en el setter: if (precio <
String negativo 0) throw new
IllegalArgumentException(...)

No se puede instanciar el No existe el constructor sin Añade public NombreBean() {}


Bean argumentos explícitamente, sobre todo si tienes otro
constructor con parámetros.

El precio se muestra con double tiene precisión flotante Usa [Link]("%.2f", precio) para
muchos decimales (89.9999999...) formatear o cambia el tipo a BigDecimal
para aplicaciones financieras.

✘ ERROR FRECUENTE
El error más difícil de detectar:
Cuando el Bean aparece vacío (todos los atributos en null o 0), casi siempre es un problema de
scope. El Servlet y la JSP no están mirando al mismo sitio. Revisa siempre el scope antes de buscar
otro problema.
IFCD0182 — Desarrollo Web con Java

7. Buenas prácticas con JavaBeans

✔ BUENAS PRÁCTICAS
Sigue estas prácticas desde el principio:
• Un Bean, una responsabilidad: Un Bean representa un solo concepto del negocio
(Producto, Usuario, Pedido). No mezcles datos de conceptos distintos en el mismo Bean.
• Organiza los Beans en un paquete separado: Usa [Link] o [Link].
Nunca pongas Beans en el mismo paquete que los Servlets.
• Valida en los setters: Los setters son la puerta de entrada a los datos. Comprueba
rangos, nulos y formatos antes de asignar.
• Añade el método toString(): Es muy útil para depurar. Imprimir el Bean en la consola te
permite ver todos sus datos de un vistazo.
• Implementa Serializable cuando uses scope="session": Si el Bean vive en la sesión HTTP,
debe poder serializarse (implements Serializable). De lo contrario puede haber
problemas con clústeres de servidores.
• No metas lógica de negocio en el Bean: Un Bean guarda datos, no los procesa. La lógica
va en clases de servicio o en el Servlet.

📊 NOTA DE MERCADO LABORAL


JavaBeans clásicos vs. mundo actual:
En el mercado laboral actual los JavaBeans clásicos (con <jsp:useBean>) casi no se usan en proyectos
nuevos. Lo que sí se usa constantemente es el mismo concepto con otro nombre:

• En Spring Boot se llaman POJOs o Entidades JPA (con @Entity). Siguen exactamente las
mismas tres reglas.
• En APIs REST se llaman DTOs (Data Transfer Objects). También son Beans.
• Lombok, una librería muy popular, genera automáticamente los getters, setters y
constructores. El estándar sigue siendo el mismo por debajo.
Aprender JavaBeans correctamente ahora te da la base para entender Entidades, DTOs y Lombok
cuando llegues a Spring Boot en el Módulo 2.
IFCD0182 — Desarrollo Web con Java

8. Actividad práctica guiada

Título Ficha de empleado con Bean, Servlet y JSP

Duración 60 minutos

Objetivo Implementar el flujo completo Servlet → Bean → JSP con un caso de negocio distinto al
explicado

Nivel Guiado — el formador explica cada paso antes de que lo implementes

Enunciado
Crea una aplicación que muestre una ficha de empleado cuando el usuario accede a la URL /empleado. La
ficha debe contener: nombre, apellidos, departamento, salario y si está activo o no.

1 Crea el Bean Empleado en el paquete [Link]


Atributos: String nombre, String apellidos, String departamento, double salario, boolean
activo.

Incluye constructor vacío, constructor con todos los parámetros, getters y setters. El setter de
salario debe rechazar valores negativos.

Añade también un método toString() que devuelva todos los datos en una sola cadena. Es útil
para depurar.

2 Crea el Servlet EmpleadoServlet mapeado a /empleado


En el doGet, crea un Empleado con datos de prueba. Guárdalo en el request con
[Link]("empleado", e).

Redirige al dispatcher hacia /WEB-INF/vistas/[Link].

3 Crea la JSP [Link] en WEB-INF/vistas


Usa <jsp:useBean> para recuperar el Bean del request.

Muestra todos los campos en una tabla HTML. Para el campo activo, muestra "Activo" o
"Inactivo" según el valor del getter.

4 Prueba y verifica
Accede a [Link] Deberías ver la ficha completa.

Cambia los datos en el Servlet y recarga. Los cambios deben verse en la JSP inmediatamente.
IFCD0182 — Desarrollo Web con Java

5 Reflexiona
¿Qué pasaría si quisieras mostrar 10 empleados distintos? ¿Cómo cambiarías el Servlet? ¿Y la
JSP?

Comenta con el grupo antes de pasar a la actividad autónoma.

🤖 CON AYUDA DE LA IA
Si algo no funciona — usa la IA para depurar
Prueba este enfoque antes de pedir ayuda al formador:

"Mi Bean Empleado tiene todos los atributos a null cuando lo recojo en la JSP. El Servlet hace
[Link](\"empleado\", e) y el useBean en JSP tiene scope=\"request\". El código es:
[pega ambos archivos]. ¿Qué puede estar fallando?"
El proceso de describir el problema con precisión para la IA te ayuda a veces a encontrar el error tú
solo antes de que la IA responda. Es una técnica real que usan los desarrolladores profesionales.
IFCD0182 — Desarrollo Web con Java

9. Actividad práctica autónoma

Título Sistema de ficha de libro para una biblioteca

Duración 90 minutos

Objetivo Diseñar e implementar de forma autónoma un sistema con múltiples Beans y un Servlet
que gestione la lógica

Nivel Autónomo — apuntes, documentación y IA disponibles. El código completo no puede


venir de la IA.

Entrega Proyecto IntelliJ en .zip con código fuente y un comentario en cada clase que explique
qué hace

Enunciado
Desarrolla una aplicación web para una biblioteca que muestre la ficha completa de un libro al acceder a
/libro.

El Bean Libro debe incluir:


• título (String)
• autor (String)
• isbn (String) — el setter debe rechazar cadenas de menos de 10 caracteres
• anioPublicacion (int) — el setter debe rechazar años anteriores a 1450 (invención de la imprenta) y
posteriores al año actual
• disponible (boolean) — si el libro está disponible para préstamo
• numeroPaginas (int) — debe ser mayor que 0

El Servlet LibroServlet debe:


• Estar mapeado a /libro.
• Crear un objeto Libro con datos de prueba reales (busca un ISBN real si quieres).
• Guardar el libro en el request y redirigir a la JSP.

La JSP [Link] debe:


• Mostrar todos los campos del libro en una página HTML bien estructurada.
• Mostrar el campo disponible como "Disponible para préstamo" o "No disponible" (no como
true/false).
IFCD0182 — Desarrollo Web con Java

• Mostrar un mensaje diferente si el libro tiene más de 500 páginas: "Obra extensa" o simplemente el
número de páginas.
• Tener un diseño mínimamente cuidado: una tabla o una lista con estilos CSS básicos.

Criterios de evaluación
Criterio Descripción Peso

Bean correcto Las tres reglas cumplidas, validaciones en los setters 25%

Flujo Servlet → JSP El Servlet crea el Bean, lo pasa al request y redirige a 25%
la JSP

JSP limpia Sin código Java entre <% %>, solo etiquetas JSP 20%
estándar

Validaciones ISBN, año y páginas se validan con mensajes de error 15%


claros

Presentación HTML estructurado, legible y con CSS básico 10%

Código comentado Cada clase tiene un comentario de cabecera 5%


explicando su propósito

⚠ ATENCIÓN
Normas de uso de la IA en esta actividad:
• Permitido: preguntar a la IA por qué algo falla, pedir que te explique un concepto, pedir
ejemplos de validaciones.
• No permitido: pedir a la IA que escriba el Bean completo, el Servlet completo o la JSP
completa.
• Obligatorio: si usas la IA para resolver un problema concreto, añade un comentario en el
código indicando qué parte te ayudó a resolver y cómo.
IFCD0182 — Desarrollo Web con Java

10. Resumen de la unidad

Esta unidad cubre un concepto que parece sencillo pero que tiene consecuencias enormes en la calidad del
código. Repasa estos puntos antes de la siguiente sesión:

Concepto Lo esencial

JavaBean Clase Java con constructor vacío, atributos privados y getters/setters públicos.
Ninguna librería extra necesaria.

Las tres reglas 1) Constructor sin args. 2) Atributos privados. 3) Getters y setters públicos con
el nombre correcto.

Getters / Setters get + NombreAtributo para leer. set + NombreAtributo para escribir. Para
boolean: is + NombreAtributo.

<jsp:useBean> Declara o recupera el Bean en un ámbito (scope). class= debe ser el nombre
completo con paquete.

<jsp:getProperty> Lee el valor de un atributo del Bean e inserta el resultado en el HTML.

<jsp:setProperty> Asigna un valor a un atributo del Bean desde la JSP.

scope page (solo esta JSP), request (esta petición), session (usuario), application (toda
la app).

Flujo correcto Servlet crea el Bean → guarda en request → redirige a JSP → JSP lee el Bean y
muestra los datos.

Separación de capas El Bean transporta datos. El Servlet procesa la lógica. La JSP solo muestra.
Ninguno invade el territorio del otro.

Conexión con el futuro Los JavaBeans son la base conceptual de las Entidades JPA, DTOs y Beans de
Spring Boot que verás en el Módulo 2.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación — respóndelas sin mirar los apuntes:
1. ¿Cuáles son las tres reglas que debe cumplir un JavaBean?
2. ¿Cómo se llama el getter del atributo apellidos? ¿Y el de activo (boolean)?
3. ¿Por qué conviene tener las JSPs dentro de WEB-INF?
4. ¿Qué scope usarías si quieres que el Bean sobreviva entre peticiones del mismo usuario?
5. ¿Qué diferencia hay entre <jsp:getProperty> y <jsp:setProperty>?
6. ¿Por qué no conviene meter lógica de negocio dentro de un JavaBean?

🤖 CON AYUDA DE LA IA
Consolida lo aprendido — prompt de repaso
Usa este prompt para afianzar los conceptos esta tarde o antes de la próxima clase:
IFCD0182 — Desarrollo Web con Java

"Actúa como formador de Java Web. Tengo claro el concepto de JavaBean y el flujo Servlet →
Bean → JSP. Hazme 4 preguntas de dificultad creciente sobre JavaBeans: la primera muy básica,
la última que me haga pensar. Después de que yo responda cada una, corrígeme y explícame si
hay algo que mejorar en mi respuesta."
Si hay algo que no puedas responder en la sesión de repaso, anótalo y tráelo a la próxima clase. Es
mucho mejor saber qué no sabes que creer que lo sabes cuando no es así.

📊 NOTA DE MERCADO LABORAL


¿Y JSF? Una nota honesta sobre el mercado:
En este módulo veremos también JSF (JavaServer Faces), que usa los JavaBeans como "Managed
Beans". JSF sigue siendo válido y muchas empresas —especialmente las más grandes y con sistemas
legacy— lo usan. Sin embargo, es una tecnología en declive: pocas empresas la piden para nuevas
contrataciones.

Lo que el mercado pide hoy para perfiles Java junior-medio es Spring MVC y Thymeleaf, que verás en
el Módulo 2. El tiempo que dediques a JSF en este módulo te servirá para entender los conceptos —
ciclo de vida, Managed Beans, componentes— que luego se aplican de forma más moderna en
Spring.

Dicho de otra forma: aprende JSF para entender cómo funciona el ecosistema Java Web; trabaja con
Spring MVC para encontrar empleo.

MÓDULO 1 — Introducción al Desarrollo Web con Java


UNIDAD 4

Seguridad básica en aplicaciones JSP


XSS, inyección SQL, CSRF y gestión segura de sesiones: lo que todo desarrollador Java debe
saber desde el primer día

Duración estimada 6 horas (teoría + prácticas)

Módulo 1 — Introducción al Desarrollo Web con Java

Unidad anterior Unidad 3 — JavaBeans: separar datos de lógica

Nivel de entrada Servlet y JSP funcionando. Bean conectado con JSP mediante Servlet

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Las vulnerabilidades más comunes en aplicaciones web y cómo proteger el código
Java frente a ellas desde el principio
IFCD0182 — Desarrollo Web con Java

✔ OBJETIVOS DE LA UNIDAD
Al terminar esta unidad serás capaz de:
• Identificar las vulnerabilidades web más comunes (XSS, SQL Injection, CSRF).
• Explicar cómo funcionan estos ataques con ejemplos concretos.
• Aplicar medidas de protección en código JSP y Servlet.
• Usar PreparedStatement para eliminar el riesgo de SQL Injection.
• Gestionar sesiones HTTP de forma segura.
• Validar y sanitizar la entrada del usuario correctamente.
• Evaluar el nivel de seguridad básico de una aplicación JSP existente.

⚠ ANTES DE EMPEZAR — LECTURA OBLIGATORIA


La seguridad no es un módulo que se añade al final. Es una forma de programar desde la primera
línea. Los errores de seguridad más graves no vienen de código complejo: vienen de descuidos
sencillos que cualquier desarrollador puede cometer.

Esta unidad te enseña a pensar como alguien que quiere proteger su aplicación, no solo a hacer que
funcione. Ese cambio de mentalidad es lo que distingue un código amateur de uno profesional.
IFCD0182 — Desarrollo Web con Java

1. Introducción: ¿por qué importa la seguridad desde el


principio?

Las aplicaciones web son, por definición, accesibles desde cualquier parte del mundo. Eso significa que
cualquier persona puede intentar atacarlas: no solo usuarios malintencionados, sino también scripts
automatizados que escanean la web buscando vulnerabilidades conocidas.

La buena noticia es que la mayoría de los ataques exitosos no explotan vulnerabilidades complejas. Explotan
errores básicos que se podrían haber evitado con unos pocos cambios en el código.

💼 EN EL TRABAJO REAL
Según el informe Verizon Data Breach Investigations Report, más del 70% de las brechas de
seguridad en aplicaciones web se deben a vulnerabilidades catalogadas y conocidas. No son ataques
sofisticados: son errores que llevan décadas documentados y que siguen apareciendo en código
nuevo.

Las empresas esperan que un desarrollador junior conozca al menos las vulnerabilidades del OWASP
Top 10 y sepa cómo evitarlas. Es una de las preguntas más frecuentes en entrevistas técnicas para
perfiles Java web.

1.1 OWASP: el estándar de referencia en seguridad web


OWASP (Open Web Application Security Project) es una fundación sin ánimo de lucro que publica el Top 10
de vulnerabilidades web más críticas. Es el estándar de referencia en la industria. En esta unidad nos
centraremos en las más relevantes para aplicaciones JSP:

Vulnerabilidad ¿En qué consiste? Riesgo si no se protege

XSS (Cross-Site Scripting) Inyección de scripts maliciosos en la Robo de sesión, redirección


salida HTML maliciosa, daño a la reputación

SQL Injection Manipulación de consultas SQL mediante Robo de toda la base de datos,
entrada del usuario eliminación de datos, acceso no
autorizado

CSRF Peticiones falsificadas que el servidor Acciones no autorizadas en nombre


procesa como legítimas del usuario (transferencias, cambios
de contraseña)

Gestión insegura de Tokens de sesión predecibles, sin Suplantación de identidad, secuestro


sesiones expiración o transmitidos sin HTTPS de sesión

Exposición de datos Datos confidenciales sin cifrar o Filtración de contraseñas, tarjetas de


sensibles expuestos en logs y URLs crédito, datos personales
IFCD0182 — Desarrollo Web con Java

2. XSS — Cross-Site Scripting

2.1 Qué es y cómo funciona


XSS ocurre cuando una aplicación web incluye en su salida HTML datos que provienen del usuario sin
procesarlos de forma segura. El resultado: el navegador ejecuta código JavaScript que el atacante ha
inyectado como si fuera código legítimo de tu aplicación.

⚠ ATENCIÓN
XSS no ataca al servidor. Ataca al navegador del usuario que visita tu aplicación. Si inyectan
JavaScript malicioso en tu página, ese script se ejecuta en el navegador de cada usuario que la visite.

Ejemplo de ataque XSS — cómo ocurre


Imagina un formulario de búsqueda. El usuario escribe algo y la página muestra: "Resultados para: [lo que
escribió el usuario]". Si el código JSP hace esto:

<!-- CÓDIGO VULNERABLE: muestra directamente lo que el usuario escribió -->


<p>Resultados para: <%= [Link]("busqueda") %></p>

Un atacante no escribe una búsqueda normal. Escribe esto:

<script>[Link]='[Link]
>

La JSP incluye ese script en el HTML. El navegador de la víctima lo ejecuta. En milisegundos, la cookie de
sesión del usuario ha sido enviada al servidor del atacante, que puede usarla para suplantar su identidad.
IFCD0182 — Desarrollo Web con Java

2.2 Cómo protegerse del XSS


La defensa principal contra XSS es escapar la salida: transformar los caracteres especiales de HTML en sus
equivalentes seguros antes de incluirlos en la página.

❌ CÓDIGO VULNERABLE ✅ CÓDIGO SEGURO


<%= [Link]("nombre") %> <c:out value="${[Link]}"/>
[Link]("Hola " + nombre); [Link]("Hola " +
escapeHtml(nombre));

Método 1: usar <c:out> de JSTL (recomendado en JSP)


La etiqueta <c:out> de la librería JSTL escapa automáticamente los caracteres peligrosos. Es la forma más
sencilla y limpia de protegerse en JSP:

<%@ taglib prefix="c" uri="[Link] %>

<!-- VULNERABLE: ejecuta cualquier script que venga en el parámetro -->


<p>Hola, ${[Link]}</p>

<!-- SEGURO: escapa < > & " ' antes de incluirlos en el HTML -->
<p>Hola, <c:out value="${[Link]}"/></p>

<!-- ¿Qué hace <c:out> exactamente? Convierte: -->


<!-- < → &lt; > → &gt; & → &amp; -->
<!-- " → &quot; ' → &#039; -->
<!-- El navegador muestra el texto, pero no lo ejecuta. -->

Método 2: escapar en el Servlet con ESAPI o una función propia


Cuando generas HTML en un Servlet, usa una librería de escape o implementa una función de sanitización:

// Función sencilla de escape para HTML


private String escapeHtml(String texto) {
if (texto == null) return "";
return texto
.replace("&", "&amp;") // SIEMPRE primero
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#039;");
}

// Uso en el doGet:
String nombreSeguro = escapeHtml([Link]("nombre"));
[Link]("<p>Hola, " + nombreSeguro + "</p>");
IFCD0182 — Desarrollo Web con Java

Content Security Policy (CSP): la segunda línea de defensa


Además de escapar la salida, puedes indicar al navegador que no ejecute scripts de orígenes no confiables
mediante la cabecera HTTP Content-Security-Policy:

// En el Servlet, añadir esta cabecera a la respuesta:


[Link]("Content-Security-Policy",
"default-src 'self'; script-src 'self'");

// Esto le dice al navegador:


// - Solo carga recursos (scripts, estilos, imágenes) del mismo origen
// - No ejecutes scripts inline a menos que vengan del servidor

🤖 CON AYUDA DE LA IA
Usa este prompt para entender XSS en profundidad antes de seguir:

"Explícame cómo funciona un ataque XSS reflejado paso a paso, usando como ejemplo una JSP de
Java que muestra el resultado de una búsqueda. Muéstrame el código vulnerable, el código del
atacante y el código corregido. Hazlo como si yo fuera desarrollador junior y no hubiera visto esto
antes."
Después de leer la respuesta, compárala con esta sección. Si la IA incluye algo que aquí no está,
anótalo y coméntalo en clase.
IFCD0182 — Desarrollo Web con Java

3. SQL Injection — La vulnerabilidad más peligrosa

3.1 Qué es y por qué es tan grave


SQL Injection ocurre cuando se construye una consulta SQL concatenando directamente los datos que
provienen del usuario. El atacante puede modificar la lógica de la consulta para saltarse autenticaciones, leer
tablas enteras o incluso eliminar la base de datos.

⚠ ATENCIÓN
SQL Injection encabeza el OWASP Top 10 desde hace más de una década. Es la causa de algunas de
las mayores filtraciones de datos de la historia. Y sigue apareciendo en código nuevo porque el error
es tentador de cometer.

Cómo funciona el ataque — ejemplo de login vulnerable


El siguiente código construye una consulta SQL concatenando el usuario y la contraseña que llegan del
formulario:

// CÓDIGO VULNERABLE — nunca hagas esto


String usuario = [Link]("usuario");
String password = [Link]("password");

String sql = "SELECT * FROM usuarios WHERE usuario='" + usuario


+ "' AND password='" + password + "'";

// Si usuario = admin'-- la consulta se convierte en:


// SELECT * FROM usuarios WHERE usuario='admin'--' AND password='lo que sea'
// El -- comenta el resto. El atacante entra sin contraseña.

✘ ERROR FRECUENTE
Muchos estudiantes piensan que validar la longitud del campo o filtrar algunos caracteres es
suficiente. No lo es. Un atacante con creatividad puede saltarse casi cualquier filtro ad-hoc. La
solución correcta no es filtrar: es usar PreparedStatement.
IFCD0182 — Desarrollo Web con Java

3.2 La solución: PreparedStatement


PreparedStatement separa la estructura de la consulta SQL de los datos del usuario. El motor de base de
datos procesa primero la estructura y luego inserta los datos como valores literales, sin posibilidad de
modificar la lógica de la consulta.

// CÓDIGO SEGURO con PreparedStatement


String usuario = [Link]("usuario");
String password = [Link]("password");

// 1. Definimos la consulta con ? como marcadores de posición


String sql = "SELECT * FROM usuarios WHERE usuario = ? AND password = ?";

// 2. Preparamos la consulta (el motor SQL la compila aquí)


PreparedStatement stmt = [Link](sql);

// 3. Asignamos los valores. Aunque contengan SQL, se tratan como texto.


[Link](1, usuario); // primer ?
[Link](2, password); // segundo ?

// 4. Ejecutamos
ResultSet rs = [Link]();

// Si usuario = admin'-- el motor lo trata como texto literal


// y busca exactamente ese string en la columna usuario.
// No puede modificar la lógica de la consulta.

Regla de oro: siempre PreparedStatement


Situación ¿Usar Por qué
PreparedStatement?

Consulta con datos del usuario Sí, siempre Cualquier dato del usuario puede ser
(GET/POST) malicioso

Consulta con datos que vienen de la Sí, siempre Los datos en BD pueden haber sido
base de datos manipulados previamente

Consulta con datos que tú mismo has Sí, igualmente Hábito profesional. Siempre
escrito en el código PreparedStatement.

Consulta construida dinámicamente Necesita validación extra Los ? no sirven para nombres de
(ORDER BY, nombres de tabla) tabla/columna. Valida contra una lista
blanca.

💡 CONSEJO
En el mundo real, casi nadie usa JDBC directamente en producción. Se usa JPA/Hibernate (en Spring
Boot), que por defecto usa PreparedStatement internamente. Pero entender PreparedStatement es
IFCD0182 — Desarrollo Web con Java

fundamental para saber qué está haciendo el framework por ti y para detectar cuándo alguien lo
está usando mal.

🤖 CON AYUDA DE LA IA
Experimenta con SQL Injection de forma segura usando IA:

"Tengo este código JDBC vulnerable a SQL Injection: [pega el código]. Muéstrame exactamente
cómo un atacante podría explotarlo con un ejemplo de input malicioso. Luego corrígelo usando
PreparedStatement y explícame por qué la versión corregida es inmune."
Hacer este ejercicio con la IA te permite ver el ataque completo sin necesitar un entorno de ataque
real. Es una técnica de aprendizaje que usan muchos formadores de seguridad.
IFCD0182 — Desarrollo Web con Java

4. CSRF — Cross-Site Request Forgery

4.1 Cómo funciona CSRF


CSRF (falsificación de petición en sitios cruzados) ocurre cuando un sitio web malicioso envía peticiones a tu
aplicación aprovechando que el usuario tiene la sesión abierta. El servidor no puede distinguir si la petición
viene de tu aplicación o de la web maliciosa.

👁 FÍJATE
Escenario de ataque CSRF:
1. El usuario inicia sesión en tu banco online ([Link]). Su navegador guarda la cookie
de sesión.
2. Sin cerrar sesión, el usuario visita una web maliciosa ([Link]).
3. Esa web contiene un formulario oculto que apunta a [Link]/transferir.
4. El formulario se envía automáticamente con JavaScript. El navegador incluye la cookie de
sesión.
5. El banco procesa la transferencia porque la cookie parece legítima.
6. El usuario acaba de transferir dinero sin saberlo.

4.2 Protección: el token CSRF


La defensa estándar es incluir en cada formulario un token secreto, único por sesión, que el servidor verifica
antes de procesar la petición. Un atacante externo no puede conocer ese token.

Implementación básica de token CSRF en Java

// En el Servlet que genera el formulario:


@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

// 1. Generar un token aleatorio seguro


String csrfToken = [Link]().toString();

// 2. Guardarlo en la sesión del usuario


[Link]().setAttribute("csrfToken", csrfToken);

// 3. Pasarlo a la JSP para incluirlo en el formulario


[Link]("csrfToken", csrfToken);
[Link]("/WEB-INF/vistas/[Link]")
.forward(request, response);
}
IFCD0182 — Desarrollo Web con Java

// En el Servlet que procesa el formulario (doPost):


@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

// 4. Recuperar el token enviado con el formulario


String tokenFormulario = [Link]("csrfToken");

// 5. Recuperar el token guardado en sesión


String tokenSesion = (String) [Link]().getAttribute("csrfToken");

// 6. Comparar — si no coinciden, rechazar la petición


if (tokenFormulario == null || ![Link](tokenSesion)) {
[Link](HttpServletResponse.SC_FORBIDDEN, "Petición inválida");
return;
}

// 7. Si coinciden, procesar el formulario normalmente


// ...
}

<!-- En la JSP del formulario, incluir el token como campo oculto -->
<form method="post" action="/procesar">
<input type="hidden" name="csrfToken" value="${csrfToken}"/>
<!-- resto de campos del formulario -->
<button type="submit">Enviar</button>
</form>

✔ BUENAS PRÁCTICAS
• Regenera el token en cada sesión: Nunca uses el mismo token para siempre. Genera uno
nuevo cuando el usuario inicia sesión.
• Tiempo de vida limitado: En aplicaciones sensibles, el token debería expirar después de
un tiempo o después de usarse.
• HTTPS obligatorio: Sin HTTPS, un atacante en la red puede interceptar el token. CSRF y
XSS protegen de poco sin HTTPS.
• En Spring Boot esto es automático: Spring Security incluye protección CSRF por defecto.
Lo verás en el Módulo 3.
IFCD0182 — Desarrollo Web con Java

5. Gestión segura de sesiones HTTP

La sesión HTTP es el mecanismo que permite que el servidor recuerde quién es el usuario entre peticiones. Si
la sesión no está bien gestionada, un atacante puede robarla o falsificarla, suplantando la identidad de
cualquier usuario.

5.1 Cómo funciona la sesión en Java


Cuando un usuario inicia sesión, el servidor crea un objeto HttpSession y le asigna un identificador único
(Session ID) que se envía al navegador como cookie. En cada petición posterior, el navegador envía esa
cookie y el servidor identifica al usuario.

⚙ CÓMO FUNCIONA
• El servidor crea la sesión: HttpSession sesion = [Link]();
• El Session ID se envía al navegador en la cookie JSESSIONID.
• En cada petición, el navegador envía la cookie y el servidor recupera la sesión.
• Si un atacante obtiene el JSESSIONID, puede hacerse pasar por ese usuario.

5.2 Prácticas de seguridad en la gestión de sesiones


Práctica 1: Regenerar el ID de sesión tras el login
Cuando un usuario se autentica, el Session ID anterior puede haber sido observado. Genera uno nuevo
inmediatamente después del login exitoso:

// Proceso de login en el Servlet


String usuario = [Link]("usuario");
String password = [Link]("password");

if (autenticacionCorrecta(usuario, password)) {

// Invalidar la sesión anterior (regenera el ID)


[Link]().invalidate();

// Crear una nueva sesión con ID diferente


HttpSession sesionNueva = [Link](true);
[Link]("usuarioLogueado", usuario);

[Link]("/dashboard");
} else {
[Link]("error", "Credenciales incorrectas");
[Link]("/[Link]").forward(request, response);
}
IFCD0182 — Desarrollo Web con Java

Práctica 2: Establecer tiempo de expiración de sesión


Una sesión que no expira nunca es un riesgo. Si el usuario olvida cerrar sesión en un ordenador público, su
cuenta queda accesible indefinidamente:

// Opción 1: En el código Java (por sesión concreta)


[Link](1800); // 30 minutos en segundos

// Opción 2: En [Link] (para toda la aplicación)


<session-config>
<session-timeout>30</session-timeout> <!-- en minutos -->
</session-config>

Práctica 3: Cerrar la sesión correctamente


@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {

// Invalidar la sesión elimina todos sus datos y el ID


HttpSession sesion = [Link](false);
if (sesion != null) {
[Link]();
}

// Eliminar la cookie de sesión del navegador


Cookie cookie = new Cookie("JSESSIONID", "");
[Link](0);
[Link]("/");
[Link](cookie);

[Link]("/login");
}
}

Práctica 4: Configurar la cookie de sesión como segura


Añade esto en [Link] para que la cookie JSESSIONID solo se envíe por HTTPS y no sea accesible desde
JavaScript:

<session-config>
<session-timeout>30</session-timeout>
<cookie-config>
<http-only>true</http-only> <!-- no accesible desde JS: evita XSS -->
<secure>true</secure> <!-- solo por HTTPS -->
</cookie-config>
</session-config>
IFCD0182 — Desarrollo Web con Java

Atributo de cookie Qué hace Por qué es importante

HttpOnly La cookie no es accesible desde Evita que XSS robe la cookie de sesión
JavaScript

Secure La cookie solo se envía por HTTPS Evita que se transmita en texto plano

SameSite Controla desde qué sitios se envía la Mitiga ataques CSRF


cookie

6. Validación y sanitización de la entrada del usuario

La regla fundamental de seguridad web es: nunca confíes en los datos que llegan del cliente. Ni parámetros
GET, ni campos POST, ni cabeceras HTTP, ni cookies. Todo puede haber sido manipulado.

👁 FÍJATE
Diferencia clave entre validar y sanitizar:
• Validar: Comprobar que el dato cumple las reglas esperadas. Si no las cumple,
rechazarlo. Ejemplo: el email debe tener @ y un dominio válido.
• Sanitizar: Transformar el dato para eliminar o escapar los caracteres peligrosos. Ejemplo:
convertir < en &lt; para evitar XSS.
En una aplicación segura se hace ambas cosas. Primero se valida; si pasa la validación, se sanitiza
antes de usarlo.

6.1 Validación en el Servlet

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

String nombre = [Link]("nombre");


String email = [Link]("email");
String edadStr = [Link]("edad");

// Validación 1: campos obligatorios no vacíos


if (nombre == null || [Link]().isEmpty()) {
[Link]("error", "El nombre es obligatorio");
[Link]("/[Link]").forward(request, response);
return;
}

// Validación 2: longitud máxima (evita ataques de desbordamiento)


IFCD0182 — Desarrollo Web con Java
if ([Link]() > 100) {
[Link]("error", "El nombre no puede superar 100 caracteres");
[Link]("/[Link]").forward(request, response);
return;
}

// Validación 3: formato de email con expresión regular


if (![Link]("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$")) {
[Link]("error", "Email con formato incorrecto");
[Link]("/[Link]").forward(request, response);
return;
}

// Validación 4: tipo de dato correcto


int edad;
try {
edad = [Link](edadStr);
if (edad < 0 || edad > 120) throw new NumberFormatException();
} catch (NumberFormatException e) {
[Link]("error", "Edad no válida");
[Link]("/[Link]").forward(request, response);
return;
}

// Si llegamos aquí, los datos son válidos. Continuamos.


}

6.2 Nunca expongas información sensible en mensajes de error

❌ CÓDIGO VULNERABLE ✅ CÓDIGO SEGURO


Error: [Link]: Unknown Ha ocurrido un error. Inténtalo de
column 'usuarios' nuevo.
Error: conexion fallida en El servicio no está disponible en este
jdbc:mysql://[Link]:3306 momento.
NullPointerException en línea 47 de Error interno. Si el problema persiste,
[Link] contacta con soporte.

✘ ERROR FRECUENTE
Mostrar el stack trace de Java directamente en la página web revela la estructura interna del código,
las rutas de los archivos, las versiones de las librerías y, a veces, datos de conexión. Un atacante
agradece cada detalle.

Configura en [Link] páginas de error personalizadas para los códigos 404, 500 y para las
excepciones más comunes. Loguea el detalle del error en el servidor (no en la respuesta) para poder
depurarlo después.
IFCD0182 — Desarrollo Web con Java

7. Errores frecuentes de seguridad en JSP

Error Por qué es un problema Cómo corregirlo

Usar ${param.x} o <%= Permite XSS si el parámetro Usa <c:out value="${param.x}"/> o


[Link]() %> contiene scripts escapeHtml() siempre
directamente en el HTML

Construir SQL concatenando SQL Injection: el atacante controla Usa PreparedStatement con ? para todos
parámetros del usuario la consulta los valores externos

Guardar datos sensibles Aparecen en logs del servidor, Usa POST para datos sensibles. Nunca en
(contraseñas, tokens) en URL historial del navegador, headers URL.
o parámetros GET Referer

No invalidar la sesión al hacer El Session ID sigue siendo válido. Llama siempre a [Link]() en
logout Cualquiera que lo tenga puede el logout
usarlo

No poner timeout a la sesión Una sesión abierta en un PC Configura session-timeout en [Link]


público puede usarse (máx 30 minutos para datos sensibles)
indefinidamente

Mostrar errores detallados de Revela estructura interna, rutas, Captura excepciones, loguéalas en
Java en la respuesta HTTP versiones de librerías servidor, muestra página de error
genérica

No validar el tipo ni el rango Puede causar errores internos o Usa [Link]() en try-catch y
de los parámetros numéricos comportamientos inesperados valida el rango esperado
IFCD0182 — Desarrollo Web con Java

8. Buenas prácticas de seguridad en aplicaciones JSP

✔ BUENAS PRÁCTICAS
Lista de verificación de seguridad básica:
• Escapa siempre la salida: Todo lo que provenga del usuario y se muestre en HTML debe
pasar por <c:out> o una función de escape.
• PreparedStatement siempre: Ninguna consulta SQL construida concatenando strings, sin
excepciones.
• Valida la entrada en el servidor: La validación JavaScript del lado cliente es útil para UX,
pero no es seguridad. Un atacante puede desactivar JavaScript. Valida siempre en el
servidor.
• Principio de mínimo privilegio: El usuario de base de datos que usa la aplicación debe
tener solo los permisos que necesita: SELECT, INSERT, UPDATE en las tablas concretas.
Nunca root ni admin.
• HTTPS en producción: Sin HTTPS, todo lo que viaja entre cliente y servidor puede ser
interceptado, incluidas cookies de sesión y contraseñas.
• No confíes en el cliente: Validaciones HTML5 (required, maxlength, type), JavaScript y
CSS son para el usuario. Un atacante las ignora.
• Logs de seguridad: Registra intentos de login fallidos, errores de validación masivos y
accesos a rutas no autorizadas. Te avisarán de un ataque en curso.
• Contraseñas con hash: Nunca almacenes contraseñas en texto plano. Usa BCrypt. Lo
verás en profundidad en el Módulo 3 con Spring Security.

📊 NOTA DE MERCADO LABORAL


Seguridad en el mercado laboral:
El conocimiento de seguridad básica diferencia candidatos en las entrevistas. Empresas que trabajan
con datos de usuarios (banca, salud, ecommerce) dan mucho peso a este área.

El Módulo 3 de este curso profundizará en Spring Security, JWT, OAuth2 y OWASP con más detalle.
Lo que aprendes aquí es la base conceptual sin la que Spring Security no tiene sentido.

Herramienta recomendada: OWASP ZAP (Zed Attack Proxy). Es gratuita, open source, y te permite
escanear tu propia aplicación en busca de vulnerabilidades. Muchas empresas la usan en su pipeline
de CI/CD.
IFCD0182 — Desarrollo Web con Java

9. Actividad práctica guiada

Título Auditoría y corrección de una aplicación JSP insegura

Duración 60 minutos

Objetivo Identificar vulnerabilidades en código existente y aplicar las correcciones aprendidas

Nivel Guiado — el formador analiza el primer bloque con el grupo antes de que cada uno
trabaje el resto

Código a auditar — [Link] (voluntariamente inseguro)


El siguiente código tiene múltiples vulnerabilidades. Identifícalas antes de leer la guía:

@WebServlet("/buscar")
public class BuscadorServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {

String termino = [Link]("q");


String sql = "SELECT * FROM productos WHERE nombre LIKE '%" + termino + "%'";

try {
Connection con = [Link](
"jdbc:mysql://localhost/tienda", "root", "admin123");
Statement st = [Link]();
ResultSet rs = [Link](sql);

[Link]("resultados", rs);
[Link]("termino", termino);
[Link]("/[Link]").forward(req, res);
} catch (Exception e) {
[Link]().println("Error: " + [Link]());
}
}
}

<!-- [Link] -->


<p>Resultados para: ${param.q}</p>
<c:forEach var="p" items="${resultados}">
<div>${[Link]} — ${[Link]}</div>
</c:forEach>
IFCD0182 — Desarrollo Web con Java

1 Identifica todas las vulnerabilidades (trabaja solo 5 minutos antes de continuar)


Escribe en tu cuaderno cuántas encuentras y de qué tipo. Luego compara con la guía.

2 Vulnerabilidades encontradas — guía del formador


• SQL Injection: la consulta concatena directamente el parámetro q. Un atacante
puede leer toda la BD.
• XSS: ${param.q} en la JSP muestra el parámetro sin escapar. Permite inyectar
scripts.
• Credenciales hardcodeadas: usuario root y contraseña admin123 en el código
fuente. Cualquiera que vea el código tiene acceso a la BD.
• Error expuesto: [Link]() se muestra directamente en la respuesta HTTP,
revelando detalles internos.
• Sin validación de entrada: El parámetro q puede ser nulo, vacío o de longitud
arbitraria.

3 Corrige el BuscadorServlet aplicando lo aprendido


Reescribe el Servlet usando PreparedStatement, validación de entrada y manejo seguro de
errores. Las credenciales deben ir en un archivo de configuración externo ([Link].,
[Link]).

4 Corrige la JSP
Sustituye ${param.q} por la forma segura usando <c:out>.

5 Prueba la versión corregida


Intenta introducir en el campo de búsqueda: <script>alert('XSS')</script>

Si aparece una ventana de alerta, el XSS no está corregido. Si el texto se muestra literalmente
sin ejecutarse, la corrección es correcta.

🤖 CON AYUDA DE LA IA
Después de hacer tu corrección, compárala con la IA:

"He corregido este Servlet Java que tenía vulnerabilidades de seguridad. Mi versión corregida es:
[pega tu código]. ¿Hay alguna vulnerabilidad que me haya dejado sin corregir o alguna mejora de
seguridad adicional que recomendarías?"
IFCD0182 — Desarrollo Web con Java

La IA puede ver ángulos que se te han escapado. Pero primero intenta la corrección por tu cuenta:
así el aprendizaje es tuyo.
IFCD0182 — Desarrollo Web con Java

10. Actividad práctica autónoma

Título Sistema de login seguro con gestión de sesiones

Duración 90 minutos

Objetivo Implementar desde cero un sistema de autenticación con todas las medidas de
seguridad aprendidas

Nivel Autónomo — apuntes, documentación oficial y IA disponibles

Entrega Proyecto IntelliJ en .zip. Incluir un archivo [Link] que liste las medidas
implementadas

Enunciado
Crea una aplicación web con las siguientes pantallas y funcionalidades:

1. Página de login (/login)


• Formulario con campos usuario y contraseña.
• Validación del lado servidor: ningún campo vacío, longitud máxima de 50 caracteres.
• Protección CSRF con token en el formulario.
• Mensajes de error genéricos (no reveles si el usuario existe o no: di solo «Credenciales incorrectas»).
• Usuarios de prueba guardados en un HashMap en memoria (para esta práctica no hace falta BD real).

2. Dashboard protegido (/dashboard)


• Solo accesible si el usuario ha iniciado sesión. Si no, redirigir a /login.
• Muestra el nombre del usuario logueado (leído de la sesión).
• Botón de cerrar sesión que invalida la sesión correctamente.
• Al regenerar la URL directamente sin sesión, no debe ser accesible.

3. Medidas de seguridad obligatorias


• Token CSRF en el formulario de login verificado en el Servlet.
• Session ID regenerado tras login exitoso.
• Timeout de sesión configurado a 15 minutos.
• Cookie de sesión configurada como HttpOnly.
• Todos los datos del usuario mostrados en la JSP escapados con <c:out>.
• Errores internos capturados con try-catch y no expuestos en la respuesta.
IFCD0182 — Desarrollo Web con Java

Criterios de evaluación
Criterio Descripción Peso

Login funcional Autenticación correcta con usuario y contraseña 20%


válidos

Protección CSRF Token generado, incluido en formulario y 20%


verificado en Servlet

Gestión de sesión Regeneración de ID, timeout, logout correcto, 25%


acceso protegido

XSS prevenido Toda salida en JSP pasa por <c:out> 15%

Validación de entrada Campos obligatorios, longitud máxima, errores 10%


genéricos

[Link] Archivo que lista cada medida implementada 10%


con una línea de explicación

⚠ ATENCIÓN
Normas de uso de la IA:
• Permitido: preguntar a la IA cómo funciona una medida de seguridad concreta, pedir que
te explique un error, pedir ejemplos de código que no sean la solución completa.
• No permitido: pedir a la IA que escriba el sistema de login completo.
• Obligatorio: el archivo [Link] debe estar escrito por ti, no por la IA. Describe
con tus propias palabras qué has implementado y por qué.
IFCD0182 — Desarrollo Web con Java

11. Resumen de la unidad

Concepto Lo esencial

XSS Inyección de scripts en la salida HTML. Se previene escapando con <c:out> o


una función de escape antes de mostrar cualquier dato del usuario.

SQL Injection Manipulación de consultas SQL. Se previene con PreparedStatement


siempre, sin excepción.

CSRF Peticiones falsas enviadas en nombre del usuario. Se previene con tokens
CSRF únicos por sesión incluidos en cada formulario.

Gestión de sesiones Regenera el Session ID tras login, establece timeout, invalida la sesión en
logout, usa HttpOnly y Secure en la cookie.

Validación de entrada Valida en el servidor, no solo en el cliente. Rechaza datos que no cumplan el
formato esperado. Establece longitud máxima siempre.

Mensajes de error Muestra mensajes genéricos al usuario. Loguea el detalle en el servidor.


Nunca expongas stack traces ni detalles técnicos.

OWASP Top 10 El estándar de referencia en vulnerabilidades web. Conocerlo es un requisito


mínimo en cualquier empresa de desarrollo.

Principio de desconfianza Todo dato que viene del cliente puede ser malicioso. Valida y sanitiza
siempre, aunque parezca imposible que llegue manipulado.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación — sin mirar los apuntes:
7. ¿Qué es XSS y con qué etiqueta JSP se previene?
8. ¿Por qué concatenar parámetros en un String SQL es peligroso? ¿Qué debes usar en su
lugar?
9. Explica en dos frases cómo funciona un ataque CSRF.
10. ¿Qué hace [Link]() y cuándo debes llamarlo?
11. ¿Qué diferencia hay entre validar y sanitizar la entrada?
12. ¿Por qué no debes mostrar [Link]() en la respuesta HTTP?

🤖 CON AYUDA DE LA IA
Prompt de repaso — seguridad web
Usa este prompt para consolidar antes de la próxima clase:

"Actúa como revisor de código de seguridad. Voy a mostrarte fragmentos de código JSP y Servlet
Java. Para cada uno dime si es seguro o no, qué vulnerabilidad tiene si la hay, y cómo lo
corregirías. Empieza con preguntas fáciles y ve subiendo la dificultad. Yo te digo si estoy listo para
el siguiente."
Esta dinámica de revisor de código con la IA simula una de las tareas más habituales en equipos de
desarrollo profesional: el code review. Practicarlo ahora te prepara para hacerlo en el trabajo.
IFCD0182 — Desarrollo Web con Java

📊 NOTA DE MERCADO LABORAL


Conexión con el Módulo 3:
En el Módulo 3 (Seguridad, Testing y DevOps) ampliarás todo lo visto aquí con Spring Security:
autenticación basada en roles, JWT (JSON Web Tokens), OAuth2 y protección CSRF automática.
Spring Security automatiza muchas de las medidas que aquí has implementado manualmente.

Haber implementado todo esto a mano en esta unidad es lo que te permitirá entender realmente
qué está haciendo Spring Security por ti cuando lo uses, y detectar cuándo está mal configurado.
IFCD0182 — Desarrollo Web con Java

MÓDULO 1 — Introducción al Desarrollo Web con Java


UNIDAD 5

JavaServer Faces (JSF)


Componentes, ciclo de vida y Managed Beans: la base conceptual que conecta con Spring
MVC

Duración estimada 5 horas (unidad compacta)

Módulo 1 — Introducción al Desarrollo Web con Java

Unidad anterior Unidad 4 — Seguridad básica en JSP

Nivel de entrada Servlet, JSP y JavaBeans funcionando correctamente

Modalidad Presencial con apoyo de herramientas de IA

Enfoque Conceptual y práctico equilibrado. Unidad compacta: JSF está en declive pero sus
conceptos son clave para entender Spring MVC

📊 NOTA DE MERCADO LABORAL


Antes de empezar — sé honesto contigo mismo sobre JSF:
JSF es una tecnología real, con millones de líneas de código en producción en empresas grandes. Sin
embargo, es una tecnología en declive: pocas empresas la piden para nuevas contrataciones.
Entonces, ¿por qué la estudiamos? Porque los conceptos que introduce JSF —componentes de
interfaz, ciclo de vida de una petición, Managed Beans, navegación declarativa— son exactamente
los mismos que después encontrarás en Spring MVC con Thymeleaf, que sí domina el mercado
actual. Aprender JSF con cabeza es aprender los fundamentos del desarrollo web por componentes.

Esta unidad es compacta a propósito: te enseña lo suficiente para entender JSF, trabajar con él si una
empresa lo usa, y sobre todo, para que Spring MVC te resulte familiar cuando llegues al Módulo 2.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué es JSF y en qué se diferencia de JSP.
• Describir el ciclo de vida de una petición JSF y sus seis fases.
• Crear y usar un Managed Bean correctamente.
• Construir un formulario JSF básico con validación integrada.
• Navegar entre páginas usando reglas de navegación JSF.
IFCD0182 — Desarrollo Web con Java

• Identificar los componentes JSF más usados y su equivalente en [Link] los


conceptos de JSF que reaparecen en Spring MVC.

1. ¿Qué es JSF y en qué se diferencia de JSP?

1.1 El problema que JSF vino a resolver


Cuando llevas tiempo trabajando con JSP y Servlets te das cuenta de un patrón que se repite: siempre estás
escribiendo el mismo código para leer parámetros del formulario, validarlos, convertir tipos, mostrar errores
y redirigir al usuario. Es código repetitivo, tedioso y fácil de hacer mal.

JSF (JavaServer Faces) es un framework de interfaz de usuario que automatiza exactamente ese ciclo. En
lugar de que tú gestiones manualmente cada paso, JSF lo hace por ti de forma estandarizada.

Aspecto Con JSP + Servlet (manual) Con JSF (automatizado)

Leer parámetros del [Link]("nombre") JSF lo lee automáticamente y lo asigna al


formulario Bean

Convertir tipos [Link]([Link] JSF convierte automáticamente String →


eter("edad")) int

Validar datos Código Java manual en el Servlet Anotaciones o validadores declarativos

Mostrar errores al usuario Código Java que genera HTML de Etiqueta <h:message> automática
error

Navegar a otra página [Link]() o Reglas de navegación declarativas en


forward() XML

Reutilizar componentes UI No existe, cada página es Componentes reutilizables con Facelets


independiente

1.2 JSF vs JSP — La diferencia fundamental


La diferencia más importante entre JSP y JSF no es técnica, sino conceptual:

👁 FÍJATE
JSP es una tecnología de plantillas: tú escribes HTML con código Java incrustado y controlas
manualmente el flujo.

JSF es un framework de componentes: defines qué componentes quieres (un campo de texto,
un botón, una tabla) y JSF gestiona automáticamente todo su ciclo de vida.

En JSP tú eres el director de orquesta. En JSF, el framework lo es.


IFCD0182 — Desarrollo Web con Java

2. El ciclo de vida de JSF — Las seis fases

Esta es la parte más importante de entender en JSF. Cada vez que el usuario envía un formulario o navega a
una página JSF, el framework ejecuta automáticamente seis fases en orden. No puedes saltarte ninguna ni
cambiar el orden.

🔄 CICLO DE VIDA
Piensa en el ciclo de vida de JSF como una cadena de montaje en una fábrica. Cada estación de la
cadena hace su trabajo específico sobre la pieza (la petición). Solo cuando una estación termina, la
pieza pasa a la siguiente. Si algo falla en una estación, la pieza no continúa.

Fase Nombre Qué hace JSF en esta fase

1 Restore View Reconstruye o crea el árbol de componentes de la página. Si es la primera


visita, crea uno nuevo. Si es un postback (formulario enviado), restaura el
anterior.

2 Apply Request Values Cada componente del árbol extrae su valor correspondiente de la petición
HTTP. El campo de texto coge su valor del parámetro del formulario.

3 Process Validations Se validan los valores de todos los componentes. Si alguno no pasa la
validación, se registra el error y el ciclo salta directamente a la fase 6 sin
continuar.

4 Update Model Values Los valores válidos se transfieren a los atributos del Managed Bean
mediante los setters. Esta es la primera vez que el Bean recibe los datos.

5 Invoke Application Se ejecuta el método de acción del Bean (el que indicaste en el action del
botón). Aquí va la lógica de negocio: guardar en BD, hacer cálculos, etc.

6 Render Response JSF genera el HTML de la respuesta a partir del árbol de componentes y lo
envía al navegador. Incluye los mensajes de error si los hay.

⚙ CÓMO FUNCIONA
El detalle más importante del ciclo de vida:
Si la validación falla en la Fase 3, el ciclo salta directamente a la Fase 6 (Render Response) mostrando
los errores. Las fases 4 y 5 no se ejecutan. Esto significa que tu Managed Bean nunca recibe datos
incorrectos. JSF garantiza que si el código de negocio se ejecuta, los datos ya han sido validados.

🤖 CON AYUDA DE LA IA
Entiende el ciclo de vida con IA
Si alguna fase no te queda clara, usa este prompt:

"Explícame el ciclo de vida de JSF con una analogía cotidiana. Quiero entender qué ocurre
exactamente en la Fase 3 (Process Validations) y por qué JSF salta a la Fase 6 si falla la validación,
sin pasar por las fases 4 y 5. Explícamelo como si fuera la primera vez que lo escucho."
IFCD0182 — Desarrollo Web con Java

3. Managed Beans — Los JavaBeans de JSF

3.1 ¿Qué es un Managed Bean?


Un Managed Bean es exactamente lo mismo que un JavaBean de la Unidad 3, con una diferencia
importante: JSF lo gestiona automáticamente. Lo crea cuando hace falta, lo destruye cuando ya no se
necesita y lo conecta con los componentes de la vista.

La palabra "Managed" (gestionado) hace referencia a que el ciclo de vida del Bean lo controla el framework,
no tú.

✔ BUENAS PRÁCTICAS
Las tres reglas del JavaBean siguen aplicando:
• Constructor sin argumentos.
• Atributos privados.
• Getters y setters públicos con el nombre correcto.
Lo único que cambia es que añadimos una anotación para decirle a JSF cómo queremos que gestione
el Bean.

3.2 Crear un Managed Bean con anotaciones


En JSF moderno (2.x con Jakarta EE) se usan anotaciones en lugar de ficheros XML de configuración. Las más
importantes son:

Anotación Qué indica a JSF Cuándo usar

@Named El nombre del Bean que usarás en la vista Siempre, en combinación con un
(Expression Language) scope

@RequestScoped El Bean vive solo durante una petición HTTP Formularios simples sin estado
entre peticiones

@SessionScoped El Bean vive toda la sesión del usuario Datos del usuario logueado,
carrito de compra

@ApplicationScoped El Bean vive mientras la app está activa Configuración global, catálogos
de datos

@ViewScoped El Bean vive mientras el usuario está en la Formularios complejos con


misma vista múltiples pasos
IFCD0182 — Desarrollo Web con Java

Ejemplo completo — Managed Bean para un registro de usuario

package [Link];

import [Link];
import [Link];

// @Named: el Bean será accesible en la vista como #{registroBean}


// @RequestScoped: vive solo durante la petición actual
@Named
@RequestScoped
public class RegistroBean {

// Atributos privados — los mismos que en un JavaBean normal


private String nombre;
private String email;
private String password;

// Constructor vacío obligatorio


public RegistroBean() {}

// Método de acción: JSF lo llama cuando el usuario pulsa el botón


// Devuelve un String que JSF usa para navegar a otra página
public String registrar() {
// Aquí iría la lógica de negocio: guardar en BD, enviar email...
[Link]("Registrando usuario: " + nombre);
// El String devuelto es el nombre de la página destino
return "confirmacion"; // Navega a [Link]
}

// Getters y setters (igual que en JavaBeans normales)


public String getNombre() { return nombre; }
public void setNombre(String n) { [Link] = n; }
public String getEmail() { return email; }
public void setEmail(String e) { [Link] = e; }
public String getPassword() { return password; }
public void setPassword(String p) { [Link] = p; }
}
IFCD0182 — Desarrollo Web con Java

4. Componentes JSF y páginas Facelets

4.1 Facelets — La tecnología de vistas de JSF moderno


Las páginas JSF modernas se escriben con Facelets, que son archivos XHTML (HTML bien formado). Tienen
extensión .xhtml en lugar de .jsp. A diferencia de JSP, Facelets es puramente declarativo: no se escribe
código Java en la página.

4.2 Los componentes JSF más importantes


JSF tiene una biblioteca de componentes estándar que se declaran con el prefijo h: en los archivos Facelets:

Componente JSF Equivalente HTML Para qué sirve

<h:form> <form> Contenedor de formulario. Gestiona


automáticamente el estado y el postback

<h:inputText> <input type="text"> Campo de texto vinculado a un atributo del Bean

<h:inputSecret> <input type="password"> Campo de contraseña

<h:commandButton> <button type="submit"> Botón que dispara un método de acción del Bean

<h:outputLabel> <label> Etiqueta vinculada a un componente

<h:outputText> <span> o texto Muestra el valor de un atributo del Bean

<h:message> Div de error personalizado Muestra el mensaje de error de validación de un


componente

<h:messages> Lista de errores Muestra todos los mensajes de error de la página

<h:dataTable> <table> Tabla que itera sobre una colección del Bean

<h:selectOneMenu> <select> Lista desplegable vinculada a un atributo del Bean

4.3 Ejemplo completo — Formulario de registro con JSF


Esta es la página Facelets que usa el Managed Bean que vimos antes:

<!DOCTYPE html>
<html xmlns="[Link]
xmlns:h="[Link]
xmlns:f="[Link]

<h:head>
<title>Registro de usuario</title>
</h:head>

<h:body>
IFCD0182 — Desarrollo Web con Java
<h1>Crear cuenta</h1>

<!-- h:form gestiona automáticamente el ciclo de vida JSF -->


<h:form id="formRegistro">

<!-- Nombre: vinculado a [Link] mediante EL -->


<h:outputLabel for="nombre" value="Nombre:"/>
<h:inputText id="nombre" value="#{[Link]}"
required="true"
requiredMessage="El nombre es obligatorio"/>
<!-- Muestra el error de validación específico de este campo -->
<h:message for="nombre" style="color:red"/>

<h:outputLabel for="email" value="Correo electrónico:"/>


<h:inputText id="email" value="#{[Link]}"
required="true"
requiredMessage="El email es obligatorio">
<!-- Validador estándar de email incluido en JSF -->
<f:validateRegex pattern="^[\w.%+\-]+@[\w.\-]+\.[a-zA-Z]{2,}$"
message="Formato de email no válido"/>
</h:inputText>
<h:message for="email" style="color:red"/>

<h:outputLabel for="password" value="Contraseña:"/>


<h:inputSecret id="password" value="#{[Link]}"
required="true" minlength="8"
requiredMessage="La contraseña es obligatoria"/>
<h:message for="password" style="color:red"/>

<!-- El action llama al método registrar() del Bean -->


<!-- JSF navega a la página que devuelva ese método -->
<h:commandButton value="Crear cuenta"
action="#{[Link]}"/>

</h:form>
</h:body>
</html>

👁 FÍJATE
Observa estos tres puntos clave:
• #{[Link]} — Esta es la Expression Language (EL) de JSF.
#{nombre_bean.atributo} conecta el componente visual con el atributo del Bean
automáticamente.
• required="true" — JSF valida automáticamente en la Fase 3 que el campo no esté vacío.
Si está vacío, muestra el requiredMessage sin llegar al Bean.
• action="#{[Link]}" — JSF llama al método registrar() en la Fase 5 y usa el
String que devuelve para decidir a qué página navegar.
IFCD0182 — Desarrollo Web con Java

5. Navegación entre páginas en JSF

5.1 Navegación implícita (la forma moderna)


En JSF 2.x la navegación implícita es la más sencilla: el método de acción del Bean devuelve el nombre de la
página destino (sin extensión ni barra inicial) y JSF navega a ella automáticamente.

// En el Managed Bean:
public String registrar() {
// Si todo va bien, navegamos a la página de confirmación
return "confirmacion"; // → navega a /[Link]
}

public String cancelar() {


return "inicio"; // → navega a /[Link]
}

public String volverConError() {


return null; // → permanece en la misma página
}

5.2 Redirect vs Forward en JSF


Por defecto JSF hace un forward interno (el usuario no ve el cambio en la URL). Si quieres que la URL cambie
en el navegador, añade ?faces-redirect=true al valor de retorno:

public String registrar() {


// Forward interno: URL no cambia en el navegador
return "confirmacion";

// Redirect: la URL sí cambia (recomendado tras operaciones POST)


// return "confirmacion?faces-redirect=true";
}

💡 CONSEJO
Regla práctica: usa redirect=true después de operaciones que modifican datos (guardar, eliminar).
Así evitas que el usuario reenvíe el formulario accidentalmente si pulsa F5 para recargar la página. Es
el patrón Post-Redirect-Get.
IFCD0182 — Desarrollo Web con Java

6. Lo que JSF te enseña para Spring MVC

Esta es la sección más valiosa de la unidad. Cada concepto de JSF tiene su equivalente directo en Spring
MVC, que es lo que el mercado pide hoy. Cuando llegues al Módulo 2, todo te resultará familiar:

Concepto en JSF Equivalente en Spring MVC Para qué sirve en ambos

Managed Bean (@Named + @Controller o @Service Clase Java que contiene la lógica de
@RequestScoped) la petición

Ciclo de vida de 6 fases DispatcherServlet + Procesar la petición de forma


HandlerChain ordenada y estructurada

Facelets (.xhtml) Thymeleaf (.html) o JSP La vista que genera el HTML


dinámico

Expression Language #{[Link]} Thymeleaf th:text="${atributo}" Vincular datos del backend con el
HTML

required / f:validateRegex @Valid + @NotBlank + @Email Validar datos del formulario de


forma declarativa

h:message / h:messages th:errors en Thymeleaf Mostrar errores de validación al


usuario

action="#{[Link]}" @PostMapping + return "vista" Llamar a la lógica y navegar a la


siguiente página

faces-redirect=true redirect:/ruta Hacer un redirect HTTP tras un


POST

📊 NOTA DE MERCADO LABORAL


Por qué esta tabla importa para tu empleabilidad:
Cuando llegues a Spring MVC en el Módulo 2, si has entendido JSF, no estarás aprendiendo algo
nuevo: estarás reconociendo los mismos patrones con sintaxis diferente. El ciclo de vida, la
vinculación de datos, la validación declarativa, la navegación... todo funciona igual por debajo.

Esto también significa que si en una empresa te encuentras con una aplicación JSF legacy, no te será
ajena. Entenderás cómo funciona y podrás mantenerla o migrarla a Spring MVC, que es una tarea
muy valorada.
IFCD0182 — Desarrollo Web con Java

7. Errores frecuentes en JSF

Error / Síntoma Causa habitual Solución

La página JSF muestra Falta la declaración del Añade


#{[Link]} en texto plano namespace h: o f: en el <html> de xmlns:h="[Link]
en lugar del valor la página " en el elemento <html>

NullPointerException al pulsar el El Managed Bean no tiene Verifica el constructor sin argumentos y


botón del formulario constructor vacío o el scope está que la anotación de scope sea la
mal configurado correcta

Los errores de validación no Falta el componente <h:message Añade <h:message> junto a cada
aparecen aunque el campo esté for="id_campo"/> en la vista campo que quieras validar
vacío

El método de acción se ejecuta El String devuelto no coincide con Comprueba que el nombre devuelto y
pero no navega a la página el nombre del archivo .xhtml el archivo .xhtml sean idénticos (sin
correcta destino extensión)

Los datos del Bean se pierden Se está usando @RequestScoped Elige el scope correcto según la vida útil
entre peticiones cuando se necesita @ViewScoped que necesita el dato
o @SessionScoped

Error: '[Link] vs Mezcla de librerías JSF antigua Usa siempre el mismo prefijo en todos
[Link]' en los imports (javax) y moderna (jakarta) los archivos. Con Tomcat 10+ usa
jakarta.*
IFCD0182 — Desarrollo Web con Java

8. Actividad práctica guiada

Título Calculadora de IMC con JSF

Duración 50 minutos

Objetivo Crear una aplicación JSF funcional con Managed Bean, formulario validado y resultado
en una segunda vista

Nivel Guiado — el formador explica cada bloque antes de implementarlo

Enunciado
Construye una aplicación JSF que calcule el Índice de Masa Corporal (IMC) del usuario:

1 Crea el Managed Bean ImcBean con scope de petición


Atributos: double peso (en kg) y double altura (en metros). Ambos con getters y setters.

Método calcular() que devuelva un String. Si los datos son válidos, calcula el IMC (peso /
altura²), guárdalo en un atributo resultado y devuelve "resultado". Si algún valor es negativo o
cero, añade un FacesMessage de error y devuelve null (permanece en la misma página).

2 Crea la página [Link]


Formulario con dos campos: peso y altura, ambos con required="true" y validación de rango
(f:validateDoubleRange). Botón que llama al método calcular() del Bean.

3 Crea la página [Link]


Página que muestra el IMC calculado y una categoría (Bajo peso / Normal / Sobrepeso /
Obesidad) usando una condición en el Bean. Incluye un botón para volver al formulario.

4 Prueba el ciclo completo


Envía el formulario con datos correctos y verifica la navegación. Envía datos vacíos y verifica
que los mensajes de error aparecen sin llegar al Bean. Envía valores negativos y verifica el
FacesMessage.
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Usa la IA para entender el ciclo de vida en tu app
Una vez que funcione, usa este prompt para profundizar:

"Tengo esta app JSF funcionando [describe brevemente qué hace]. Cuando el usuario envía el
formulario con un campo vacío, ¿en qué fase del ciclo de vida de JSF se detiene el proceso y por
qué nunca llega al método calcular()? Explícamelo trazando las 6 fases con mi aplicación como
ejemplo."

9. Actividad práctica autónoma

Título Formulario de contacto con JSF y dos vistas

Duración 70 minutos

Objetivo Implementar autónomamente un flujo JSF completo con validación, navegación y


presentación del resultado

Nivel Autónomo — apuntes, documentación JSF y IA disponibles

Entrega Proyecto comprimido en .zip con comentarios en el Bean explicando cada decisión de
scope y navegación

Enunciado
Desarrolla un formulario de contacto web con JSF que tenga:

• Página [Link]: Formulario con campos nombre (texto, requerido, mínimo 2 caracteres),
email (texto, requerido, validación de formato), asunto (desplegable con al menos 4 opciones usando
<h:selectOneMenu>), mensaje (área de texto con mínimo 10 caracteres).
• ContactoBean: Managed Bean con el scope adecuado (justifica en un comentario por qué elegiste
ese scope). Método enviar() que valida que el mensaje no sea spam (contiene palabras clave como
'oferta' o 'gratis') y en ese caso añade un FacesMessage de advertencia y devuelve null. Si todo es
correcto, devuelve la vista de confirmación con redirect.
• Página [Link]: Muestra un resumen del mensaje enviado: nombre, email, asunto y los
primeros 100 caracteres del mensaje. Incluye un botón para volver al formulario que limpia los datos
del Bean.
• Manejo de errores: Todos los errores de validación deben mostrarse junto al campo correspondiente
con <h:message>. Los errores globales (spam) deben mostrarse en la parte superior del formulario
con <h:messages>.
IFCD0182 — Desarrollo Web con Java

Criterios de evaluación
Criterio Descripción Peso

Managed Bean correcto Scope justificado, método enviar() funciona, 25%


navegación correcta

Validaciones JSF Todos los campos validados con mensajes en el 25%


lugar correcto

Navegación Redirect correcto tras envío exitoso, permanencia 20%


con errores

Vista de confirmación Muestra los datos correctamente con #{} EL, sin 15%
código Java

Detección de spam FacesMessage de advertencia funciona 10%


correctamente

Comentarios El scope elegido y las decisiones de navegación 5%


están explicados

⚠ ATENCIÓN
Normas de uso de la IA:
• Permitido: pedir a la IA que explique cómo funciona un componente específico, que
corrija un error concreto, que aclare la diferencia entre dos scopes.
• No permitido: pedir el Bean completo, el formulario completo o la página de
confirmación completa.
• Obligatorio: si la IA te sugiere usar un scope diferente al que tenías, analiza por qué y
añade un comentario con tu razonamiento.
IFCD0182 — Desarrollo Web con Java

10. Resumen de la unidad

JSF es una tecnología con mucho vocabulario específico. Lo más importante no es memorizar las etiquetas,
sino entender los conceptos que hay detrás:

Concepto Lo esencial

JSF Framework de componentes UI para Java Web. Automatiza el ciclo petición-


respuesta.

Facelets (.xhtml) Sistema de vistas de JSF moderno. HTML bien formado con componentes h: y f:.

Ciclo de vida 6 fases ordenadas: Restore View → Apply Values → Validations → Update
Model → Invoke App → Render.

Fase 3 crítica Si la validación falla, el ciclo salta a la fase 6. El Bean nunca recibe datos
inválidos.

Managed Bean JavaBean normal con @Named + anotación de scope. JSF gestiona su ciclo de
vida automáticamente.

Scopes Request (una petición), View (misma página), Session (sesión usuario),
Application (toda la app).

Expression Language #{[Link]} conecta los componentes de la vista con los atributos del
Bean.

Navegación El método de acción devuelve un String con el nombre de la vista destino.

Redirect Añade ?faces-redirect=true al String de retorno para cambiar la URL en el


navegador.

Conexión con Spring Managed Bean → @Controller, Facelets → Thymeleaf, EL → th:text, action →
@PostMapping.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
1. ¿En qué fase del ciclo de vida JSF se ejecuta el método de acción del Bean? ¿Y la
validación?
2. ¿Qué ocurre si la validación falla? ¿A qué fase salta el ciclo?
3. ¿Cuándo usarías @SessionScoped en lugar de @RequestScoped?
4. ¿Qué diferencia hay entre un forward y un redirect en JSF?
5. Pon un ejemplo de un componente JSF y su equivalente en Spring MVC con Thymeleaf.

🤖 CON AYUDA DE LA IA
Prompt de consolidación antes de la siguiente unidad:

"Sé mi entrevistador técnico. Acabo de aprender JSF: ciclo de vida de 6 fases, Managed Beans,
Facelets, validación y navegación. Hazme 4 preguntas de dificultad creciente. Después de cada
respuesta mía, dime qué añadiría un candidato con experiencia real en JSF o Spring MVC."
IFCD0182 — Desarrollo Web con Java

MÓDULO 1 — Introducción al Desarrollo Web con Java


UNIDAD 6 — CIERRE DEL MÓDULO 1

El patrón MVC en Java EE


Separación de responsabilidades, mantenibilidad y el puente directo hacia Spring MVC

Duración estimada 6 horas (teoría + prácticas + revisión de módulo)

Módulo 1 — Introducción al Desarrollo Web con Java (unidad final)

Unidad anterior Unidad 5 — JavaServer Faces (JSF)

Nivel de entrada Servlet, JSP, JavaBeans y JSF entendidos y practicados

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás El patrón arquitectónico que unifica todo lo visto en el módulo y sienta las bases
del Módulo 2

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué es el patrón MVC y por qué se usa en el desarrollo web.
• Identificar las tres capas MVC en cualquier aplicación Java Web.
• Implementar MVC completo con Servlet (Controlador), JavaBean (Modelo) y JSP (Vista).
• Aplicar MVC con JSF usando Managed Bean como Controlador-Modelo y Facelets como
Vista.
• Refactorizar código sin capas en código con capas MVC bien separadas.
• Reconocer los mismos conceptos MVC en Spring MVC, que verás en el Módulo 2.
• Consolidar y conectar todo lo aprendido en el Módulo 1.
IFCD0182 — Desarrollo Web con Java

1. ¿Qué es el patrón MVC y por qué existe?

1.1 El problema que MVC resuelve


Cuando empiezas a programar, la forma más natural de hacerlo es meter todo junto: la lógica de negocio, el
acceso a datos y el código de presentación en el mismo archivo o clase. Funciona al principio, pero cuando la
aplicación crece aparecen los problemas.

✘ ERROR FRECUENTE
Una aplicación sin capas tiene estos problemas:
• Si cambias la base de datos, tienes que reescribir también el código HTML.
• Si el diseñador modifica el HTML, puede romper accidentalmente la lógica Java.
• Es imposible probar la lógica de negocio sin arrancar el servidor web completo.
• Añadir una funcionalidad nueva requiere tocar muchos archivos distintos.
• El código es tan difícil de leer que nadie más puede trabajar en él sin dedicar horas a
entenderlo.

MVC (Modelo-Vista-Controlador) es un patrón de diseño que divide la aplicación en tres responsabilidades


claramente separadas. Cada parte hace una sola cosa, bien definida, sin meterse en el trabajo de las otras.

1.2 Las tres capas del patrón MVC

¿Qué es?
M Representa los datos y la lógica de negocio de la aplicación. Es la parte que sabe qué hace la
MODE
LO aplicación, no cómo se lo muestra al usuario ni cómo llegan las peticiones.

En Java Web: JavaBeans, clases de servicio, clases de acceso a datos (DAO), entidades JPA.
Regla clave: El Modelo nunca sabe cómo se va a mostrar la información. No genera HTML,
no conoce la Vista.

¿Qué es?
V Es la interfaz de usuario. Presenta los datos que le pasa el Controlador y recoge las acciones
VISTA
del usuario (clics, formularios). No contiene lógica de negocio.

En Java Web: JSP, JSF Facelets (.xhtml), Thymeleaf (.html). En el Módulo 2 usarás Thymeleaf.
Regla clave: La Vista no hace cálculos ni consulta la base de datos. Solo muestra lo que
recibe.
IFCD0182 — Desarrollo Web con Java

¿Qué es?
C Es el director de tráfico. Recibe las peticiones del usuario, decide qué hacer, llama al Modelo
CONT
ROLA para obtener o modificar datos, y elige qué Vista mostrar con esos datos.
DOR

En Java Web: Servlet (Java EE clásico), @Controller en Spring MVC, Managed Bean en JSF.
Regla clave: El Controlador no genera HTML ni contiene lógica de negocio compleja. Delega
en el Modelo y elige la Vista.

👁 FÍJATE
La regla de oro del patrón MVC:
Cada capa solo debe comunicarse con la capa adyacente. El Controlador habla con el Modelo y con
la Vista. El Modelo no sabe que existe la Vista. La Vista no sabe que existe el Modelo directamente.
Si una capa empieza a hacer el trabajo de otra, el patrón está roto.
IFCD0182 — Desarrollo Web con Java

2. El flujo completo de una petición MVC

Cada petición que hace el usuario en una aplicación MVC sigue siempre el mismo camino. Aprenderlo de
memoria es una de las cosas más importantes que puedes hacer en este módulo:

1 El usuario hace una acción (clic, formulario, URL)


El navegador envía una petición HTTP al servidor. Esta petición lleva la URL, el método HTTP y,
si hay formulario, los datos.

2 El Controlador recibe la petición


El Servlet (o el @Controller de Spring) captura la petición. Es el primero en recibirla. Decide
qué operación hay que hacer según la URL y el método HTTP.

3 El Controlador llama al Modelo


El Controlador no hace la lógica él solo. Llama a las clases del Modelo (servicios, DAOs, Beans)
para obtener o modificar los datos que necesita.

4 El Modelo devuelve los datos al Controlador


El Modelo ejecuta la lógica (consulta a la BD, cálculos, validaciones de negocio) y devuelve el
resultado al Controlador. El Modelo no sabe para qué se usarán esos datos.

5 El Controlador prepara los datos y elige la Vista


El Controlador recibe los datos del Modelo, los coloca donde la Vista pueda encontrarlos
(request, model...) y decide a qué página debe enviarse la respuesta.

6 La Vista genera el HTML y lo envía al navegador


La Vista (JSP, Thymeleaf, Facelets) recoge los datos que el Controlador preparó, genera el
HTML con ellos y lo envía al navegador del usuario. La Vista no hace ningún cálculo.

💡 CONSEJO
Si memorizas estos 6 pasos, podrás describir el funcionamiento de cualquier aplicación web Java, ya
sea JSP con Servlets, JSF o Spring MVC. El flujo siempre es el mismo. Solo cambia la tecnología
concreta que implementa cada capa.
IFCD0182 — Desarrollo Web con Java

3. MVC con Servlet + JavaBean + JSP

Esta es la implementación más pura de MVC en Java EE clásico. Cada capa es una tecnología diferente con
una responsabilidad clara:

Capa MVC Tecnología Archivo Responsabilidad

Controlador Servlet [Link] Recibe peticiones, llama al


Modelo, elige la Vista

Modelo JavaBean [Link] Representa los datos del


dominio con sus reglas

Modelo Servicio [Link] Lógica de negocio: buscar,


crear, validar

Vista JSP [Link] Muestra los datos recibidos en


HTML

3.1 El Modelo — Bean + Servicio


[Link] — el Bean de datos

package [Link];

public class Producto {


private Long id;
private String nombre;
private double precio;
private int stock;

public Producto() {}
public Producto(Long id, String nombre, double precio, int stock) {
[Link] = id; [Link] = nombre;
[Link] = precio; [Link] = stock;
}
// Getters y setters...
}

[Link] — la lógica de negocio

package [Link];

import [Link];
import [Link];

// En una app real esta clase accedería a la base de datos


// Aquí usamos datos en memoria para simplificar
public class ProductoService {
IFCD0182 — Desarrollo Web con Java

public List<Producto> findAll() {


return [Link](
new Producto(1L, "Teclado mecánico", 89.99, 15),
new Producto(2L, "Monitor 27 pulgadas", 349.00, 8),
new Producto(3L, "Ratón inalámbrico", 29.50, 42)
);
}

public Producto findById(Long id) {


return findAll().stream()
.filter(p -> [Link]().equals(id))
.findFirst()
.orElse(null);
}
}

3.2 El Controlador — Servlet

package [Link];

import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
import [Link];

@WebServlet("/productos")
public class ProductoController extends HttpServlet {

// El servicio contiene la lógica de negocio


private final ProductoService service = new ProductoService();

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws [Link], IOException {

// 1. Llamamos al MODELO para obtener los datos


List<Producto> productos = [Link]();

// 2. Colocamos los datos donde la VISTA los pueda encontrar


[Link]("productos", productos);

// 3. Elegimos la VISTA y le cedemos el control


[Link]("/WEB-INF/vistas/[Link]")
.forward(req, resp);
}
}
IFCD0182 — Desarrollo Web con Java

3.3 La Vista — JSP

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>


<%@ taglib prefix="c" uri="[Link] %>

<%-- La Vista SOLO muestra los datos. Sin lógica de negocio. --%>
<!DOCTYPE html>
<html lang="es">
<head><title>Catálogo de productos</title></head>
<body>
<h1>Nuestros productos</h1>
<table>
<tr><th>Nombre</th><th>Precio</th><th>Stock</th></tr>

<%-- c:forEach itera sobre la lista que preparó el Controlador --%>


<c:forEach var="p" items="${productos}">
<tr>
<td><c:out value="${[Link]}"/></td>
<td><c:out value="${[Link]}"/> €</td>
<td><c:out value="${[Link]}"/></td>
</tr>
</c:forEach>
</table>
</body>
</html>

✔ BUENAS PRÁCTICAS
Observa las reglas MVC en este código:
• El Bean ([Link]) no tiene ni una línea de HTML. No sabe que existe una Vista.
• El Servicio ([Link]) no sabe cómo se va a mostrar la información.
• El Servlet no genera HTML. Solo llama al Modelo, pone los datos en el request y elige la
JSP.
• La JSP no tiene código Java entre <% %>. Solo JSTL y EL para mostrar los datos que
recibió.
IFCD0182 — Desarrollo Web con Java

4. MVC con JSF — Cómo encaja el ciclo de vida

En JSF el patrón MVC también existe, aunque las responsabilidades se distribuyen de forma ligeramente
diferente. El framework se encarga de mucho del trabajo del Controlador automáticamente:

Capa MVC En JSF Responsabilidad específica

Controlador JSF FacesServlet + Managed Bean El FacesServlet recibe todas las peticiones. El
(métodos de acción) Managed Bean decide qué hacer y a qué vista
navegar.

Modelo Managed Bean (atributos) + clases Los atributos del Bean guardan el estado. Los
de servicio servicios implementan la lógica de negocio.

Vista Facelets (.xhtml) Muestra los datos del Bean mediante Expression
Language. No tiene lógica Java.

⚙ CÓMO FUNCIONA
Una diferencia importante entre JSF y Servlet+JSP:
En el modelo Servlet+JSP el Controlador (Servlet) y el Modelo (Bean) son clases separadas. En JSF el
Managed Bean asume el papel de ambos: sus atributos son el Modelo y sus métodos de acción son
el Controlador. Esta mezcla es una de las razones por las que algunos consideran que JSF no
implementa MVC puro, sino una variante llamada MVP (Model-View-Presenter).

Lo importante para ti en este punto: el concepto de separación de responsabilidades es el mismo en


ambos casos. La Vista no tiene lógica, el Modelo no sabe cómo se muestra, y hay un componente
que coordina el flujo.
IFCD0182 — Desarrollo Web con Java

5. Separación de responsabilidades y mantenibilidad

5.1 Por qué la separación importa en el mundo real


La separación de responsabilidades no es un capricho académico. Es lo que hace que el código sea
mantenible cuando el proyecto crece y cuando hay más de una persona trabajando en él.

Situación Sin MVC Con MVC

Un diseñador modifica el Puede romper el código Java que Solo toca la Vista. El Modelo y el
HTML está mezclado Controlador no se ven afectados

Cambias de MySQL a Hay que buscar y modificar SQL Solo modificas la capa de acceso a datos
PostgreSQL en JSPs, Servlets y Beans del Modelo

Quieres añadir una API REST Es casi imposible sin reescribir Añades un nuevo Controlador que usa el
todo mismo Modelo existente

Quieres probar la lógica de Hay que arrancar el servidor Pruebas el Servicio con JUnit sin Tomcat ni
negocio entero HTTP

Entra un nuevo desarrollador Tarda días en entender la mezcla El código está organizado por capas, es
al equipo de responsabilidades predecible y fácil de navegar

5.2 Refactorizar código sin capas a MVC


Uno de los ejercicios más valiosos es tomar código que mezcla responsabilidades y reorganizarlo en capas
MVC. Mira este ejemplo de antes y después:

ANTES — Todo mezclado en un Servlet (mal)

✘ ERROR FRECUENTE
Código con responsabilidades mezcladas — no hagas esto en proyectos reales:

@WebServlet("/productos")
public class ProductoServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws Exception {

// Lógica de negocio mezclada con control y presentación


Connection conn = [Link]("jdbc:mysql://...");
ResultSet rs = [Link]()
.executeQuery("SELECT * FROM productos WHERE stock > 0");

[Link]("text/html");
PrintWriter out = [Link]();
[Link]("<html><body><table>");
while ([Link]()) {
[Link]("<tr><td>" + [Link]("nombre") + "</td></tr>");
}
[Link]("</table></body></html>");
IFCD0182 — Desarrollo Web con Java
}
}

DESPUÉS — Responsabilidades separadas (bien)

✔ BUENAS PRÁCTICAS
El mismo resultado, con capas correctamente separadas:

// MODELO: [Link]
public List<Producto> findConStock() {
// Toda la lógica de acceso a datos aquí, aislada
// ...
}

// CONTROLADOR: [Link]
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws Exception {
List<Producto> lista = [Link](); // llama al Modelo
[Link]("productos", lista); // prepara la Vista
[Link]("/WEB-INF/vistas/[Link]").forward(req, resp);
}

<!-- VISTA: [Link] — solo muestra, sin lógica -->


<c:forEach var="p" items="${productos}">
<tr><td><c:out value="${[Link]}"/></td></tr>
</c:forEach>

🤖 CON AYUDA DE LA IA
Practica la refactorización con IA
Usa este prompt para practicar uno de los ejercicios más habituales en código real:

"Tengo este código Java Web que mezcla lógica de negocio, acceso a datos y generación de
HTML en el mismo Servlet [pega el código]. Refactorízalo aplicando el patrón MVC
correctamente: crea un Bean, un Servicio, un Controlador (Servlet) y una Vista (JSP). Explica en
cada archivo por qué esa responsabilidad va ahí y no en otro sitio."
Después de que la IA lo refactorice, revisa cada archivo y asegúrate de que puedes explicar por qué
cada línea está donde está. Ese ejercicio de comprensión es el que te diferencia en una entrevista.
IFCD0182 — Desarrollo Web con Java

6. El mismo patrón en Spring MVC — El puente al Módulo 2

Todo lo que has aprendido sobre MVC en este módulo se aplica directamente en Spring MVC. Las capas son
las mismas. Las responsabilidades son las mismas. Solo cambia la forma de escribirlo:

Capa Java EE (lo que sabes) Spring MVC (lo que aprenderás) Diferencia principal

Controlador @WebServlet + doGet/doPost @Controller + @GetMapping + Spring usa anotaciones


@PostMapping más expresivas. No
extends HttpServlet.

Modelo — JavaBean (POJO) POJO idéntico o @Entity para Igual. Los mismos tres
datos JPA reglas del JavaBean.

Modelo — Clase de servicio manual @Service con inyección de Spring crea y gestiona
lógica dependencias (@Autowired) el servicio
automáticamente.

Vista JSP con JSTL Thymeleaf (.html) Thymeleaf es HTML


puro que funciona en
el navegador sin
servidor.

Datos [Link]("clave", valor) [Link]("clave", Mismo concepto,


Controlador → valor) distinta sintaxis.
Vista

Navegación [Link]() o return "nombre-vista" o return Spring deduce la vista


sendRedirect() "redirect:/ruta" desde el nombre. Sin
dispatcher manual.

Configuración [Link] + @WebServlet [Link] + Spring Boot


anotaciones autoconfigura casi
todo. Cero XML.

📊 NOTA DE MERCADO LABORAL


Por qué el Módulo 2 es el más importante del curso:
Spring MVC y Spring Boot son las tecnologías que el mercado laboral pide en la gran mayoría de las
ofertas de desarrollador Java. Lo que has aprendido en el Módulo 1 —Servlets, JSP, JavaBeans, JSF,
MVC— es la base conceptual que hace que Spring MVC tenga sentido.

Un desarrollador que llega al Módulo 2 sin haber entendido el Módulo 1 aprende Spring de memoria
pero no entiende por qué funciona así. Un desarrollador que ha entendido el Módulo 1 llega al
Módulo 2 reconociendo los mismos patrones con mejor implementación.
IFCD0182 — Desarrollo Web con Java

7. Errores frecuentes al aplicar MVC

Error Por qué es un problema Cómo corregirlo

Poner lógica de negocio en el El Controlador se vuelve enorme y Mueve la lógica a una clase de Servicio.
Servlet/Controlador difícil de probar. La lógica no es El Controlador solo orquesta.
reutilizable.

Poner código Java entre <% %> La Vista deja de ser solo Usa JSTL y Expression Language. Todo
en la JSP presentación. El diseñador no el Java va en el Servlet o el Bean.
puede trabajar en ella.

El Modelo accede al El Modelo queda acoplado a la El Modelo solo recibe y devuelve datos
HttpServletRequest o capa web. No se puede reutilizar simples (Strings, números, listas). Sin
HttpSession fuera de un contexto HTTP. objetos HTTP.

Hacer forward desde la Vista a La Vista está tomando decisiones Solo el Controlador decide a dónde va
otro Servlet de control de flujo, que no le el flujo. La Vista solo muestra.
corresponden.

Usar el mismo Servlet para El Servlet se convierte en una Un Servlet por recurso o funcionalidad.
demasiadas funciones clase de miles de líneas imposible ProductoServlet, UsuarioServlet,
de mantener. PedidoServlet.

No separar el acceso a datos del Si cambias la base de datos, tienes Usa el patrón DAO: una clase solo para
Servicio que modificar las clases de lógica acceso a datos, una clase solo para
de negocio. lógica.
IFCD0182 — Desarrollo Web con Java

8. Actividad práctica guiada

Título Refactorizar una aplicación sin capas a MVC completo

Duración 60 minutos

Objetivo Tomar una aplicación funcional pero mal estructurada y reorganizarla en capas MVC
siguiendo el patrón correctamente

Nivel Guiado — el formador proporciona el código inicial y explica cada decisión de


refactorización

El código inicial — aplicación sin capas


El formador te entrega un Servlet que gestiona una lista de tareas (to-do list). El Servlet hace todo: consulta
una lista en memoria, genera HTML directamente y procesa el formulario para añadir tareas. Todo en un
único archivo de 80 líneas.

1 Identificar responsabilidades mezcladas


Lee el código y señala en papel cada línea indicando si es lógica de negocio (M), presentación
(V) o control de flujo (C). El objetivo es visualizar qué pertenece a cada capa antes de mover
nada.

2 Crear el Bean Tarea


Extrae los datos de una tarea (id, descripción, completada, fechaCreación) a un JavaBean
separado en el paquete [Link]. Añade los getters, setters y un toString() para
depurar.

3 Crear el TareaService
Mueve la lógica de manejo de la lista (añadir tarea, marcar como completada, obtener todas) a
una clase de servicio en [Link]. El servicio no recibe parámetros HTTP.

4 Limpiar el Controlador
El Servlet ahora solo debe: llamar al servicio, colocar los datos en el request y hacer forward a
la JSP. Si tiene más de 20 líneas de lógica real, hay algo que todavía no has movido al Servicio.

5 Crear la Vista JSP


Mueve todo el HTML a una JSP en WEB-INF/vistas/[Link]. Usa JSTL para iterar y mostrar las
tareas. Comprueba que no hay ni un solo <% %> en la JSP.
IFCD0182 — Desarrollo Web con Java

6 Verificar que el resultado es idéntico


La aplicación debe funcionar exactamente igual antes y después de la refactorización. Si algo
cambió en el comportamiento, la refactorización no fue correcta.

🤖 CON AYUDA DE LA IA
Usa la IA como revisor de arquitectura
Cuando termines la refactorización, pide a la IA que revise si el MVC está correctamente aplicado:

"Revisa esta aplicación Java Web que he refactorizado a MVC [pega los tres archivos: Bean,
Servicio y Servlet, y la JSP]. Dime si alguna capa está haciendo el trabajo de otra, si hay lógica de
negocio donde no debería o si la Vista tiene código que debería estar en el Controlador. Sé
específico con la línea exacta si encuentras algún problema."
IFCD0182 — Desarrollo Web con Java

9. Proyecto integrador del Módulo 1

ℹ NOTA
Esta actividad autónoma es el proyecto final del Módulo 1. Integra todo lo aprendido: Servlets, JSP,
JavaBeans, seguridad básica y el patrón MVC. Es deliberadamente más larga que las anteriores.

Título Gestor de biblioteca personal — Aplicación MVC completa

Duración 120 minutos

Objetivo Construir desde cero una aplicación Java Web completa aplicando el patrón MVC y las
medidas de seguridad del Módulo 1

Nivel Autónomo — apuntes, documentación y IA disponibles con las normas habituales

Entrega Proyecto IntelliJ en .zip. Debe incluir un archivo [Link] explicando la arquitectura
MVC elegida

Enunciado
Desarrolla un gestor de biblioteca personal. Un usuario puede ver los libros de su biblioteca, añadir nuevos y
marcarlos como leídos. La aplicación debe tener:

Arquitectura MVC obligatoria


• Modelo: Bean Libro (titulo, autor, isbn, anio, leido) + LibroService con findAll(), save(Libro) y
marcarLeido(String isbn).
• Controlador: LibroController (@WebServlet) que gestiona GET (listar) y POST (añadir / marcar como
leído).
• Vista: [Link] que muestra todos los libros en una tabla HTML con una columna que indica si está
leído o no.

Seguridad obligatoria (Unidad 4)


• Toda salida de datos en la JSP usa <c:out> para prevenir XSS.
• El formulario de añadir libro incluye token CSRF generado en el Servlet y validado en el POST.
• El campo año se valida en el Servlet: debe ser un número entre 1450 y el año actual.
• El campo ISBN se valida en el Servlet: debe tener 10 o 13 caracteres numéricos.
• Si alguna validación falla, el usuario vuelve al formulario con un mensaje de error concreto, no
genérico.
IFCD0182 — Desarrollo Web con Java

Criterios de evaluación
Criterio Descripción Peso

MVC correcto Las tres capas están claramente separadas. La JSP no 30%
tiene <% %>. El Servlet no genera HTML.

Funcionalidad Listar, añadir y marcar como leído funcionan 25%


correctamente

Seguridad XSS prevenido, CSRF protegido, validaciones en 20%


servidor funcionando

Calidad del código Nombres claros, comentarios en las decisiones 15%


arquitectónicas, sin código muerto

README Explica en pocas líneas qué clase es el Modelo, cuál el 10%


Controlador y cuál la Vista

⚠ ATENCIÓN
Normas de uso de IA para el proyecto final:
• Permitido: pedir revisión de arquitectura, preguntar si una clase está en la capa correcta,
resolver errores específicos.
• No permitido: generar el proyecto completo o cualquiera de las clases principales
completas.
• Recomendado: una vez terminado, pide a la IA que actúe como revisor técnico y señale
cualquier violación del patrón MVC.
IFCD0182 — Desarrollo Web con Java

10. Resumen de la unidad y cierre del Módulo 1

Concepto MVC Lo esencial

Modelo Datos y lógica de negocio. No sabe que existe la Vista. No genera HTML.

Vista Presentación al usuario. No hace cálculos ni toca la base de datos.

Controlador Director de tráfico. Recibe peticiones, llama al Modelo y elige la Vista.

Flujo de petición Usuario → Controlador → Modelo → Controlador → Vista → Usuario.


Siempre en este orden.

Separación Cada capa tiene una única responsabilidad. Si una toca el trabajo de otra,
algo está mal.

MVC en Java EE Servlet (C) + JavaBean/Servicio (M) + JSP con JSTL (V).

MVC en JSF FacesServlet + Managed Bean métodos (C) + Managed Bean atributos +
Servicio (M) + Facelets (V).

MVC en Spring MVC @Controller (C) + @Service/@Entity (M) + Thymeleaf (V). Lo verás en el
Módulo 2.

Mantenibilidad Con MVC puedes cambiar la Vista sin tocar el Modelo y viceversa.

Testabilidad El Servicio (Modelo) se puede probar con JUnit sin servidor web. La JSP sola
no se puede probar.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación del Módulo 1 completo:
1. ¿Cuál es el flujo completo de una petición en una aplicación MVC con Servlet + JSP?
2. ¿En qué capa va la lógica que consulta la base de datos? ¿Y la que genera HTML?
3. ¿Qué tiene en común un JavaBean con un Managed Bean de JSF?
4. ¿Por qué usar PreparedStatement en lugar de concatenar SQL directamente?
5. ¿Qué diferencia hay entre el scope request y el scope session en JSP/JSF?
6. ¿Qué fase del ciclo de vida JSF se ejecuta cuando falla la validación?
7. ¿Cómo se llama en Spring MVC el equivalente al @WebServlet del Java EE clásico?

🤖 CON AYUDA DE LA IA
Revisión completa del Módulo 1 con IA
Para consolidar todo el módulo antes de empezar el Módulo 2:

"Actúa como entrevistador técnico para un puesto de desarrollador Java junior. Voy a hacer una
entrevista de 10 preguntas sobre el Módulo 1 de Java Web: Servlets, JSP, JavaBeans, seguridad
básica (XSS, SQL Injection, CSRF), JSF y el patrón MVC. Hazme las preguntas de una en una,
espera mi respuesta, corrígela y explícame qué añadiría un candidato senior. Al final dame una
valoración honesta de mi nivel."
IFCD0182 — Desarrollo Web con Java

🌉 PUENTE AL MÓDULO 2
Cierre del Módulo 1 — Lo que llevas contigo al Módulo 2
Has completado el Módulo 1. Esto es lo que has construido:

• Entiendes cómo funciona la web por dentro: HTTP, cliente-servidor, ciclo petición-
respuesta.
• Sabes crear Servlets que procesan peticiones y JSP que muestran resultados.
• Dominas los JavaBeans y la separación de datos y presentación.
• Conoces las amenazas de seguridad más comunes y cómo prevenirlas en el código.
• Entiendes JSF, su ciclo de vida y los Managed Beans.
• Aplicas el patrón MVC: cada capa en su lugar, con su única responsabilidad.

El Módulo 2 empieza exactamente donde lo deja esto. Spring Boot no es magia: es el mismo patrón
MVC que acabas de aprender, implementado de forma más elegante, más potente y mucho más
demandada en el mercado. Llegas preparado.
IFCD0182 — Desarrollo Web con Java

MÓDULO 2 — Spring Framework, APIs REST y Microservicios


UNIDAD 1

Spring Framework y su ecosistema


IoC, inyección de dependencias, Spring Boot y el ecosistema que domina el desarrollo Java
empresarial

Duración estimada 6 horas (teoría + prácticas)

Módulo 2 — Spring Framework, APIs REST y Microservicios

Unidad anterior Módulo 1 completado — patrón MVC, Servlets, JSP, JavaBeans, seguridad

Nivel de entrada Heterogéneo — reforzamos la conexión con el Módulo 1 antes de avanzar

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Qué es Spring, cómo funciona por dentro (IoC y DI) y cómo Spring Boot simplifica
todo el ecosistema

🔗 CONEXIÓN CON EL MÓDULO 1


Antes de empezar — repaso express del Módulo 1
En el Módulo 1 aprendiste a construir aplicaciones web con Java EE clásico: Servlets como
Controladores, JavaBeans como Modelo y JSP como Vista. También viste JSF y el patrón MVC.

El Módulo 2 no empieza desde cero. Spring toma exactamente ese patrón MVC que ya conoces y lo
implementa de forma mucho más cómoda, potente y demandada en el mercado. Todo lo que sabes
sigue siendo válido. Ahora vas a aprender a hacerlo mejor.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué es Spring Framework y por qué nació.
• Describir qué es la Inversión de Control (IoC) con tus propias palabras.
• Explicar la Inyección de Dependencias (DI) y sus tres formas.
• Reconocer los módulos principales del ecosistema Spring.
• Crear tu primer proyecto Spring Boot desde Spring Initializr.
• Entender qué hace Spring Boot por ti y por qué simplifica tanto el desarrollo.
• Ejecutar una aplicación Spring Boot básica y entender su estructura.
IFCD0182 — Desarrollo Web con Java

1. ¿De dónde viene Spring y por qué existe?

1.1 El problema que Spring vino a resolver


En los primeros años de Java EE (entonces llamado J2EE), desarrollar una aplicación empresarial era
extraordinariamente complejo. Para hacer algo tan básico como guardar un objeto en base de datos había
que escribir decenas de líneas de configuración, implementar interfaces específicas, desplegar en servidores
enormes y pesados como JBoss o WebSphere.

En 2003 Rod Johnson publicó un libro y un framework alternativo con una idea sencilla: ¿y si el framework se
adaptara a tu código en lugar de obligarte a adaptar tu código al framework? Ese framework se llamó Spring.

👁 FÍJATE
La idea central de Spring en una frase:
"Tú escribe tu código de negocio. Spring se ocupa del resto."
El «resto» incluye: crear y gestionar objetos, gestionar transacciones de base de datos, manejar la
seguridad, procesar peticiones HTTP, conectar con servicios externos, y mucho más.

1.2 La evolución: de Spring a Spring Boot


Spring fue creciendo con los años añadiendo módulos para cada necesidad. Pero esa riqueza también trajo
un problema nuevo: demasiada configuración. Configurar Spring correctamente requería muchos archivos
XML y mucho conocimiento previo.

En 2014 apareció Spring Boot con una solución elegante: autoconfiguración. Spring Boot detecta qué tienes
en tu proyecto y lo configura solo, con valores razonables por defecto. Si añades la dependencia de una base
de datos, Spring Boot la configura automáticamente. Si añades la de seguridad, la activa con configuración
básica. Tú solo ajustas lo que necesites cambiar.

Aspecto Spring Framework solo Spring Boot

Configuración Manual, en XML o Java Config. Mucho Automática. Cero XML. Mínima
código. configuración.

Servidor web Debes instalar y configurar Tomcat Tomcat embebido incluido. La app
externamente. arranca sola.

Dependencias Gestionas versiones manualmente. Starters con versiones compatibles


Riesgo de conflictos. garantizadas.

Punto de entrada No hay un main() estándar. Una clase con main() y


Configuración compleja. @SpringBootApplication.

Primer proyecto Varios días para configurarlo Minutos con Spring Initializr.
correctamente.
IFCD0182 — Desarrollo Web con Java

Uso actual Proyectos legacy muy antiguos. Estándar en toda nueva aplicación
Spring.

📊 NOTA DE MERCADO LABORAL


Spring Boot en el mercado laboral:
Spring Boot es la tecnología más demandada en las ofertas de trabajo de desarrollador Java en
España y Europa. Si buscas «desarrollador Java» en cualquier portal de empleo, el 80% de las ofertas
mencionan Spring Boot explícitamente. No es una opción entre varias: es el estándar del sector.

Lo que aprendas en este módulo es directamente lo que te pedirán en las entrevistas y lo que usarás
desde el primer día en la mayoría de empresas.
IFCD0182 — Desarrollo Web con Java

2. Inversión de Control e Inyección de Dependencias

Estos dos conceptos son el corazón de Spring. No son complicados, pero sí requieren un cambio de
mentalidad respecto a cómo programabas hasta ahora. Tómate el tiempo de entenderlos bien porque todo
lo demás en Spring se apoya en ellos.
2.1 Inversión de Control (IoC) — Quién manda aquí
Cuando programas sin un framework, tu código crea los objetos que necesita. Si un Servlet necesita un
Servicio, escribe new ProductoService() y lo crea él mismo. Tu código tiene el control total sobre cuándo y
cómo se crean los objetos.

Con Spring es al revés. Es Spring quien crea los objetos, los configura y los entrega a quien los necesita. Tu
código no crea nada, solo declara qué necesita. Spring se lo proporciona. Eso es la Inversión de Control: el
control de la creación de objetos pasa de tu código al framework.

👁 FÍJATE
Una analogía para entender IoC:
Sin IoC: cuando tienes hambre, vas a la cocina, buscas los ingredientes, los preparas y te haces la
comida tú mismo. Tú tienes el control.

Con IoC: cuando tienes hambre, llamas al servicio de catering. Ellos saben lo que necesitas (porque
se lo dijiste una vez), lo preparan y te lo traen. El control sobre la preparación ya no es tuyo.

El contenedor IoC de Spring es ese servicio de catering. Tú solo dices «necesito un ProductoService»
y Spring te lo prepara y entrega.

2.2 El contenedor IoC — ApplicationContext


El contenedor IoC de Spring se llama ApplicationContext. Es el componente central de Spring: crea todos los
objetos (llamados Beans en Spring), los configura y gestiona su ciclo de vida.

⚙ CÓMO FUNCIONA
Qué hace el ApplicationContext exactamente:
• Lee las anotaciones de tu código (@Component, @Service, @Controller...) para saber
qué clases debe gestionar.
• Crea una instancia de cada Bean al arrancar la aplicación (o cuando se necesita, según la
configuración).
• Detecta las dependencias entre Beans y las resuelve automáticamente.
• Gestiona el ciclo de vida de cada Bean: creación, inicialización y destrucción.
IFCD0182 — Desarrollo Web con Java

• Hace que cada Bean esté disponible para cualquier otra parte de la aplicación que lo
necesite.

2.3 Inyección de Dependencias (DI) — Cómo Spring entrega los objetos


La Inyección de Dependencias es el mecanismo concreto que usa el contenedor IoC para entregar los
objetos. En lugar de que tu clase cree sus dependencias con new, Spring se las inyecta automáticamente.

✘ ERROR FRECUENTE
Sin DI — creación manual de dependencias (forma antigua):

public class ProductoController {


// Tu clase crea su propia dependencia — acoplamiento fuerte
private ProductoService service = new ProductoService();
// Problema: si ProductoService cambia su constructor, aquí también hay que
cambiar
// Problema: imposible de probar con un mock (objeto de prueba falso)
}

✔ BUENAS PRÁCTICAS
Con DI — Spring inyecta la dependencia automáticamente:

@Controller
public class ProductoController {
// Spring crea el ProductoService y lo inyecta aquí automáticamente
@Autowired
private ProductoService service;
// Ventaja: si cambias la implementación de ProductoService, aquí no cambia nada
// Ventaja: en tests puedes inyectar un mock en lugar del servicio real
}

2.4 Las tres formas de inyección en Spring


Spring ofrece tres maneras de inyectar dependencias. Cada una tiene sus casos de uso:

Forma 1 — Inyección por campo (field injection)


@Controller
public class ProductoController {
// Simple pero no recomendada en producción
// No permite campos final ni facilita el testing
@Autowired
private ProductoService service;
}
IFCD0182 — Desarrollo Web con Java

Forma 2 — Inyección por constructor (recomendada)


@Controller
public class ProductoController {
// Recomendada: el campo puede ser final
// Facilita el testing: puedes pasar el mock en el constructor
private final ProductoService service;

// Spring detecta el constructor y lo usa para inyectar


public ProductoController(ProductoService service) {
[Link] = service;
}
}

Forma 3 — Inyección por setter


@Controller
public class ProductoController {
private ProductoService service;

// Útil cuando la dependencia es opcional


@Autowired
public void setService(ProductoService service) {
[Link] = service;
}
}

💡 CONSEJO
¿Cuál usar?
En proyectos nuevos usa siempre la inyección por constructor. Es la que recomienda el propio
equipo de Spring, permite declarar los campos como final (inmutabilidad) y facilita enormemente el
testing. Con Lombok (una librería que verás pronto) ni siquiera hace falta escribir el constructor: la
anotación @RequiredArgsConstructor lo genera automáticamente.

🤖 CON AYUDA DE LA IA
Entiende IoC y DI con IA
Si todavía no terminas de ver la diferencia entre IoC y DI, prueba este prompt:

"Explícame la diferencia entre Inversión de Control e Inyección de Dependencias en Spring. Usa un


ejemplo de código Java: primero sin Spring (creando objetos con new) y luego con Spring
IFCD0182 — Desarrollo Web con Java

(@Autowired). Explica línea a línea qué cambia y por qué el segundo enfoque es mejor para
mantener y probar el código."
Pide también que te muestre las tres formas de inyección y te diga cuándo usar cada una. Compara
la respuesta con lo que has leído aquí.
IFCD0182 — Desarrollo Web con Java

3. Los Beans de Spring — Qué son y cómo se declaran

En Spring, un Bean es cualquier objeto que gestiona el contenedor IoC. No confundas esto con los JavaBeans
del Módulo 1: aunque comparten el nombre, son conceptos distintos. Un Spring Bean puede ser cualquier
clase Java, no necesita seguir las tres reglas de los JavaBeans.

🔗 CONEXIÓN CON EL MÓDULO 1


Conexión con el Módulo 1:
En el Módulo 1 gestionabas los objetos tú mismo: new ProductoService(), new ProductoBean()... En
Spring, declaras qué clases quieres que gestione el contenedor y él se encarga de todo. Es el mismo
concepto que el Managed Bean de JSF, pero aplicado a toda la aplicación.

3.1 Anotaciones para declarar Beans


La forma más habitual de decirle a Spring que gestione una clase es añadirle una anotación. Spring ofrece
cuatro anotaciones principales, cada una con un significado semántico diferente:

Anotación Para qué tipo de clase Qué hace Spring con ella

@Component Clase genérica — no encaja en las otras La registra como Bean. Punto de partida
categorías de todas las demás.

@Service Clase que contiene lógica de negocio Igual que @Component.


Semánticamente indica que es un
servicio.

@Repository Clase de acceso a datos (DAO) Igual que @Component + traduce


excepciones de BD automáticamente.

@Controller Clase que gestiona peticiones web Igual que @Component + habilita
(MVC) funcionalidades de Spring MVC.

@RestController Clase que gestiona APIs REST @Controller + @ResponseBody. Cada


método devuelve datos, no vistas.

@Configuration Clase que define Beans mediante Define la configuración de la aplicación.


métodos @Bean

ℹ NOTA
Todas las anotaciones específicas (@Service, @Repository, @Controller) son en el fondo
@Component con un nombre diferente. Spring las trata igual a nivel técnico. La diferencia es
semántica: ayudan a entender el rol de cada clase con solo mirar su anotación.
IFCD0182 — Desarrollo Web con Java

3.2 Ejemplo — Las tres capas MVC con anotaciones Spring


La capa de datos — Repositorio
package [Link];

import [Link];
import [Link].*;

// @Repository: Spring la gestiona y traduce excepciones de BD


@Repository
public class ProductoRepository {

// En una app real usaría JPA/Hibernate. Aquí usamos memoria.


private final List<String> productos = new ArrayList<>(
[Link]("Teclado", "Monitor", "Ratón")
);

public List<String> findAll() { return productos; }


public void save(String producto) { [Link](producto); }
}

La capa de negocio — Servicio


package [Link];

import [Link];
import [Link];

// @Service: Spring la gestiona como un servicio de negocio


@Service
public class ProductoService {

// Inyección por constructor — forma recomendada


private final ProductoRepository repo;

public ProductoService(ProductoRepository repo) {


[Link] = repo;
}

public List<String> obtenerTodos() {


return [Link]();
}
}
IFCD0182 — Desarrollo Web con Java

La capa de presentación — Controlador


package [Link];

import [Link];
import [Link];
import [Link];
import [Link];

// @Controller: Spring lo gestiona y habilita el manejo de peticiones web


@Controller
public class ProductoController {

private final ProductoService service;

public ProductoController(ProductoService service) {


[Link] = service;
}

// @GetMapping: responde a peticiones GET en /productos


@GetMapping("/productos")
public String listar(Model model) {
[Link]("productos", [Link]());
return "productos/lista"; // nombre de la vista Thymeleaf
}
}

🔗 CONEXIÓN CON EL MÓDULO 1


¿Ves el patrón MVC del Módulo 1?
Es exactamente lo mismo que hacías con Servlet + Servicio + JSP. Solo cambian las anotaciones y el
mecanismo de inyección. El Controlador llama al Servicio, el Servicio llama al Repositorio, el
Controlador pasa datos a la Vista. Mismo flujo, mejor implementación.
IFCD0182 — Desarrollo Web con Java

4. El ecosistema Spring — Los módulos principales

Spring no es un solo framework: es un ecosistema de módulos que puedes combinar según lo que necesites.
Conocer qué hace cada uno te ayuda a entender qué herramienta usar en cada situación:

Módulo Qué hace Lo verás en

Spring Core El núcleo: contenedor IoC, ApplicationContext, Esta unidad — es la base de


gestión de Beans todo

Spring MVC Framework web MVC: @Controller, Unidad 2 del Módulo 2


@GetMapping, Model, Thymeleaf

Spring Boot Autoconfiguración, servidor embebido, starters Esta unidad y todo el módulo
de dependencias

Spring Data JPA Acceso a base de datos con JPA/Hibernate. Unidad 2 — integrado en los
Repositorios automáticos. ejemplos

Spring Security Autenticación, autorización, protección CSRF, Módulo 3 — Unidad 1


JWT, OAuth2

Spring Test Utilidades para probar aplicaciones Spring con Módulo 3 — Testing
JUnit y Mockito

Spring Cloud Herramientas para microservicios: Eureka, Unidad 4 del Módulo 2


Gateway, Config Server

Spring Web Flux Programación reactiva para aplicaciones de alta Concepto avanzado — fuera
concurrencia del curso

💡 CONSEJO
No tienes que aprenderlos todos a la vez. Spring Boot se encarga de que cada módulo que añadas a
tu proyecto esté correctamente configurado y listo para usar. Vas añadiendo lo que necesitas y
Spring Boot lo integra automáticamente.
IFCD0182 — Desarrollo Web con Java

5. Spring Boot — Cómo funciona por dentro

5.1 Los tres pilares de Spring Boot

Pilar Qué significa Ejemplo práctico

Autoconfiguración Spring Boot detecta las librerías que tienes en el Si añades spring-boot-starter-data-
proyecto y las configura automáticamente con jpa, Spring Boot configura
valores razonables automáticamente Hibernate, el
DataSource y el EntityManager

Starters Dependencias empaquetadas que incluyen todo spring-boot-starter-web incluye


lo necesario para una funcionalidad, con Spring MVC + Tomcat embebido +
versiones garantizadas compatibles Jackson (JSON) + todo lo que
necesitas para crear una web

Servidor embebido Tomcat (o Jetty/Undertow) viene incluido Tu aplicación Spring Boot es un .jar
dentro del .jar. No hay que instalarlo ni ejecutable: java -jar [Link] la
configurarlo externamente arranca completa con su propio
servidor

5.2 La clase principal — @SpringBootApplication


Toda aplicación Spring Boot tiene una clase principal con el método main(). Esta clase tiene una única
anotación que lo activa todo:

package [Link];

import [Link];
import [Link];

// @SpringBootApplication es la combinación de tres anotaciones:


// @Configuration — esta clase puede definir Beans
// @EnableAutoConfiguration — activa la autoconfiguración
// @ComponentScan — escanea el paquete actual buscando Beans
@SpringBootApplication
public class TiendaApplication {

public static void main(String[] args) {


// Esta línea arranca toda la aplicación Spring:
// crea el contenedor IoC, registra los Beans,
// aplica la autoconfiguración y arranca el servidor web
[Link]([Link], args);
}
}
IFCD0182 — Desarrollo Web con Java

5.3 El archivo de configuración — [Link]


Spring Boot se configura a través de un archivo de propiedades que ajusta los valores por defecto de la
autoconfiguración. Está en src/main/resources/:

# Nombre de la aplicación
[Link]=tienda-app

# Puerto del servidor (por defecto es 8080)


[Link]=8080

# Base de datos H2 en memoria (perfecta para aprender)


[Link]=jdbc:h2:mem:tiendadb
[Link]-class-name=[Link]

# Consola web de H2 (solo para desarrollo)


[Link]=true
[Link]=/h2-console

# JPA — mostrar el SQL generado en la consola


[Link]-sql=true
[Link]-auto=update

ℹ NOTA
En aplicaciones reales también existe [Link], que usa un formato más legible para
configuraciones grandes. Spring Boot acepta ambos. Empezarás con .properties porque es más
sencillo para aprender, y podrás migrar a .yml cuando te resulte cómodo.
IFCD0182 — Desarrollo Web con Java

6. Crear tu primer proyecto Spring Boot

Spring Initializr ([Link]) es la herramienta oficial para crear proyectos Spring Boot. Genera
automáticamente la estructura del proyecto con todas las dependencias que necesitas. En menos de dos
minutos tienes un proyecto listo para ejecutar.

1 Abre [Link] en el navegador


Verás un formulario con las opciones del proyecto. No te agobies con tantas opciones: la
mayoría las puedes dejar por defecto.

2 Configura los metadatos del proyecto


Project: Maven. Language: Java. Spring Boot: elige la última versión estable (sin SNAPSHOT ni
M1).

Group: [Link] (por ejemplo [Link]). Artifact: mi-primera-app. Java: 17.

3 Añade las dependencias que necesitas


Haz clic en «Add Dependencies». Para empezar añade: Spring Web (para crear aplicaciones
web y APIs REST) y Spring Boot DevTools (para que la app se recargue automáticamente al
cambiar el código).

4 Genera y descarga el proyecto


Haz clic en «Generate». Se descargará un archivo .zip. Descomprímelo en tu carpeta de
proyectos.

5 Abre el proyecto en IntelliJ IDEA


File → Open → selecciona la carpeta del proyecto descomprimido. IntelliJ detectará que es un
proyecto Maven y descargará las dependencias automáticamente (puede tardar unos minutos
la primera vez).

6 Ejecuta la aplicación
Abre la clase principal (la que tiene @SpringBootApplication y main()) y haz clic en el triángulo
verde. En la consola verás el log de arranque de Spring Boot.

Cuando veas «Started MiPrimeraAppApplication in X seconds», abre el navegador y ve a


[Link] Verás una página de error (Whitelabel Error Page) porque no tienes
ningún endpoint todavía. Eso es correcto — significa que Spring Boot está funcionando.
IFCD0182 — Desarrollo Web con Java

6.1 La estructura del proyecto generado

mi-primera-app/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/academia/miprimeraapp/
│ │ │ └── [Link] ← clase principal
│ │ └── resources/
│ │ ├── static/ ← CSS, JS, imágenes
│ │ ├── templates/ ← vistas Thymeleaf
│ │ └── [Link] ← configuración
│ └── test/
│ └── java/ ← pruebas JUnit
└── [Link] ← dependencias Maven

⚙ CÓMO FUNCIONA
Dónde van las clases que crearás:
• src/main/java/com/tuapp/controlador/ — tus @Controller y @RestController
• src/main/java/com/tuapp/servicio/ — tus @Service
• src/main/java/com/tuapp/repositorio/ — tus @Repository y JPA Repositories
• src/main/java/com/tuapp/modelo/ — tus entidades y DTOs (clases de datos)
• src/main/resources/templates/ — tus páginas Thymeleaf (.html)

6.2 Tu primer endpoint en Spring Boot


Con el proyecto creado, añade tu primer endpoint. Crea una clase en el paquete del proyecto:

package [Link];

import [Link];
import [Link];

// @RestController: combina @Controller + @ResponseBody


// Devuelve el valor del método directamente como respuesta HTTP
@RestController
public class HolaController {

// Responde a GET /hola con el texto "¡Hola desde Spring Boot!"


@GetMapping("/hola")
public String hola() {
return "¡Hola desde Spring Boot!";
}

// Responde a GET /estado con un objeto que Spring convierte a JSON


@GetMapping("/estado")
IFCD0182 — Desarrollo Web con Java
public String estado() {
return "La aplicación está funcionando correctamente";
}
}
Reinicia la aplicación y accede a [Link] Verás el texto directamente en el navegador.
Acabas de crear tu primer endpoint con Spring Boot.

🤖 CON AYUDA DE LA IA
Experimenta y aprende con IA
Una vez que el endpoint /hola funciona, usa este prompt:

"Tengo un controlador Spring Boot con @RestController y @GetMapping('/hola') que devuelve un


String. Explícame qué hace Spring internamente cuando llega una petición GET a /hola: cómo
llega al método, qué hace @RestController con el valor devuelto y cómo se convierte en una
respuesta HTTP con código 200."
Comprender qué pasa por dentro de Spring cuando llega una petición es lo que te diferencia de
alguien que solo copia código. La IA puede trazar ese flujo interno paso a paso.
IFCD0182 — Desarrollo Web con Java

7. Errores frecuentes al empezar con Spring Boot

Error / Síntoma Causa habitual Solución

La aplicación no arranca: 'Field Spring no encuentra ningún Bean Añade la anotación correspondiente a
required a bean of type X' del tipo que necesita inyectar. la clase que necesita ser un Bean.
Falta la anotación @Service, Verifica que está en un paquete dentro
@Repository o @Component en del paquete principal.
la clase.

Error: 'Port 8080 already in use' Otro proceso está usando el Para el proceso anterior (botón Stop en
puerto 8080, probablemente otra IntelliJ) o cambia el puerto en
instancia de Spring Boot o [Link]:
Tomcat. [Link]=8081

La clase @Component no es La clase está en un paquete fuera Mueve la clase a un subpaquete del
detectada por Spring del paquete base de paquete principal, o añade
@SpringBootApplication. @ComponentScan con el paquete
correcto a la clase principal.

@Autowired da La clase que tiene @Autowired Nunca crees con new una clase que
NullPointerException en tiempo fue creada con new en lugar de tiene dependencias inyectadas. Obtén
de ejecución dejar que Spring la gestionara. el objeto del contenedor Spring
(inyéctalo en otra clase gestionada).

La versión de Spring Boot en Mezcla de versiones Usa siempre la última versión estable
[Link] da conflictos incompatibles o uso de de Spring Boot (sin SNAPSHOT). Spring
SNAPSHOT en producción. Initializr la selecciona
automáticamente.

No encuentra la vista Thymeleaf El nombre devuelto por el Verifica que el String devuelto (ej.
(Error 500: template not found) @Controller no coincide con el "productos/lista") corresponde a
archivo .html en /templates. /templates/productos/[Link]
exactamente.

💡 CONSEJO
El log de arranque de Spring Boot es tu mejor aliado para depurar errores. Cuando algo falla, lee el
mensaje de error completo antes de buscar en internet. Spring Boot suele indicar exactamente qué
falta y a veces hasta sugiere cómo arreglarlo.
IFCD0182 — Desarrollo Web con Java

8. Buenas prácticas con Spring Boot

✔ BUENAS PRÁCTICAS
Aplica estas prácticas desde el primer proyecto:
• Organiza por paquetes desde el principio: controlador, servicio, repositorio, modelo. Un
proyecto bien organizado es fácil de navegar para cualquier desarrollador.
• Usa inyección por constructor: no por campo. Es más fácil de probar y Spring Boot la
soporta de forma nativa sin necesitar @Autowired explícito.
• No guardes secretos en [Link]: contraseñas, claves de API y datos
sensibles deben ir en variables de entorno, no en el archivo de configuración que se sube
al repositorio.
• Un Controller, un recurso: ProductoController gestiona /productos, UsuarioController
gestiona /usuarios. No pongas todo en un único Controller.
• Los Controllers no tienen lógica de negocio: solo llaman a los Services y devuelven la
respuesta. La lógica siempre en el Service.
• DevTools en desarrollo, nunca en producción: spring-boot-devtools debe estar como
dependencia de scope desarrollo, no incluida en el .jar final.
IFCD0182 — Desarrollo Web con Java

9. Actividad práctica guiada

Título Mi primera aplicación Spring Boot con capas correctas

Duración 60 minutos

Objetivo Crear desde cero un proyecto Spring Boot con las tres capas (Controlador, Servicio,
Repositorio) y verificar la inyección de dependencias

Nivel Guiado — el formador explica cada paso antes de implementarlo

Enunciado
Crea una aplicación Spring Boot que gestione una lista de tareas (to-do list) en memoria. Al acceder a /tareas
deberá devolver la lista de tareas en texto plano. Al acceder a /tareas/añadir?texto=MiTarea deberá añadir
una tarea y confirmar con un mensaje.

1 Genera el proyecto en Spring Initializr


Ve a [Link]. Maven, Java 17, Spring Boot última versión estable. Añade la dependencia
Spring Web. Descarga, descomprime y abre en IntelliJ.

2 Crea el modelo — clase Tarea


Crea la clase Tarea en el paquete modelo con un id (Long), texto (String) y completada
(boolean). Constructor, getters y setters. No hace falta @Component — es solo un objeto de
datos.

3 Crea el repositorio — TareaRepository


Clase con @Repository que guarda las tareas en un ArrayList. Métodos: findAll() y save(Tarea).
No recibe parámetros HTTP.

4 Crea el servicio — TareaService


Clase con @Service. Inyecta TareaRepository por constructor. Métodos: obtenerTodas() y
añadir(String texto). El servicio crea el objeto Tarea y llama al repositorio.

5 Crea el controlador — TareaController


Clase con @RestController. Inyecta TareaService por constructor. Mapea GET /tareas a
obtenerTodas() y GET /tareas/añadir con @RequestParam a añadir().
IFCD0182 — Desarrollo Web con Java

6 Ejecuta y prueba
Arranca la aplicación. Abre el navegador y prueba: [Link] (debe devolver
la lista vacía) y [Link] (añade una tarea).

Dibuja en papel el flujo: ¿qué clase llama a cuál? ¿Quién crea qué objetos? ¿Dónde está la
inyección de dependencias?

🤖 CON AYUDA DE LA IA
Verifica tu arquitectura con IA
Cuando la aplicación funcione, pide a la IA que verifique si la arquitectura es correcta:

"Revisa estas clases de mi aplicación Spring Boot [pega el código de las cuatro clases]. ¿Está bien
aplicado el patrón MVC? ¿Hay alguna clase que esté haciendo el trabajo de otra? ¿La inyección
de dependencias está bien configurada? ¿Usaría inyección por campo o por constructor?"
IFCD0182 — Desarrollo Web con Java

10. Actividad práctica autónoma

Título Gestor de contactos Spring Boot con tres capas

Duración 90 minutos

Objetivo Desarrollar autónomamente una aplicación Spring Boot con IoC, DI y capas
correctamente separadas

Nivel Autónomo — documentación Spring, apuntes e IA disponibles con las normas


habituales

Entrega Proyecto en .zip con un comentario en cada clase explicando su rol en la arquitectura

Enunciado
Desarrolla una aplicación Spring Boot que gestione una agenda de contactos en memoria con los siguientes
endpoints:

• GET /contactos — Devuelve todos los contactos en formato texto o JSON básico.
• GET /contactos/{id} — Devuelve un contacto por su id. Si no existe, devuelve el mensaje 'Contacto
no encontrado'.
• GET /contactos/añadir — Recibe nombre, email y telefono como @RequestParam. Valida que
ninguno esté vacío. Añade el contacto y confirma.
• GET /contactos/buscar — Recibe un parámetro texto y devuelve todos los contactos cuyo nombre lo
contenga (sin distinguir mayúsculas/minúsculas).
• GET /info — Devuelve el número total de contactos y el nombre de la aplicación (desde
[Link]).

Requisitos de arquitectura obligatorios


• Tres capas separadas: @RestController, @Service, @Repository.
• Inyección por constructor en todas las clases que lo necesiten.
• El Controller no tiene ninguna lógica de negocio: solo delega en el Service.
• El Service no accede a datos directamente: delega en el Repository.
• El Repository no sabe nada de HTTP: trabaja solo con objetos Contacto.
IFCD0182 — Desarrollo Web con Java

Criterios de evaluación
Criterio Descripción Peso

Arquitectura Tres capas con anotaciones correctas e inyección por 30%


constructor en todas

Funcionalidad Los cinco endpoints funcionan según lo descrito 30%

Separación Ninguna capa hace el trabajo de otra. Controller sin 20%


lógica. Repository sin HTTP.

Validación /contactos/añadir valida que los campos no estén 10%


vacíos antes de guardar

Comentarios Cada clase tiene un comentario de cabecera 10%


explicando su responsabilidad

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir a la IA que revise si la arquitectura está bien, preguntar por qué un error
ocurre, pedir ejemplos de @RequestParam.
• No permitido: pedir el proyecto completo, el Controller completo o el Service completo.
• Recomendado: cuando termines, pide a la IA que actúe como revisor técnico y detecte si
alguna capa está violando sus responsabilidades.
IFCD0182 — Desarrollo Web con Java

11. Resumen de la unidad

Concepto Lo esencial

Spring Framework Ecosistema de librerías Java que simplifica el desarrollo empresarial. Nació en
2003 como alternativa ligera a J2EE.

Spring Boot Capa sobre Spring que autoconfigura todo. Servidor embebido, starters, cero
XML. El estándar actual del mercado.

IoC Inversión de Control: Spring crea y gestiona los objetos. Tu código no usa
new para objetos gestionados por Spring.

ApplicationContext El contenedor IoC de Spring. Crea, configura y gestiona el ciclo de vida de


todos los Beans.

DI Inyección de Dependencias: Spring entrega automáticamente las


dependencias que cada Bean necesita.

Inyección por constructor La forma recomendada. Permite campos final y facilita el testing. Con
Lombok se genera automáticamente.

@Component y variantes @Service (negocio), @Repository (datos), @Controller (web),


@RestController (APIs). Todas son @Component con semántica.

@SpringBootApplication Anotación de la clase principal. Activa IoC, autoconfiguración y escaneo de


componentes.

[Link] Archivo de configuración de Spring Boot. Ajusta los valores por defecto de la
autoconfiguración.

Spring Initializr [Link] — generador oficial de proyectos Spring Boot. El punto de


partida de cualquier proyecto nuevo.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
1. ¿Qué significa que Spring usa Inversión de Control? ¿En qué se diferencia de crear
objetos con new?
2. ¿Cuáles son las tres formas de inyección de dependencias en Spring? ¿Cuál es la
recomendada y por qué?
3. ¿Qué diferencia hay entre @Service, @Repository y @Controller? ¿Son lo mismo
técnicamente?
4. ¿Qué hace @SpringBootApplication exactamente? ¿Cuántas anotaciones combina?
5. Si una clase @Service no es detectada por Spring, ¿qué es lo primero que compruebas?
6. ¿Qué ventaja tiene Spring Boot frente a configurar Spring Framework manualmente?
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Consolidación — prompt de entrevista técnica
Para repasar antes de la siguiente sesión:

"Actúa como entrevistador técnico para un puesto de desarrollador Java con Spring Boot. Hazme
5 preguntas sobre IoC, inyección de dependencias, las anotaciones de Spring (@Component,
@Service, @Repository, @Controller) y Spring Boot. Después de cada respuesta mía, corrígeme y
dime qué añadiría un candidato con 2 años de experiencia en Spring."

📊 NOTA DE MERCADO LABORAL


Lo que viene en la Unidad 2:
En la siguiente unidad construirás tu primera aplicación web completa con Spring MVC y Thymeleaf:
el equivalente a lo que hacías con Servlet + JSP en el Módulo 1, pero con la potencia y la comodidad
de Spring. Verás @GetMapping, @PostMapping, @ModelAttribute, validación con @Valid y cómo
gestionar formularios.

Todo lo que has aprendido en esta unidad —IoC, DI, Beans, capas— es la base sobre la que se
construye Spring MVC. Llegas preparado.
IFCD0182 — Desarrollo Web con Java

MÓDULO 2 — Spring Framework, APIs REST y Microservicios


UNIDAD 2

Spring MVC y Thymeleaf


Controladores, vistas, formularios y validación: construye tu primera web completa con
Spring

Duración estimada 6 horas (teoría + prácticas)

Módulo 2 — Spring Framework, APIs REST y Microservicios

Unidad anterior Unidad 1 — Spring Framework, IoC, DI y Spring Boot

Nivel de entrada Proyecto Spring Boot funcionando, tres capas con @Service y @Repository
creadas

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Crear aplicaciones web completas con Spring MVC: controladores, vistas
Thymeleaf, formularios y validación

🔗 CONEXIÓN CON LO ANTERIOR


Conexión con lo que ya sabes:
En el Módulo 1 construiste aplicaciones MVC con Servlet + JSP. En la Unidad 1 del Módulo 2
aprendiste los fundamentos de Spring. En esta unidad juntas ambas cosas: vas a construir el mismo
tipo de aplicación MVC, pero con @Controller de Spring y Thymeleaf en lugar de Servlet y JSP. El
patrón es idéntico. La implementación, mucho más cómoda.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Crear controladores Spring MVC con @Controller, @GetMapping y @PostMapping.
• Pasar datos del controlador a la vista usando Model y @ModelAttribute.
• Escribir páginas Thymeleaf con th:text, th:each, th:if y th:href.
• Crear formularios con Thymeleaf vinculados a objetos Java.
• Validar datos de formularios con @Valid y las anotaciones de Bean Validation.
• Mostrar errores de validación en la vista de forma clara y localizada.
• Implementar el patrón Post-Redirect-Get para evitar reenvíos de formularios.
IFCD0182 — Desarrollo Web con Java
IFCD0182 — Desarrollo Web con Java

1. Cómo funciona Spring MVC por dentro

1.1 El DispatcherServlet — el portero de Spring MVC


Spring MVC funciona con un único Servlet central llamado DispatcherServlet. Todas las peticiones HTTP que
llegan a tu aplicación pasan primero por él. Es el equivalente al Servlet que escribías tú manualmente en el
Módulo 1, pero implementado por Spring y capaz de gestionar toda la aplicación solo.

⚙ CÓMO FUNCIONA
El flujo completo de una petición en Spring MVC:
1. El navegador envía una petición GET a /productos.
2. El DispatcherServlet la recibe (intercepta todas las peticiones).
3. Consulta el HandlerMapping para saber qué método @Controller tiene mapeada esa
URL.
4. Llama al método del @Controller correspondiente, pasándole los parámetros necesarios.
5. El @Controller ejecuta la lógica (llama al @Service), carga datos en el Model y devuelve
el nombre de la vista.
6. El DispatcherServlet pide al ViewResolver que encuentre la plantilla Thymeleaf con ese
nombre.
7. Thymeleaf combina la plantilla HTML con los datos del Model y genera el HTML final.
8. El DispatcherServlet envía el HTML al navegador.

🔗 CONEXIÓN CON LO ANTERIOR


Comparado con el Módulo 1:
Módulo 1 — Java EE clásico Módulo 2 — Spring MVC

@WebServlet("/productos") en cada Servlet Un solo DispatcherServlet centraliza todo

doGet() / doPost() con código repetitivo @GetMapping / @PostMapping declarativos

[Link]("clave", valor) [Link]("clave", valor)

JSP con JSTL y scriptlets Thymeleaf con atributos th:*

[Link]() o sendRedirect() return "nombre-vista" o "redirect:/ruta"


IFCD0182 — Desarrollo Web con Java

2. El Controlador Spring MVC — @Controller

2.1 Anotaciones de mapeo de peticiones


Spring MVC usa anotaciones para mapear URLs a métodos Java. Es mucho más expresivo que el
doGet()/doPost() del Módulo 1 porque el propósito de cada método es evidente con solo leer su anotación:

Anotación Método HTTP Cuándo usar

@GetMapping("/ruta") GET Leer o mostrar datos. Listar productos, mostrar formulario


vacío.

@PostMapping("/ruta") POST Crear datos nuevos. Procesar un formulario de registro.

@PutMapping("/ruta") PUT Actualizar datos existentes completamente. (Más usado en


APIs REST)

@DeleteMapping("/ruta") DELETE Eliminar datos. (Más usado en APIs REST)

@PatchMapping("/ruta") PATCH Actualizar datos parcialmente. (APIs REST)

@RequestMapping("/ruta") Todos Mapeo genérico. Puede especificar el método con


method=.

2.2 El objeto Model — cómo pasar datos a la vista


El Model es el objeto que Spring inyecta automáticamente en los métodos del controlador cuando lo
declaras como parámetro. Es el equivalente directo del [Link]() del Módulo 1, pero más
limpio:

@Controller
@RequestMapping("/productos")
public class ProductoController {

private final ProductoService service;

public ProductoController(ProductoService service) {


[Link] = service;
}

// Spring inyecta Model automáticamente — no hay que crearlo


@GetMapping
public String listar(Model model) {
// Equivalente a [Link]("productos", lista)
[Link]("productos", [Link]());
[Link]("titulo", "Catálogo de productos");

// Devolver el nombre de la vista Thymeleaf


// Spring busca /templates/productos/[Link]
return "productos/lista";
}

@GetMapping("/{id}")
public String detalle(@PathVariable Long id, Model model) {
IFCD0182 — Desarrollo Web con Java
[Link]("producto", [Link](id));
return "productos/detalle";
}
}

2.3 Parámetros útiles en los métodos del controlador


Spring MVC puede inyectar automáticamente muchos tipos de parámetros en los métodos del controlador
según lo que necesites:

Anotación / Tipo Qué contiene Ejemplo

Model Mapa para pasar datos a la vista [Link]("clave", valor)

@PathVariable Valor extraído de la URL: @PathVariable Long id


/productos/{id}

@RequestParam Parámetro de la query string: @RequestParam String texto


/buscar?texto=X

@ModelAttribute Objeto Java relleno con los campos @ModelAttribute Producto producto
del formulario

HttpSession La sesión HTTP del usuario [Link]("usuario")

@RequestHeader Una cabecera HTTP específica @RequestHeader("User-Agent")


String ua

RedirectAttributes Atributos que sobreviven a un [Link]("ok",


redirect "Guardado")

🤖 CON AYUDA DE LA IA
Practica con distintos parámetros de controlador
Para entender cuándo usar cada tipo de parámetro:

"Tengo un controlador Spring MVC. Explícame la diferencia entre @PathVariable,


@RequestParam y @ModelAttribute con un ejemplo concreto de cuándo usaría cada uno en una
aplicación de gestión de productos. Muéstrame el código del controlador y la URL
correspondiente para cada caso."
IFCD0182 — Desarrollo Web con Java

3. Thymeleaf — La vista de Spring MVC

3.1 ¿Qué es Thymeleaf y por qué reemplazó a JSP?


Thymeleaf es el motor de plantillas que Spring Boot usa por defecto para las vistas. Es la evolución moderna
de JSP. Sus archivos son HTML puro y válido que funciona directamente en el navegador sin necesitar un
servidor, lo que facilita enormemente el trabajo con diseñadores.

Aspecto JSP (Módulo 1) Thymeleaf (Módulo 2)

Extensión de archivo .jsp .html

¿Funciona sin servidor? No. Solo en el contenedor. Sí. Es HTML válido y se abre en el
navegador.

Sintaxis de variables ${variable} con JSTL th:text="${variable}" como atributo


HTML

Iteración <c:forEach var="x" <tr th:each="x : ${lista}">


items="${lista}">

Condiciones <c:if test="${condicion}"> <div th:if="${condicion}">

Enlace a URL <a href="/ruta"> <a th:href="@{/ruta}">

Formularios <form action="/ruta"> <form th:action="@{/ruta}"


th:object="${objeto}">

Integración con Spring Buena pero limitada Nativa. @Valid, errores de validación,
mensajes.

3.2 Los atributos Thymeleaf más importantes


Thymeleaf añade sus funcionalidades como atributos HTML con el prefijo th:. El navegador los ignora (los
trata como atributos personalizados desconocidos), pero Thymeleaf los procesa en el servidor para generar
el HTML dinámico:

Atributo Para qué sirve Ejemplo

th:text Reemplaza el texto del elemento con el <span


valor de la expresión th:text="${[Link]}">Nombre</s
pan>

th:each Itera sobre una colección generando un <tr th:each="p : ${productos}">


elemento por ítem

th:if Muestra el elemento solo si la condición es <p th:if="${[Link]}">Sin


verdadera resultados</p>

th:unless Muestra el elemento solo si la condición es <p th:unless="${[Link]}">Hay


FALSA resultados</p>
IFCD0182 — Desarrollo Web con Java

th:href Genera una URL gestionada por Spring (con <a


contexto de la app) th:href="@{/productos/{id}(id=${[Link]})}">V
er</a>

th:action URL del formulario, gestionada por Spring <form th:action="@{/guardar}">

th:object Vincula el formulario con un objeto Java <form th:object="${producto}">


(@ModelAttribute)

th:field Vincula un campo del formulario con un <input th:field="*{nombre}"/>


atributo del objeto

th:errors Muestra los errores de validación de un <span th:errors="*{nombre}"


campo específico class="error"/>

th:class Aplica clases CSS dinámicamente según <div th:class="${activo} ? 'activo' :


una condición 'inactivo'">

3.3 Tu primera página Thymeleaf completa


Veamos cómo se traduce lo que hacías en JSP a Thymeleaf con un ejemplo real de listado de productos:

Controlador — prepara los datos


@GetMapping("/productos")
public String listar(Model model) {
[Link]("productos", [Link]());
[Link]("total", [Link]());
return "productos/lista"; // → /templates/productos/[Link]
}

Vista — /templates/productos/[Link]
<!DOCTYPE html>
<html lang="es" xmlns:th="[Link]
<head>
<meta charset="UTF-8">
<title>Catálogo</title>
</head>
<body>
<h1>Catálogo de productos</h1>

<!-- th:if: solo muestra si la lista no está vacía -->


<p th:if="${[Link]}">No hay productos disponibles.</p>

<!-- th:unless: lo contrario — muestra si hay productos -->


<table th:unless="${[Link]}">
<thead>
<tr><th>Nombre</th><th>Precio</th><th>Acciones</th></tr>
</thead>
<tbody>
<!-- th:each itera. p es cada producto. pStat tiene index, count, etc. -->
<tr th:each="p, pStat : ${productos}">
<td th:text="${[Link]}">Nombre aquí</td>
<td th:text="${#[Link]([Link], 1, 2)} + ' €'">0.00 €</td>
<!-- th:href genera la URL con el id del producto -->
IFCD0182 — Desarrollo Web con Java
<td><a th:href="@{/productos/{id}(id=${[Link]})}">Ver detalle</a></td>
</tr>
</tbody>
</table>

<p>Total de productos: <span th:text="${total}">0</span></p>


</body>
</html>

✔ BUENAS PRÁCTICAS
Cosas importantes a observar:
• xmlns:th="[Link] — Declaración del namespace de Thymeleaf en
el <html>. Sin ella los atributos th: no funcionan.
• th:each="p, pStat : ${productos}" — La variable pStat es opcional y da acceso al índice
([Link]), si es el primero ([Link]), el último ([Link]) y el total ([Link]).
• ${#[Link](...)} — Thymeleaf incluye utilidades para formatear
números, fechas y textos. Son muy útiles para presentación.
• @{/productos/{id}(id=${[Link]})} — Sintaxis para URLs con variables en Thymeleaf.
Siempre usa @{} para URLs en lugar de cadenas fijas.
IFCD0182 — Desarrollo Web con Java

4. Formularios — Crear y procesar datos

El manejo de formularios es una de las partes más importantes del desarrollo web. Spring MVC + Thymeleaf
lo hace mucho más cómodo que el enfoque manual del Módulo 1. El patrón es siempre el mismo: un GET
muestra el formulario, un POST lo procesa.

4.1 El objeto de formulario — @ModelAttribute


En lugar de leer los parámetros uno a uno con [Link](), Spring MVC puede llenar
automáticamente un objeto Java con todos los campos del formulario. A esto se le llama binding (vinculación
de datos).

La clase del formulario — ProductoForm


package [Link];

// Clase simple que representa los datos del formulario


// Spring la rellenará automáticamente con los campos enviados
public class ProductoForm {
private String nombre;
private double precio;
private int stock;

public ProductoForm() {}
// Getters y setters...
}

El controlador — GET muestra el formulario, POST lo procesa


@Controller
@RequestMapping("/productos")
public class ProductoController {

private final ProductoService service;


public ProductoController(ProductoService service) { [Link] = service; }

// GET /productos/nuevo — muestra el formulario vacío


@GetMapping("/nuevo")
public String formulario(Model model) {
// Pasamos un objeto vacío para que Thymeleaf lo vincule al formulario
[Link]("productoForm", new ProductoForm());
return "productos/formulario";
}

// POST /productos/nuevo — procesa el formulario enviado


@PostMapping("/nuevo")
public String guardar(@ModelAttribute ProductoForm form,
RedirectAttributes attr) {
// Spring rellena ProductoForm automáticamente con los campos del formulario
[Link](form);
IFCD0182 — Desarrollo Web con Java

// FlashAttribute: sobrevive al redirect y luego desaparece


[Link]("mensaje", "Producto guardado correctamente");

// Post-Redirect-Get: evita reenvío del formulario al pulsar F5


return "redirect:/productos";
}
}

La vista — /templates/productos/[Link]
<!DOCTYPE html>
<html lang="es" xmlns:th="[Link]
<head><title>Nuevo producto</title></head>
<body>
<h1>Añadir producto</h1>

<!-- th:action: URL del formulario. th:object: el objeto vinculado -->


<form th:action="@{/productos/nuevo}" th:object="${productoForm}"
method="post">

<label for="nombre">Nombre:</label>
<!-- th:field vincula el input con [Link] -->
<!-- Genera automáticamente id, name y value correctos -->
<input type="text" th:field="*{nombre}" id="nombre"/>

<label for="precio">Precio:</label>
<input type="number" th:field="*{precio}" step="0.01" id="precio"/>

<label for="stock">Stock:</label>
<input type="number" th:field="*{stock}" id="stock"/>

<button type="submit">Guardar</button>
<a th:href="@{/productos}">Cancelar</a>
</form>
</body>
</html>

👁 FÍJATE
El patrón Post-Redirect-Get:
Cuando un formulario se envía por POST y el servidor responde directamente con HTML, si el usuario
pulsa F5 el navegador pregunta si quiere reenviar el formulario. Esto puede crear datos duplicados.

Con return "redirect:/productos" el servidor responde con un 302 que redirige al navegador a una
nueva URL con GET. El historial del navegador queda en la URL de listado, no en la de envío del
formulario. F5 ya no reenvía el POST.
IFCD0182 — Desarrollo Web con Java

5. Validación de formularios con @Valid

Spring MVC se integra con Bean Validation (JSR-380) para validar los datos de los formularios de forma
declarativa. En lugar de escribir código de validación en el controlador, declaras las reglas directamente en la
clase del formulario con anotaciones.

5.1 Anotaciones de validación más usadas

Anotación Valida que... Mensaje de error personalizable

@NotBlank El texto no es nulo ni está vacío (ni solo message="El nombre es obligatorio"
espacios)

@NotNull El valor no es nulo (para tipos no String) message="El precio es obligatorio"

@Size(min=, max=) La longitud del texto está dentro del message="Entre 3 y 100 caracteres"
rango

@Min(value=) El número es mayor o igual al mínimo message="Debe ser al menos 0"

@Max(value=) El número es menor o igual al máximo message="No puede superar 9999"

@Email El texto tiene formato de email válido message="Email no válido"

@Pattern(regexp=) El texto coincide con la expresión message="Solo letras y números"


regular

@Positive El número es positivo (mayor que cero) message="Debe ser un número positivo"

@Future La fecha es futura message="La fecha debe ser futura"

5.2 Validación completa — clase, controlador y vista


Clase con validaciones declarativas
import [Link].*;

public class ProductoForm {

@NotBlank(message = "El nombre no puede estar vacío")


@Size(min = 3, max = 100, message = "Entre 3 y 100 caracteres")
private String nombre;

@NotNull(message = "El precio es obligatorio")


@Positive(message = "El precio debe ser un número positivo")
private Double precio;

@NotNull(message = "El stock es obligatorio")


@Min(value = 0, message = "El stock no puede ser negativo")
private Integer stock;

// Constructor vacío, getters y setters...


}
IFCD0182 — Desarrollo Web con Java

Controlador con @Valid y BindingResult


@PostMapping("/nuevo")
public String guardar(@Valid @ModelAttribute("productoForm") ProductoForm form,
BindingResult resultado,
RedirectAttributes attr) {

// BindingResult contiene los errores de validación


// IMPORTANTE: debe ir justo después del @ModelAttribute, sin nada entre medio
if ([Link]()) {
// Si hay errores, volvemos al formulario con los mensajes
// No hace falta pasar el form al model: @ModelAttribute lo hace
automáticamente
return "productos/formulario";
}

// Solo llegamos aquí si todos los campos son válidos


[Link](form);
[Link]("mensaje", "Producto guardado correctamente");
return "redirect:/productos";
}

Vista con mensajes de error localizados


<form th:action="@{/productos/nuevo}" th:object="${productoForm}" method="post">

<!-- Mensaje de éxito (si viene de un flashAttribute tras redirect) -->


<div th:if="${mensaje}" class="exito" th:text="${mensaje}"></div>

<label for="nombre">Nombre:</label>
<input type="text" th:field="*{nombre}" id="nombre"
th:classappend="${#[Link]('nombre')} ? 'error-campo'"/>
<!-- th:errors muestra el mensaje de error de este campo específico -->
<span th:if="${#[Link]('nombre')}"
th:errors="*{nombre}" class="error-texto"></span>

<label for="precio">Precio (€):</label>


<input type="number" th:field="*{precio}" step="0.01" id="precio"
th:classappend="${#[Link]('precio')} ? 'error-campo'"/>
<span th:if="${#[Link]('precio')}"
th:errors="*{precio}" class="error-texto"></span>

<label for="stock">Stock:</label>
<input type="number" th:field="*{stock}" id="stock"/>
<span th:if="${#[Link]('stock')}"
th:errors="*{stock}" class="error-texto"></span>

<button type="submit">Guardar</button>
</form>
IFCD0182 — Desarrollo Web con Java

✔ BUENAS PRÁCTICAS
Reglas que debes respetar siempre con @Valid:
• BindingResult va justo después de @ModelAttribute: si pones otro parámetro entre
medio, Spring lanza una excepción en lugar de capturar los errores.
• El nombre en @ModelAttribute debe coincidir con el th:object de la vista: si el
controlador usa productoForm como nombre, la vista también debe usar
${productoForm}.
• Usa Double e Integer en lugar de double e int: los tipos primitivos no pueden ser null, lo
que impide distinguir entre 0 y «no introducido».
• Siempre incluye messages personalizados: los mensajes por defecto de Bean Validation
están en inglés y son poco descriptivos para el usuario final.

🤖 CON AYUDA DE LA IA
Genera validaciones personalizadas con IA
Para validaciones que van más allá de las anotaciones estándar:

"Necesito validar en Spring Boot que el campo email de mi formulario no solo tenga formato
correcto (@Email) sino que además pertenezca al dominio @[Link]. ¿Cómo creo una
anotación de validación personalizada en Spring Boot? Muéstrame el código completo: la
anotación, el validador y cómo usarla en la clase del formulario."
Las validaciones personalizadas son un tema muy valorado en entrevistas técnicas para perfiles con
algo de experiencia.
IFCD0182 — Desarrollo Web con Java

6. Errores frecuentes en Spring MVC y Thymeleaf

Error / Síntoma Causa Solución

Error 500: template not found El String devuelto por el Verifica que return "productos/lista"
controlador no coincide con corresponde a
ningún archivo en /templates /templates/productos/[Link]
exactamente (sin extensión en el
return)

th:text muestra literalmente Falta Añade el namespace de Thymeleaf en


'${variable}' en el HTML xmlns:th="[Link] la apertura del <html>
org" en el elemento <html>

Los errores de validación no BindingResult no está justo El orden de parámetros importa:


aparecen aunque el campo esté después del @ModelAttribute en @Valid @ModelAttribute Objeto obj,
vacío el método BindingResult result — sin nada entre
medio

NullPointerException al enviar el El objeto pasado al formulario con Verifica que el GET añade al model el
formulario [Link]() es null o el mismo nombre que usa th:object en la
nombre no coincide vista

Tras guardar, F5 reenvía el El POST devuelve directamente Usa return "redirect:/ruta" tras
formulario y duplica los datos una vista en lugar de hacer guardar. Implementa el patrón Post-
redirect Redirect-Get

Los tipos int/double del Los primitivos no admiten null: si Usa Integer y Double en lugar de int y
formulario dan error de el campo viene vacío, la double en las clases de formulario
conversión con campo vacío conversión falla

El th:href no incluye el context Se usó href="/ruta" en lugar de Usa siempre th:href con @{} para que
path y la URL es incorrecta th:href="@{/ruta}" Thymeleaf añada automáticamente el
context path
IFCD0182 — Desarrollo Web con Java

7. Buenas prácticas en Spring MVC y Thymeleaf

✔ BUENAS PRÁCTICAS
Aplica estas prácticas en todas tus vistas y controladores:
• Clases de formulario separadas de las entidades: ProductoForm para el formulario,
Producto para la entidad de base de datos. No uses la misma clase para ambas cosas.
• Siempre Post-Redirect-Get tras un POST exitoso: return "redirect:/ruta" después de
guardar. Sin excepciones.
• Mensajes de error en el idioma del usuario: personaliza todos los mensajes de
@NotBlank, @Size, etc. en español desde el principio.
• Layouts con Thymeleaf Fragments: crea un layout base con cabecera y pie de página y
reutilízalo en todas las vistas con th:insert o th:replace. Así un cambio en la cabecera
afecta a toda la app.
• Nunca lógica de negocio en la vista: th:if está bien para mostrar/ocultar elementos, pero
nunca hagas cálculos o accesos a base de datos desde Thymeleaf.
• @RequestMapping a nivel de clase para agrupar: @RequestMapping("/productos") en
la clase evita repetir /productos en cada @GetMapping y @PostMapping.
• FlashAttributes para mensajes de éxito: addFlashAttribute sobrevive al redirect y luego
desaparece automáticamente. Es la forma correcta de mostrar mensajes de operación
exitosa.
IFCD0182 — Desarrollo Web con Java

8. Actividad práctica guiada

Título CRUD completo de productos con Spring MVC y Thymeleaf

Duración 75 minutos

Objetivo Construir un CRUD (Crear, Listar, Editar, Eliminar) completo con Spring MVC, Thymeleaf
y validación

Nivel Guiado — el formador desarrolla cada parte explicando cada decisión

Enunciado
Amplía el proyecto de la Unidad 1 (gestor de tareas) o crea uno nuevo. Implementa un CRUD de libros con
los siguientes endpoints y vistas:

1 Listar libros — GET /libros


Vista [Link] con tabla de todos los libros. Si no hay libros, muestra un mensaje. Incluye
botones de editar y eliminar en cada fila. Muestra el mensaje de éxito si viene de un
flashAttribute.

2 Crear libro — GET /libros/nuevo y POST /libros/nuevo


Clase LibroForm con titulo (@NotBlank), autor (@NotBlank), isbn (@Pattern de 10-13 dígitos)
y anio (@Min(1450) @Max(año actual)). GET muestra el formulario vacío. POST valida, guarda
si es correcto o vuelve con errores si no lo es. Mensaje de éxito con flashAttribute.

3 Editar libro — GET /libros/{id}/editar y POST /libros/{id}/editar


GET carga el libro existente en el formulario. POST valida y actualiza. Si el id no existe, redirige
a /libros con un mensaje de error.

4 Eliminar libro — POST /libros/{id}/eliminar


Elimina el libro por id. Redirige a /libros con mensaje de confirmación. El botón de eliminar es
un formulario con method=post (no un enlace GET, por seguridad).

5 Layout compartido con Thymeleaf Fragments


Crea un fragmento base ([Link]) con la cabecera y el pie de página. Incluye ese
fragmento en todas las vistas con th:insert. Así un cambio en la cabecera se refleja en toda la
app.
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Usa la IA para entender th:replace y th:insert
Los Thymeleaf Fragments son muy potentes pero tienen una sintaxis algo peculiar. Si tienes dudas:

"Explícame cómo funcionan los Thymeleaf Fragments para crear un layout compartido en Spring
Boot. Muéstrame cómo crear un archivo [Link] con cabecera y pie de página, y cómo
incluirlo en otra vista usando th:insert y th:replace. ¿Cuál es la diferencia entre los dos?"
IFCD0182 — Desarrollo Web con Java

9. Actividad práctica autónoma

Título Gestor de empleados con Spring MVC, Thymeleaf y validación completa

Duración 90 minutos

Objetivo Desarrollar autónomamente una aplicación web completa con CRUD, validación y
layout compartido

Nivel Autónomo — documentación, apuntes e IA disponibles con las normas habituales

Entrega Proyecto en .zip. Debe incluir al menos un fragmento Thymeleaf compartido entre
vistas

Enunciado
Desarrolla una aplicación web de gestión de empleados para una empresa. La aplicación debe permitir:

• Listar empleados: tabla con nombre, email, departamento y salario. Si no hay empleados, mensaje
informativo. Botones de editar y eliminar.
• Crear empleado: formulario con nombre (@NotBlank, máx 80 chars), email (@Email), departamento
(desplegable con mínimo 4 opciones), salario (@Positive), fechaAlta (tipo LocalDate,
@PastOrPresent). Validación completa con mensajes en español.
• Editar empleado: formulario con los datos precargados. Mismas validaciones que al crear.
• Eliminar empleado: confirmación con POST y flashAttribute de éxito.
• Buscar empleados: campo de búsqueda por nombre o departamento. GET
/empleados/buscar?texto=X filtra la lista.
• Layout compartido: cabecera con nombre de la app y menú de navegación, pie de página con tu
nombre. Incluido en todas las vistas con Thymeleaf Fragments.

Criterios de evaluación
Criterio Descripción Peso

CRUD funcional Crear, listar, editar y eliminar funcionan 30%


correctamente con redirecciones

Validación Todos los campos validan con mensajes en español y 25%


errores localizados

Post-Redirect-Get Todos los POST redirigen. F5 no reenvía el formulario. 15%

Layout Thymeleaf Cabecera y pie compartidos con th:insert en todas las 15%
vistas

Búsqueda El endpoint /buscar filtra correctamente por nombre 10%


o departamento
IFCD0182 — Desarrollo Web con Java

Código limpio Controlador sin lógica de negocio, nombres claros, sin 5%


código duplicado

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir a la IA que explique cómo funciona th:replace, cómo resolver un error
de validación concreto o cómo formatear una fecha en Thymeleaf.
• No permitido: pedir el controlador completo, el formulario completo o el layout
completo.
• Recomendado: cuando termines, pide a la IA que revise si Post-Redirect-Get está bien
implementado en todos los POST y si hay lógica de negocio donde no debería.
IFCD0182 — Desarrollo Web con Java

10. Resumen de la unidad

Concepto Lo esencial

DispatcherServlet El Servlet central de Spring MVC. Intercepta todas las peticiones y las dirige al
controlador correcto.

@Controller Marca la clase como controlador MVC. Sus métodos manejan peticiones y
devuelven nombres de vista.

@GetMapping / @PostMapping Mapean métodos HTTP específicos a métodos Java del controlador.

Model Objeto para pasar datos del controlador a la vista. Equivale a


[Link]() del Módulo 1.

@PathVariable Extrae variables de la URL: @GetMapping("/{id}") → @PathVariable Long id.

@RequestParam Extrae parámetros de la query string: /buscar?texto=X → @RequestParam


String texto.

Thymeleaf Motor de plantillas HTML. Usa atributos th:* procesados en el servidor. Los
archivos son HTML válido.

th:each / th:if Iteración y condicional en Thymeleaf. Equivalen a c:forEach y c:if de JSTL.

th:object / th:field Vinculan el formulario y sus campos con un objeto Java (@ModelAttribute).

@ModelAttribute Spring rellena automáticamente el objeto con los campos del formulario
enviado.

@Valid + BindingResult @Valid activa las validaciones del objeto. BindingResult captura los errores
sin lanzar excepción.

Bean Validation @NotBlank, @Email, @Size, @Min, @Max, @Positive, @Pattern —


anotaciones de validación declarativa.

Post-Redirect-Get Patrón obligatorio: tras un POST exitoso, siempre redirect. Evita


duplicaciones con F5.

FlashAttribute Mensaje que sobrevive a un redirect y luego desaparece. Perfecto para


mensajes de éxito.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
9. ¿Cuál es la diferencia entre @GetMapping y @PostMapping? ¿Cuándo usa cada uno?
10. ¿Para qué sirve el objeto Model? ¿Cuál es su equivalente en el Módulo 1?
11. ¿Qué diferencia hay entre @PathVariable y @RequestParam? Pon un ejemplo de URL
para cada uno.
12. ¿Por qué BindingResult debe ir justo después de @ModelAttribute?
13. ¿Qué ocurre si no implementas Post-Redirect-Get y el usuario pulsa F5 después de enviar
un formulario?
14. ¿Qué hace th:field="*{nombre}" exactamente? ¿Qué atributos HTML genera?
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Consolidación — entrevista técnica Spring MVC

"Actúa como entrevistador técnico para un puesto de desarrollador Java junior con Spring Boot.
Hazme 6 preguntas sobre Spring MVC y Thymeleaf: dos sobre el flujo de una petición, dos sobre
validación con @Valid, y dos sobre formularios y Thymeleaf. Después de cada respuesta
corrígeme y dime qué añadiría alguien con experiencia real."

📊 NOTA DE MERCADO LABORAL


Lo que viene en la Unidad 3:
La siguiente unidad da un salto muy importante: dejarás de devolver vistas HTML y empezarás a
devolver datos en JSON. Eso es una API REST. Con @RestController, @ResponseBody y
ResponseEntity aprenderás a construir servicios que cualquier cliente (web, móvil, otra app) pueda
consumir.

Es el tipo de desarrollo más demandado en el mercado hoy y conecta directamente con los
microservicios de la Unidad 4.
IFCD0182 — Desarrollo Web con Java

MÓDULO 2 — Spring Framework, APIs REST y Microservicios


UNIDAD 3

APIs RESTful con Spring Boot


De las vistas HTML a los datos JSON: construye APIs que cualquier cliente puede consumir

Duración estimada 6 horas (teoría + prácticas)

Módulo 2 — Spring Framework, APIs REST y Microservicios

Unidad anterior Unidad 2 — Spring MVC y Thymeleaf

Nivel de entrada @Controller, Model y Thymeleaf dominados. CRUD web funcionando.

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Construir APIs REST completas con Spring Boot: @RestController, ResponseEntity,
manejo de errores y documentación con Swagger

🔗 CONEXIÓN CON LO ANTERIOR


El salto de esta unidad:
En la Unidad 2 construiste aplicaciones web que devuelven HTML: el usuario abre una URL y ve una
página. En esta unidad construirás APIs que devuelven datos en JSON: cualquier cliente (un frontend
React, una app móvil, otro servidor) puede consumir esos datos y decidir cómo mostrarlos.

Es el mismo Spring Boot, el mismo patrón MVC. Solo cambia lo que devuelve el controlador: en lugar
de un nombre de vista, devuelve datos. Esa diferencia, que parece pequeña, cambia completamente
el tipo de sistema que puedes construir.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué es REST y sus principios fundamentales.
• Diferenciar @Controller de @RestController y saber cuándo usar cada uno.
• Construir una API REST CRUD completa con Spring Boot.
• Usar ResponseEntity para controlar los códigos de respuesta HTTP.
• Manejar errores de la API de forma centralizada con @ControllerAdvice.
• Probar una API REST con Postman o la herramienta de IA.
IFCD0182 — Desarrollo Web con Java

• Documentar una API automáticamente con Swagger/OpenAPI.


• Aplicar las convenciones REST de nombres de endpoints y códigos HTTP.
IFCD0182 — Desarrollo Web con Java

1. ¿Qué es REST y por qué domina el mercado?

1.1 Definición accesible de REST


REST (Representational State Transfer) no es una tecnología ni un protocolo. Es un conjunto de principios de
diseño para crear servicios web. Cuando decimos que una API es RESTful significa que sigue esos principios.

La idea central es sencilla: tu API expone recursos (productos, usuarios, pedidos) identificados por URLs, y los
clientes interactúan con esos recursos usando los métodos HTTP estándar que ya conoces (GET, POST, PUT,
DELETE).

📊 NOTA DE MERCADO LABORAL


REST en el mercado laboral:
Prácticamente todas las aplicaciones modernas tienen una API REST. El frontend (React, Angular, app
móvil) y el backend se comunican a través de ella. Saber construir APIs REST con Spring Boot es la
habilidad más buscada en desarrolladores Java hoy. Si dominas esta unidad, tendrás la competencia
que más aparece en las ofertas de trabajo.

1.2 Los seis principios REST

Principio Qué significa en la práctica

Sin estado (Stateless) Cada petición contiene toda la información necesaria. El servidor no recuerda
peticiones anteriores. No hay sesiones en el servidor para las APIs REST: se usa
JWT u otros tokens.

Interfaz uniforme Las URLs identifican recursos (sustantivos, no verbos). Los métodos HTTP
indican la acción. /productos GET=listar, POST=crear. /productos/42
GET=detalle, PUT=actualizar, DELETE=eliminar.

Cliente-Servidor El frontend y el backend están completamente separados. El backend expone la


API; el frontend decide cómo mostrar los datos. Pueden desplegarse y escalar
de forma independiente.

Caché Las respuestas pueden marcarse como cacheables para que el cliente no repita
peticiones innecesarias. Mejora el rendimiento.

Sistema en capas El cliente no sabe si habla directamente con el servidor final o con un
intermediario (proxy, API Gateway). El contrato de la API no cambia.

Código bajo demanda El servidor puede enviar código ejecutable al cliente (JavaScript). Opcional y
(opcional) poco usado en la práctica.
IFCD0182 — Desarrollo Web con Java

1.3 Recursos, URLs y métodos HTTP — La convención REST


La clave de una API REST bien diseñada está en nombrar los endpoints correctamente. Usa sustantivos en
plural para los recursos y métodos HTTP para las acciones:

Operación Método URL correcta URL incorrecta (estilo antiguo)

Listar todos GET /api/productos /api/getProductos

Ver uno GET /api/productos/42 /api/getProducto?id=42

Crear POST /api/productos /api/crearProducto

Actualizar todo PUT /api/productos/42 /api/actualizarProducto/42

Actualizar parte PATCH /api/productos/42 /api/patchProducto/42

Eliminar DELETE /api/productos/42 /api/eliminarProducto/42

👁 FÍJATE
Observa que las URLs correctas nunca tienen verbos (get, crear, actualizar, eliminar). Los verbos ya
los proporciona el método HTTP. La URL solo identifica el recurso. Este es el error de diseño más
común en APIs creadas por desarrolladores que no conocen REST.
IFCD0182 — Desarrollo Web con Java

2. @RestController y ResponseEntity

2.1 @RestController vs @Controller


La diferencia entre @Controller (Unidad 2) y @RestController (esta unidad) es concreta y sencilla:

Aspecto @Controller (Unidad 2) @RestController (esta unidad)

¿Qué devuelve? El nombre de una vista Thymeleaf Datos (objeto Java) que Spring convierte a
JSON

¿Para qué sirve? Aplicaciones web con HTML APIs REST que devuelven JSON/XML

Equivale a @Controller solo @Controller + @ResponseBody en todos


los métodos

¿Puede devolver HTML? Sí, es su función principal No directamente — puede pero no es su


propósito

Quién lo consume El navegador del usuario Cualquier cliente: React, app móvil, otro
backend

⚙ CÓMO FUNCIONA
¿Cómo convierte Spring el objeto Java a JSON?
Spring Boot incluye automáticamente la librería Jackson. Cuando un método de @RestController
devuelve un objeto Java, Jackson lo serializa a JSON de forma automática. No tienes que escribir
nada extra: con que tu clase tenga getters, Jackson puede convertirla a JSON.

Ejemplo: un objeto Producto con nombre="Teclado" y precio=89.99 se convierte automáticamente


a: {"nombre":"Teclado","precio":89.99}

2.2 ResponseEntity — Control total sobre la respuesta HTTP


Cuando devuelves un objeto directamente desde un método @RestController, Spring le asigna
automáticamente el código 200 OK. Pero en una API REST necesitas devolver diferentes códigos según la
situación: 201 cuando creas algo, 404 cuando no existe, 400 cuando los datos son inválidos...

ResponseEntity te da control total sobre la respuesta: puedes especificar el cuerpo, el código HTTP y las
cabeceras:

import [Link];
import [Link];

// Respuesta simple con código 200 OK (implícito)


@GetMapping("/productos")
public List<Producto> listar() {
return [Link](); // Spring devuelve 200 OK automáticamente
}
IFCD0182 — Desarrollo Web con Java
// Respuesta con código explícito usando ResponseEntity
@GetMapping("/productos/{id}")
public ResponseEntity<Producto> obtener(@PathVariable Long id) {
return [Link](id)
// Si existe: 200 OK con el producto en el cuerpo
.map(ResponseEntity::ok)
// Si no existe: 404 Not Found sin cuerpo
.orElse([Link]().build());
}

// Respuesta 201 Created tras crear un recurso


@PostMapping("/productos")
public ResponseEntity<Producto> crear(@Valid @RequestBody Producto p,
BindingResult r) {
if ([Link]()) {
// 400 Bad Request si los datos no son válidos
return [Link]().build();
}
Producto guardado = [Link](p);
// 201 Created con el recurso creado en el cuerpo
return [Link]([Link]).body(guardado);
}

// 204 No Content tras eliminar (éxito sin cuerpo de respuesta)


@DeleteMapping("/productos/{id}")
public ResponseEntity<Void> eliminar(@PathVariable Long id) {
[Link](id);
return [Link]().build();
}

2.3 Los códigos HTTP más importantes en una API REST

Código Nombre Cuándo devolverlo en una API REST

200 OK Éxito GET exitoso, PUT/PATCH exitoso

201 Created Creado POST que crea un recurso nuevo

204 No Sin contenido DELETE exitoso, PUT sin cuerpo de respuesta


Content

400 Bad Petición incorrecta Datos de entrada inválidos (validación fallida)


Request

401 No autenticado El usuario no ha iniciado sesión o el token es inválido


Unauthorized

403 Forbidden Sin permiso Autenticado pero sin permiso para esa operación

404 Not Found No encontrado El recurso con ese ID no existe

409 Conflict Conflicto El recurso ya existe (email duplicado, etc.)

422 No procesable Los datos tienen formato correcto pero son semánticamente
Unprocessable inválidos

500 Internal Error del servidor Error inesperado en el servidor (nunca intencionado)
Error
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Aprende los códigos HTTP con IA
Los códigos HTTP son fáciles de confundir al principio. Prueba este prompt:

"Soy desarrollador junior aprendiendo APIs REST con Spring Boot. Tengo estos 5 escenarios: 1) El
usuario pide un producto que no existe, 2) El usuario envía un formulario con el precio en
negativo, 3) El usuario crea un producto correctamente, 4) El usuario intenta acceder sin token de
autenticación, 5) El servidor tiene un bug y lanza una NullPointerException. ¿Qué código HTTP
debería devolver mi API en cada caso y por qué?"

3. API REST CRUD completa con Spring Boot

Vamos a construir una API REST completa para gestionar productos. Esta es la estructura que seguiremos en
todo proyecto REST profesional:
3.1 El DTO — Separar la API del Modelo interno
En APIs REST profesionales no se expone directamente el objeto de base de datos al cliente. Se usa un DTO
(Data Transfer Object): una clase que define exactamente qué campos se envían o reciben en la API,
independientemente de cómo estén en la base de datos.

👁 FÍJATE
¿Por qué usar DTOs?
• Seguridad: puedes excluir campos sensibles (contraseña, tokens) de la respuesta.
• Control: decides exactamente qué recibe el cliente, sin exponer la estructura interna.
• Flexibilidad: puedes cambiar la base de datos sin cambiar el contrato de la API.
• Validación separada: las reglas del DTO de entrada pueden diferir del modelo interno.

// DTO para CREAR un producto (lo que recibe la API)


public class ProductoCreateDTO {
@NotBlank(message = "El nombre es obligatorio")
private String nombre;
@DecimalMin(value = "0.01", message = "El precio debe ser mayor que cero")
private Double precio;
@Min(value = 0, message = "El stock no puede ser negativo")
private Integer stock;
// Constructor, getters y setters...
}

// DTO para RESPONDER (lo que devuelve la API)


public class ProductoResponseDTO {
// El cliente ve el id pero no puede modificarlo en el create
private Long id;
IFCD0182 — Desarrollo Web con Java
private String nombre;
private Double precio;
private Integer stock;
// Añadimos un campo calculado que no está en la BD
private String disponibilidad; // "En stock" o "Agotado"
// Constructor, getters y setters...
}

3.2 El controlador REST completo

package [Link];

import [Link].*;
import [Link];
import [Link];
import [Link].*;
import [Link].*;
import [Link];

// @RestController = @Controller + @ResponseBody en todos los métodos


// @RequestMapping: prefijo común de todas las URLs de este controlador
@RestController
@RequestMapping("/api/productos")
public class ProductoApiController {

private final ProductoService service;


public ProductoApiController(ProductoService service) {
[Link] = service;
}

// ─── GET /api/productos ─── Listar todos ───────────────────────


@GetMapping
public ResponseEntity<List<ProductoResponseDTO>> listar() {
return [Link]([Link]());
}

// ─── GET /api/productos/{id} ─── Ver uno ────────────────────────


@GetMapping("/{id}")
public ResponseEntity<ProductoResponseDTO> obtener(@PathVariable Long id) {
return [Link](id)
.map(ResponseEntity::ok)
.orElse([Link]().build());
}

// ─── POST /api/productos ─── Crear ──────────────────────────────


// @RequestBody: lee el JSON del cuerpo de la petición y lo convierte al DTO
@PostMapping
public ResponseEntity<ProductoResponseDTO> crear(
@Valid @RequestBody ProductoCreateDTO dto) {
ProductoResponseDTO creado = [Link](dto);
return [Link]([Link]).body(creado);
}

// ─── PUT /api/productos/{id} ─── Actualizar completo ────────────


IFCD0182 — Desarrollo Web con Java
@PutMapping("/{id}")
public ResponseEntity<ProductoResponseDTO> actualizar(
@PathVariable Long id,
@Valid @RequestBody ProductoCreateDTO dto) {
return [Link](id, dto)
.map(ResponseEntity::ok)
.orElse([Link]().build());
}

// ─── DELETE /api/productos/{id} ─── Eliminar ─────────────────────


@DeleteMapping("/{id}")
public ResponseEntity<Void> eliminar(@PathVariable Long id) {
if (![Link](id)) {
return [Link]().build();
}
[Link](id);
return [Link]().build();
}
}

3.3 @RequestBody — Leer JSON de la petición


La anotación @RequestBody es el equivalente REST de @ModelAttribute del Módulo 2. Lee el cuerpo JSON
de la petición y lo convierte automáticamente al objeto Java correspondiente:

Aspecto @ModelAttribute (formulario HTML) @RequestBody (API REST)

Formato de entrada application/x-www-form-urlencoded application/json (cuerpo JSON)


(formulario HTML)

Origen de los datos Campos del formulario HTML Cuerpo de la petición HTTP en formato
JSON

Quién lo envía El navegador al hacer submit de un Un cliente REST (Postman, React, app
<form> móvil)

Conversión Spring convierte strings del form a Jackson convierte JSON a objeto Java
tipos Java automáticamente
IFCD0182 — Desarrollo Web con Java

4. Manejo de errores en APIs REST

4.1 El problema de los errores sin gestionar


Si no gestionas los errores, cuando algo falla en tu API el cliente recibe una respuesta con código 500 y un
JSON generado por Spring con información técnica interna que el cliente no puede procesar de forma limpia.
Necesitas un formato de error consistente y controlado.
4.2 Clase de respuesta de error personalizada
// Esta clase define el formato JSON de todos los errores de la API
public class ApiError {
private int status;
private String mensaje;
private List<String> errores;
private LocalDateTime timestamp;

public ApiError(int status, String mensaje, List<String> errores) {


[Link] = status;
[Link] = mensaje;
[Link] = errores;
[Link] = [Link]();
}
// Getters...
}

4.3 @ControllerAdvice — Gestión centralizada de errores


En lugar de manejar errores en cada controlador, Spring ofrece @ControllerAdvice: una clase que intercepta
las excepciones de toda la aplicación y las convierte en respuestas HTTP controladas:

import [Link].*;
import [Link].*;
import [Link];

// @ControllerAdvice: intercepta excepciones de todos los controladores


@ControllerAdvice
public class GlobalExceptionHandler {

// Captura errores de validación (@Valid falló)


@ExceptionHandler([Link])
public ResponseEntity<ApiError> handleValidationErrors(
MethodArgumentNotValidException ex) {

// Extraemos los mensajes de error de cada campo


List<String> errores = [Link]().getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect([Link]());

ApiError error = new ApiError(400, "Datos de entrada inválidos", errores);


return [Link]().body(error);
IFCD0182 — Desarrollo Web con Java
}

// Captura cuando un recurso no se encuentra (excepción personalizada)


@ExceptionHandler([Link])
public ResponseEntity<ApiError> handleNotFound(
RecursoNoEncontradoException ex) {
ApiError error = new ApiError(404, [Link](), [Link]());
return [Link](HttpStatus.NOT_FOUND).body(error);
}

// Captura cualquier excepción no contemplada (safety net)


@ExceptionHandler([Link])
public ResponseEntity<ApiError> handleGeneral(Exception ex) {
// Registramos el error técnico pero no lo mostramos al cliente
[Link]("Error inesperado: " + [Link]());
ApiError error = new ApiError(500,
"Ha ocurrido un error interno. Por favor, inténtelo de nuevo.",
[Link]());
return [Link]().body(error);
}
}

✔ BUENAS PRÁCTICAS
Con esta clase el cliente siempre recibe un JSON de error consistente:
{
"status": 404,
"mensaje": "Producto con id 99 no encontrado",
"errores": [],
"timestamp": "2025-04-28T10:30:00"
}

Este formato es predecible. El frontend puede leerlo y mostrar mensajes de error apropiados al
usuario sin tener que interpretar mensajes técnicos de Java.
IFCD0182 — Desarrollo Web con Java

5. Probar la API con Postman

Las APIs REST no se prueban desde el navegador directamente (salvo los GET simples). Necesitas una
herramienta que te permita enviar peticiones con cualquier método HTTP, cabeceras y cuerpo JSON.
Postman es la más usada en el mercado.

5.1 Instalar y configurar Postman


1. Ve a [Link]/downloads y descarga la versión gratuita para tu sistema operativo.
2. Instala y crea una cuenta gratuita (puedes saltar el paso de cuenta).
3. Crea una nueva colección llamada «API Productos IFCD0182» para organizar tus peticiones.

5.2 Probar cada endpoint

1 GET — Listar todos los productos


Método: GET. URL: [Link] Haz clic en Send. Deberías recibir un
JSON con la lista (o un array vacío [] si no hay datos). Código esperado: 200 OK.

2 POST — Crear un producto


Método: POST. URL: [Link]

En la pestaña Body → raw → JSON, escribe:

{
"nombre": "Teclado mecánico",
"precio": 89.99,
"stock": 15
}
Código esperado: 201 Created. El cuerpo de la respuesta debe incluir el producto con su id
asignado.

3 GET — Obtener un producto por ID


Método: GET. URL: [Link] Código esperado: 200 OK con el
producto. Prueba con un ID que no existe (ej. /api/productos/999). Código esperado: 404 Not
Found.

4 PUT — Actualizar un producto


Método: PUT. URL: [Link] Body JSON con los nuevos datos.
Código esperado: 200 OK con el producto actualizado.
IFCD0182 — Desarrollo Web con Java

5 DELETE — Eliminar un producto


Método: DELETE. URL: [Link] Código esperado: 204 No
Content (sin cuerpo). Haz GET de nuevo al mismo ID: debe devolver 404.

6 POST con datos inválidos — Probar la validación


Envía un POST con nombre vacío o precio negativo. Código esperado: 400 Bad Request con el
JSON de error que definiste en GlobalExceptionHandler.

🤖 CON AYUDA DE LA IA
Prueba tu API directamente con IA si no tienes Postman instalado
La IA puede ayudarte a construir las peticiones curl equivalentes para probar desde la terminal:

"Tengo una API REST Spring Boot en localhost:8080/api/productos. Genera los comandos curl
para: 1) listar todos, 2) crear un producto con nombre 'Monitor', precio 349.00 y stock 5, 3)
obtener el producto con id 1, 4) actualizarlo cambiando el precio a 299.00, 5) eliminarlo. Explica
cada opción del comando curl."
IFCD0182 — Desarrollo Web con Java

6. Documentación automática con Swagger / OpenAPI

6.1 ¿Por qué documentar la API?


Una API sin documentación es una API inutilizable para quien no la escribió. Los desarrolladores frontend, los
equipos de QA y los consumidores externos necesitan saber qué endpoints existen, qué parámetros aceptan,
qué devuelven y qué errores pueden ocurrir.

Swagger (también conocido como OpenAPI) es el estándar de la industria para documentar APIs REST. La
librería Springdoc genera automáticamente esa documentación a partir de tu código con solo añadir una
dependencia.
6.2 Añadir Springdoc al proyecto
<dependency>
<groupId>[Link]</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>

Con solo añadir esta dependencia, Spring Boot genera automáticamente:

• Una interfaz web interactiva en [Link]


• Un archivo JSON con la especificación en [Link]

6.3 Enriquecer la documentación con anotaciones


Por defecto Swagger genera documentación básica. Puedes mejorarla con anotaciones:

import [Link].*;
import [Link].*;

@Operation(summary = "Obtener un producto por ID",


description = "Devuelve el producto con el ID especificado o 404 si no
existe")
@ApiResponse(responseCode = "200", description = "Producto encontrado")
@ApiResponse(responseCode = "404", description = "Producto no encontrado")
@GetMapping("/{id}")
public ResponseEntity<ProductoResponseDTO> obtener(@PathVariable Long id) {
// ...
}

💡 CONSEJO
Swagger es uno de los requisitos más habituales en proyectos reales. En muchos equipos, el
frontend y el backend acuerdan el contrato de la API a través del archivo Swagger antes de empezar
a programar. Conocer Swagger y saber documentar una API es una habilidad muy valorada en
entrevistas.
IFCD0182 — Desarrollo Web con Java
IFCD0182 — Desarrollo Web con Java

7. CORS — Permitir peticiones desde el frontend

7.1 ¿Qué es CORS y por qué aparece?


Cuando un frontend React en localhost:3000 intenta llamar a tu API Spring Boot en localhost:8080, el
navegador bloquea la petición por seguridad. Esto se llama la política de Same-Origin: un navegador solo
permite peticiones entre el mismo dominio y puerto por defecto.

CORS (Cross-Origin Resource Sharing) es el mecanismo que permite al servidor indicar al navegador qué
dominios tienen permiso para hacer peticiones. Sin configurarlo, el frontend verá un error «CORS policy» y
no podrá llamar a tu API.
7.2 Configurar CORS en Spring Boot

Opción 1 — Anotación @CrossOrigin en el controlador (rápida, para desarrollo)


@RestController
@RequestMapping("/api/productos")
// Permite peticiones desde el frontend de desarrollo
@CrossOrigin(origins = "[Link]
public class ProductoApiController {
// ...
}

Opción 2 — Configuración global (recomendada para producción)


import [Link];
import [Link].*;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
[Link]("/api/**")
// Dominios permitidos — en producción, pon tu dominio real
.allowedOrigins("[Link] "[Link]
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
// true si necesitas enviar cookies o cabeceras de autenticación
.allowCredentials(true)
.maxAge(3600);
}
}

ℹ NOTA
En el Módulo 4 (Integración con Frontend) verás cómo un proyecto React consume esta API y la
importancia de tener CORS correctamente configurado. Por ahora lo importante es saber que si el
frontend no puede llamar a tu API, CORS es el primer lugar donde mirar.
IFCD0182 — Desarrollo Web con Java

8. Errores frecuentes en APIs REST con Spring Boot

Error / Síntoma Causa habitual Solución

El cliente recibe código 200 El método devuelve null o una Usa ResponseEntity para controlar el
aunque hay un error colección vacía sin usar código. Devuelve 404 cuando no
ResponseEntity encuentras el recurso.

Error 415 Unsupported Media La petición no incluye la cabecera En Postman: Body → raw → selecciona
Type al hacer POST Content-Type: application/json JSON. Curl: añade -H 'Content-Type:
application/json'

@Valid no lanza errores — el Falta la dependencia spring-boot- Añade la dependencia al [Link]. Para
método recibe el objeto con starter-validation o no se usa APIs REST, si usas @ControllerAdvice
datos null BindingResult no necesitas BindingResult
manualmente.

CORS policy error en el frontend El frontend intenta llamar a la API Añade @CrossOrigin al controlador
desde un origen diferente sin para desarrollo o configura CorsConfig
configurar CORS globalmente.

El JSON de respuesta tiene Jackson serializa todos los campos Añade


campos en null incluyendo los null @JsonIgnoreProperties(ignoreUnknow
n=true) o configura en
[Link]:
[Link]-property-
inclusion=non_null

Error 405 Method Not Allowed Se está llamando con el método Verifica el método HTTP en Postman y
HTTP incorrecto (ej. GET a un compáralo con la anotación del
endpoint POST) controlador (@PostMapping,
@GetMapping...)

StackOverflowError al serializar Jackson entra en un bucle infinito Usa @JsonManagedReference y


objetos con relaciones serializando A→B→A→B... @JsonBackReference, o mejor: usa
bidireccionales DTOs para evitar exponer las relaciones
directamente.
IFCD0182 — Desarrollo Web con Java

9. Buenas prácticas en APIs REST

✔ BUENAS PRÁCTICAS
Aplica estas prácticas desde el primer endpoint:
• Versiona tu API desde el principio: Usa /api/v1/productos. Cuando necesites romper la
compatibilidad, crea /api/v2/productos. Los clientes existentes siguen usando v1 sin
romperse.
• Usa sustantivos en plural para los recursos: /api/productos, /api/usuarios, /api/pedidos.
Nunca verbos: /api/getProductos, /api/crearUsuario.
• Devuelve siempre el código HTTP correcto: 200, 201, 204, 400, 404, 409... Nunca
devuelvas 200 para indicar un error.
• Usa DTOs, no entidades directamente: Nunca expongas la entidad JPA al cliente. Usa
DTOs para controlar qué entra y qué sale.
• Centraliza el manejo de errores: Un único @ControllerAdvice para toda la API. Un
formato de error consistente.
• Documenta con Swagger: Al menos los parámetros, el cuerpo esperado y los posibles
códigos de respuesta de cada endpoint.
• CORS solo para los orígenes necesarios: No uses allowedOrigins("*") en producción.
Lista explícitamente los dominios permitidos.
• Paginación para listas grandes: No devuelvas 10.000 registros en un GET. Spring Data
ofrece Pageable para paginar resultados automáticamente.
IFCD0182 — Desarrollo Web con Java

10. Actividad práctica guiada

Título API REST de libros con manejo de errores y Swagger

Duración 60 minutos

Objetivo Construir una API REST CRUD completa con DTOs, @ControllerAdvice y documentación
Swagger

Nivel Guiado — el formador explica cada bloque antes de implementarlo

Enunciado
Construye una API REST para una biblioteca digital. La entidad Libro tiene: título, autor, isbn (único), año de
publicación y disponible (boolean). Datos en memoria.

1 Crea los DTOs: LibroCreateDTO y LibroResponseDTO


CreateDTO: titulo (@NotBlank), autor (@NotBlank), isbn (@Pattern de 10 o 13 dígitos), anio
(@Min 1450, @Max año actual). ResponseDTO añade el campo id y un campo estado
('Disponible' o 'Prestado').

2 Crea el LibroService y LibroRepository con datos de prueba


El repositorio tiene una lista con 3 libros precargados. El servicio expone findAll(), findById(),
save(), update() y deleteById(). Si el ISBN ya existe al crear, lanza una excepción personalizada
IsbnDuplicadoException.

3 Crea el LibroApiController con los 5 endpoints CRUD


Todos los endpoints devuelven ResponseEntity con el código correcto. Usa /api/v1/libros
como prefijo base.

4 Crea el GlobalExceptionHandler
Captura: MethodArgumentNotValidException (400), IsbnDuplicadoException (409) y Exception
genérica (500). Todos devuelven un ApiError en JSON.

5 Añade Springdoc y verifica la documentación


Añade la dependencia al [Link]. Abre [Link] Prueba cada
endpoint directamente desde la interfaz Swagger.
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Usa la IA para revisar el diseño de tu API

"Revisa el diseño de mi API REST de libros. Las URLs son: GET /api/v1/libros, GET
/api/v1/libros/{id}, POST /api/v1/libros, PUT /api/v1/libros/{id}, DELETE /api/v1/libros/{id}. Los
códigos de respuesta son: 200, 200, 201, 200, 204. ¿Sigo las convenciones REST correctamente?
¿Hay algo que mejorar en el diseño?"

11. Actividad práctica autónoma

Título API REST de gestión de empleados con búsqueda y paginación básica

Duración 90 minutos

Objetivo Desarrollar autónomamente una API REST completa con DTOs, errores centralizados,
Swagger y endpoints adicionales de búsqueda

Nivel Autónomo — documentación Spring Boot, Springdoc e IA disponibles

Entrega Proyecto en .zip con colección Postman exportada (.json) con todos los endpoints
probados

Enunciado
Construye una API REST /api/v1/empleados para gestionar una plantilla de empleados. La entidad Empleado
tiene: nombre, apellidos, email (único), departamento, salario y fechaAlta. Datos en memoria.

Endpoints requeridos
• CRUD estándar: GET /empleados, GET /empleados/{id}, POST /empleados, PUT /empleados/{id},
DELETE /empleados/{id} con los códigos HTTP correctos.
• Búsqueda: GET /empleados/buscar?departamento=IT&salarioMin=30000 — filtra por departamento
y/o salario mínimo. Ambos parámetros son opcionales.
• Estadísticas: GET /empleados/stats — devuelve un JSON con: total de empleados, salario medio,
departamento con más empleados y empleado más antiguo.
• Paginación básica: GET /empleados?page=0&size=5 — devuelve solo 5 empleados por página.
Implementa la lógica manualmente con [Link]().

Requisitos técnicos obligatorios


• DTOs separados para creación y respuesta. El DTO de respuesta no incluye el campo email completo
— muestra solo los primeros 3 caracteres + *** (privacidad).
• GlobalExceptionHandler con al menos 3 tipos de excepción manejados.
• Email único validado en el servicio. Si ya existe, respuesta 409 Conflict.
IFCD0182 — Desarrollo Web con Java

• Swagger funcional con descripciones en todos los endpoints.


• CORS configurado para [Link]

MÓDULO 2 — Spring Framework, APIs REST y Microservicios


UNIDAD 4 — CIERRE DEL MÓDULO 2

Microservicios con Spring Boot y Spring Cloud


Arquitectura distribuida, Eureka, Feign Client, API Gateway y sostenibilidad en el despliegue

Duración estimada 6 horas (teoría + prácticas + cierre del módulo)

Módulo 2 — Spring Framework, APIs REST y Microservicios (unidad final)

Unidad anterior Unidad 3 — APIs RESTful con Spring Boot

Nivel de entrada API REST CRUD completa funcionando con ResponseEntity y manejo de errores

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Qué son los microservicios, cuándo usarlos y cómo construir una arquitectura
básica con Spring Cloud

📊 NOTA DE MERCADO LABORAL


Nivel de exigencia de esta unidad — sé honesto contigo mismo:
Los microservicios son un tema avanzado. En el mercado, los perfiles junior raramente diseñan
arquitecturas de microservicios desde cero, pero sí trabajan en entornos donde ya existen. Esta
unidad te da la base conceptual sólida y la capacidad de implementar un sistema básico.

El objetivo no es que salgas de aquí siendo arquitecto de microservicios. Es que entiendas qué son,
por qué existen, cómo se comunican y que puedas construir uno funcional. Eso ya te diferencia de
muchos candidatos junior.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué son los microservicios y en qué se diferencian de una arquitectura
monolítica.
• Describir los componentes de Spring Cloud y para qué sirve cada uno.
• Crear un Eureka Server y registrar microservicios en él.
• Implementar comunicación entre microservicios con Feign Client.
• Configurar un API Gateway con Spring Cloud Gateway.
IFCD0182 — Desarrollo Web con Java

• Explicar el patrón Circuit Breaker y por qué es necesario en arquitecturas distribuidas.


• Aplicar consideraciones de sostenibilidad energética en el despliegue de microservicios.
IFCD0182 — Desarrollo Web con Java

1. ¿Qué son los microservicios?

1.1 El monolito — punto de partida


Todo lo que has construido hasta ahora en el curso es un monolito: una única aplicación que contiene toda
la lógica (usuarios, productos, pedidos, seguridad...) desplegada en un único servidor. El monolito es la
forma natural de empezar y funciona perfectamente para muchos proyectos.

⚙ CÓMO FUNCIONA
Problemas que aparecen cuando el monolito crece:
• Despliegue: si cambias una línea del módulo de facturación, tienes que redesplegar toda
la aplicación.
• Escalado: si el módulo de búsqueda necesita más potencia, tienes que escalar todo el
servidor, aunque lo demás vaya bien.
• Equipos: dos equipos tocando el mismo código base tienen conflictos constantes.
• Tecnología: toda la aplicación usa el mismo lenguaje y versión de Java, aunque algunas
partes se beneficiarían de otra tecnología.
• Fallo en cascada: un error en el módulo de informes puede tumbar toda la aplicación.

1.2 La solución: dividir en servicios independientes


Los microservicios dividen la aplicación en servicios pequeños e independientes, cada uno con su propia
lógica, su propia base de datos y su propio ciclo de despliegue. Se comunican entre sí a través de APIs REST o
mensajería.

Aspecto Monolito Microservicios

Despliegue Un único artefacto. Redespliegue Cada servicio se despliega de forma


completo en cada cambio. independiente.

Escalado Escala todo o nada. Escala solo el servicio que lo necesita.

Tecnología Un único lenguaje y framework para Cada servicio puede usar la tecnología más
todo. adecuada.

Equipos Todos trabajan en el mismo código — Cada equipo es propietario de su servicio.


conflictos frecuentes.

Fallo Un error puede afectar a toda la Un fallo en un servicio no necesariamente


aplicación. tumba los demás.

Complejidad Simple al principio, complicada cuando Más compleja desde el principio, más
crece. manejable cuando crece.
IFCD0182 — Desarrollo Web con Java

Cuándo usar Proyectos pequeños/medianos, equipos Sistemas grandes, múltiples equipos, alta
pequeños, inicio de producto. demanda.

⚠ ATENCIÓN
El error más común con microservicios:
Los microservicios no son la solución a todos los problemas. Una startup con un equipo de 3
personas que divide su aplicación en 15 microservicios tiene un problema de sobreingeniería, no una
mejora. La regla práctica: empieza con un monolito bien estructurado. Divide cuando el problema de
escala o equipo sea real, no anticipado.

2. Spring Cloud — El ecosistema para microservicios

Spring Cloud es el conjunto de herramientas de Spring para construir y gestionar arquitecturas de


microservicios. Resuelve los problemas específicos que aparecen cuando tienes múltiples servicios
comunicándose entre sí:

Componente Problema que resuelve Tecnología Spring Cloud

Registro y ¿Dónde está el servicio X? ¿En qué IP y Eureka Server + Eureka Client
descubrimiento puerto está corriendo?

Comunicación entre ¿Cómo llama el Servicio A a la API del Feign Client (declarativo) o
servicios Servicio B de forma sencilla? RestTemplate/WebClient

Balanceo de carga Si hay 3 instancias del Servicio B, ¿a cuál Spring Cloud LoadBalancer (integrado
de las 3 mando la petición? con Feign)

API Gateway Los clientes externos no deben conocer Spring Cloud Gateway
la URL de cada microservicio.

Configuración Cada servicio tiene su Spring Cloud Config Server


centralizada [Link] — ¿cómo
gestionarlo todo?

Tolerancia a fallos Si el Servicio B falla, ¿cómo evito que el Resilience4j (Circuit Breaker)
Servicio A también caiga?

Trazabilidad distribuida Una petición pasa por 5 servicios. Micrometer + Zipkin


¿Cómo sigo su rastro si algo falla?

👁 FÍJATE
No todos los proyectos con microservicios usan todos estos componentes. En este módulo verás los
fundamentales: Eureka (registro), Feign Client (comunicación) y API Gateway (punto de entrada). El
resto los verás mencionados en contexto pero sin profundizar, ya que su uso completo es propio de
niveles más avanzados.
IFCD0182 — Desarrollo Web con Java

3. Eureka — Registro y descubrimiento de servicios

3.1 ¿Para qué sirve Eureka?


Imagina que tienes un servicio de pedidos que necesita llamar al servicio de usuarios. El servicio de usuarios
podría estar en cualquier servidor, en cualquier puerto, y podrían existir múltiples instancias de él. ¿Cómo
sabe el servicio de pedidos dónde llamar?

Eureka Server actúa como un directorio telefónico: cada microservicio al arrancar se registra en Eureka
indicando su nombre, IP y puerto. Cuando un servicio necesita llamar a otro, consulta a Eureka por el
nombre del servicio y obtiene la dirección actual. Si hay varias instancias, Eureka devuelve una
aleatoriamente (balanceo de carga básico).

3.2 Crear el Eureka Server

Dependencia en [Link]
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

Clase principal
@SpringBootApplication
// @EnableEurekaServer: activa el servidor de registro
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
[Link]([Link], args);
}
}

[Link] del servidor Eureka


[Link]=eureka-server
[Link]=8761
# El servidor no se registra en sí mismo
[Link]-with-eureka=false
[Link]-registry=false

Con esto arranca el servidor en [Link] Puedes ver el panel de Eureka en el navegador:
muestra todos los servicios registrados, su estado y sus instancias.
IFCD0182 — Desarrollo Web con Java

3.3 Registrar un microservicio en Eureka

Dependencia en cada microservicio


<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

[Link] de cada microservicio


[Link]=producto-service
[Link]=8081
# URL del servidor Eureka donde registrarse
[Link]=[Link]
# Nombre con el que aparece en el panel de Eureka
[Link]-id=${[Link]}:${[Link]}

✔ BUENAS PRÁCTICAS
Con solo añadir la dependencia y las propiedades, el microservicio se registra automáticamente en
Eureka al arrancar. No necesitas código adicional. En el panel de Eureka verás el servicio apareciendo
unos segundos después de arrancarlo.
IFCD0182 — Desarrollo Web con Java

4. Feign Client — Comunicación declarativa entre servicios

4.1 El problema de llamar a otro servicio


Cuando el servicio de pedidos necesita obtener datos del servicio de usuarios, tiene que hacer una petición
HTTP. Sin herramientas especiales, esto implica usar RestTemplate o WebClient, gestionar las URLs
manualmente y manejar errores de red de forma explícita. Es código repetitivo y frágil.

4.2 Feign Client — Llamadas HTTP declarativas


Feign Client simplifica enormemente la comunicación entre servicios: defines una interfaz Java que describe
los endpoints del servicio destino, y Spring genera automáticamente el cliente HTTP. El nombre del servicio
lo resuelve consultando Eureka.

Dependencia
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Activar Feign en la clase principal del microservicio que llama


@SpringBootApplication
// @EnableFeignClients: activa el soporte de Feign en esta aplicación
@EnableFeignClients
public class PedidoServiceApplication { ... }

Definir el cliente Feign — interfaz Java


import [Link];
import [Link].*;

// name: nombre del servicio tal como está registrado en Eureka


// Feign resuelve automáticamente la URL real consultando Eureka
@FeignClient(name = "usuario-service")
public interface UsuarioClient {

// Esta anotación funciona igual que en un @RestController


// pero aquí describe el endpoint del SERVICIO REMOTO
@GetMapping("/api/v1/usuarios/{id}")
UsuarioDTO obtenerUsuario(@PathVariable Long id);

@GetMapping("/api/v1/usuarios")
List<UsuarioDTO> listarUsuarios();
}
IFCD0182 — Desarrollo Web con Java

Usar el cliente Feign desde el servicio de pedidos


@Service
public class PedidoService {

private final PedidoRepository repo;


// Spring inyecta automáticamente el cliente Feign generado
private final UsuarioClient usuarioClient;

public PedidoService(PedidoRepository repo, UsuarioClient usuarioClient) {


[Link] = repo;
[Link] = usuarioClient;
}

public PedidoDetalleDTO crearPedido(Long usuarioId, PedidoCreateDTO dto) {


// Llamamos al servicio de usuarios como si fuera un método local
// Feign se encarga de la petición HTTP a usuario-service
UsuarioDTO usuario = [Link](usuarioId);

Pedido pedido = new Pedido([Link](), [Link]());


Pedido guardado = [Link](pedido);

return new PedidoDetalleDTO(guardado, usuario);


}
}

🔗 CONEXIÓN CON LO ANTERIOR


La elegancia de Feign:
Sin Feign necesitarías construir una URL, hacer una petición HTTP, deserializar el JSON, manejar
excepciones de red... Con Feign simplemente llamas a [Link](id) como si
fuera un método local. Feign gestiona todo lo demás: resolución de URL via Eureka, serialización,
balanceo de carga y reintentos.

🤖 CON AYUDA DE LA IA
Entiende el flujo con IA
Para visualizar cómo funciona la comunicación entre servicios:

"Tengo un microservicio pedido-service que necesita llamar a usuario-service usando Feign Client
y Eureka. Traza exactamente qué ocurre cuando el servicio de pedidos llama a
[Link](42): cómo Feign sabe la URL de usuario-service, cómo interviene
Eureka, qué petición HTTP se genera y cómo llega la respuesta de vuelta. Explícamelo paso a
paso."
IFCD0182 — Desarrollo Web con Java

5. API Gateway — El punto de entrada único

5.1 ¿Por qué un API Gateway?


Sin un API Gateway, el cliente externo (frontend, app móvil) necesitaría conocer la URL de cada
microservicio: [Link] para productos, [Link] para usuarios,
[Link] para pedidos... Cada vez que un servicio cambia de puerto o servidor, el cliente
también tiene que actualizar su configuración.

El API Gateway es el único punto de entrada para todos los clientes externos. Recibe todas las peticiones y
las redirige al microservicio correcto. Para el cliente externo, solo existe una URL.

Sin API Gateway Con API Gateway

Cliente conoce múltiples URLs Cliente solo conoce la URL del Gateway

Cliente gestiona el balanceo de carga Gateway gestiona el balanceo

Autenticación implementada en cada servicio Autenticación centralizada en el Gateway

Si un servicio cambia de URL, el cliente falla El Gateway absorbe los cambios internos

Sin punto central para logs y métricas Logs y métricas de todas las peticiones en un lugar

5.2 Spring Cloud Gateway — Configuración básica

Dependencia
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

[Link] del API Gateway


spring:
application:
name: api-gateway
cloud:
gateway:
routes:
# Ruta para el servicio de productos
- id: producto-service
# lb:// indica que usa Eureka para resolver la URL
uri: lb://producto-service
predicates:
# Todas las peticiones a /api/productos/** van a este servicio
- Path=/api/productos/**
IFCD0182 — Desarrollo Web con Java

# Ruta para el servicio de usuarios


- id: usuario-service
uri: lb://usuario-service
predicates:
- Path=/api/usuarios/**

# Ruta para el servicio de pedidos


- id: pedido-service
uri: lb://pedido-service
predicates:
- Path=/api/pedidos/**

server:
port: 8080

eureka:
client:
service-url:
defaultZone: [Link]

💡 CONSEJO
Con esta configuración, el cliente externo solo habla con [Link] El Gateway decide a
qué microservicio redirigir cada petición basándose en la URL. Los microservicios internos pueden
estar en cualquier puerto — el cliente externo no necesita saberlo.
IFCD0182 — Desarrollo Web con Java

6. Circuit Breaker — Tolerancia a fallos

6.1 El problema del fallo en cascada


Imagina que el servicio de usuarios está caído. Cada vez que el servicio de pedidos intenta llamarle, la
petición espera hasta que agota el timeout. Si llegan 100 peticiones por segundo, 100 hilos quedan
bloqueados esperando. El servicio de pedidos se queda sin recursos y también se cae. El problema se
propaga en cascada.
6.2 El patrón Circuit Breaker
El Circuit Breaker (interruptor automático) funciona exactamente como el de tu hogar eléctrico. Cuando
detecta que un servicio falla repetidamente, «abre el circuito»: deja de intentar llamar al servicio caído y
devuelve una respuesta alternativa (fallback) inmediatamente. Después de un tiempo, prueba de nuevo. Si el
servicio se recuperó, cierra el circuito.

Estado del Circuit Breaker Qué ocurre Cuándo ocurre

CLOSED (Cerrado) Las peticiones pasan normalmente al servicio Estado normal — el servicio
funciona bien

OPEN (Abierto) Las peticiones NO llegan al servicio — Cuando el % de errores supera el


respuesta fallback inmediata umbral configurado

HALF-OPEN (Medio) Deja pasar algunas peticiones de prueba Tras un tiempo de espera en
OPEN — comprueba si el servicio
se recuperó

6.3 Resilience4j con Feign — Fallback básico

// FallbackFactory: implementa el comportamiento alternativo cuando el servicio falla


@Component
public class UsuarioClientFallback implements FallbackFactory<UsuarioClient> {

@Override
public UsuarioClient create(Throwable cause) {
return new UsuarioClient() {

@Override
public UsuarioDTO obtenerUsuario(Long id) {
// El servicio de usuarios no responde
// Devolvemos un usuario genérico para no bloquear la respuesta
return new UsuarioDTO(id, "Usuario no disponible", "");
}

@Override
public List<UsuarioDTO> listarUsuarios() {
// Lista vacía como fallback
return [Link]();
}
};
IFCD0182 — Desarrollo Web con Java
}
}

// En el FeignClient, indicamos el fallback:


@FeignClient(name = "usuario-service", fallbackFactory = [Link])
public interface UsuarioClient { ... }

ℹ NOTA
El Circuit Breaker completo con Resilience4j incluye configuración de umbrales, tiempos de espera y
métricas. Para este módulo es suficiente entender el concepto y saber implementar el fallback
básico. En proyectos reales la configuración se afina con la experiencia en producción.
IFCD0182 — Desarrollo Web con Java

7. Arquitectura completa y sostenibilidad

7.1 El sistema completo — Todos los componentes


Un sistema de microservicios con Spring Cloud tiene esta arquitectura cuando está completo:

Componente Puerto Función en el sistema

Eureka Server 8761 Directorio de servicios. Todos los demás se registran aquí.

API Gateway 8080 Punto de entrada único para clientes externos. Enruta
peticiones.

Config Server 8888 Configuración centralizada para todos los microservicios.

usuario-service 8081 Gestiona usuarios. Registrado en Eureka.

producto-service 8082 Gestiona productos. Registrado en Eureka.

pedido-service 8083 Gestiona pedidos. Llama a usuario y producto vía Feign.

Zipkin (trazabilidad) 9411 Recoge y visualiza trazas de peticiones entre servicios.

⚙ CÓMO FUNCIONA
Orden de arranque — MUY IMPORTANTE:
1. Primero arranca el Config Server (si lo tienes) — los demás lo consultan al arrancar.
2. Segundo arranca el Eureka Server — los demás se registran en él.
3. Después arranca los microservicios de negocio (usuario, producto, pedido...).
4. Por último arranca el API Gateway — necesita que Eureka ya tenga los servicios
registrados.
Si arrancas en otro orden, los servicios pueden fallar al intentar registrarse o al resolver rutas.
IFCD0182 — Desarrollo Web con Java

7.2 Sostenibilidad ambiental en el despliegue de microservicios


El programa formativo del curso incluye explícitamente la consideración medioambiental en el despliegue de
microservicios. Este es un tema real en la industria: el software tiene un impacto energético significativo.

✔ BUENAS PRÁCTICAS
Prácticas de sostenibilidad en arquitecturas de microservicios:
• Escala a cero cuando no se usa: Las plataformas cloud modernas (Kubernetes con KEDA,
AWS Lambda) permiten que los servicios consuman cero recursos cuando no hay tráfico.
• Dimensiona correctamente cada servicio: Un microservicio que gestiona 10
peticiones/día no necesita 4 CPUs. Asigna los recursos según la carga real, no la máxima
teórica.
• Caché eficiente: Evitar peticiones repetidas entre servicios con caché (Redis) reduce el
consumo de red y CPU.
• Timeouts ajustados: Peticiones bloqueadas que nunca llegan consumen recursos sin
utilidad. Define siempre timeouts en Feign Client.
• Elegir la región del cloud más cercana: Menor latencia = menos tiempo de
procesamiento = menos energía. Y menos latencia para el usuario.
• Monitorización del consumo real: Herramientas como Cloud Carbon Footprint permiten
medir el impacto CO₂ de tu infraestructura y optimizarla.
IFCD0182 — Desarrollo Web con Java

8. Errores frecuentes en microservicios con Spring Cloud

Error / Síntoma Causa habitual Solución

El microservicio no aparece en el La dependencia eureka-client no Verifica la dependencia y que


panel de Eureka está en el [Link] o la URL de [Link]
Eureka en [Link] apunta a [Link]
es incorrecta exactamente

Feign Client lanza El nombre del servicio en Los nombres deben coincidir
UnknownHostException al @FeignClient no coincide con exactamente. Verifica en el panel
llamar a otro servicio [Link] del Eureka cómo aparece registrado el
servicio destino servicio.

El API Gateway devuelve 503 El microservicio al que intenta Arranca primero Eureka, luego los
Service Unavailable redirigir no está registrado en microservicios, por último el Gateway.
Eureka o no ha arrancado Espera unos segundos entre arranques.

Dependencia circular entre Diseño incorrecto de la Introduce un tercer servicio que


microservicios (A llama a B que arquitectura coordine, o replantea la distribución de
llama a A) responsabilidades.

Las peticiones entre servicios No hay circuit breaker ni timeout Configura timeout en Feign:
son lentas configurado — las peticiones [Link]
fallidas esperan el timeout out=5000 y readTimeout=5000
completo

Errores al arrancar: 'No instances Se arrancó antes de que Eureka Espera 30 segundos tras arrancar un
available for servicio-x' procesara el registro del servicio servicio antes de intentar llamarlo.
Eureka tarda en procesar el registro.
IFCD0182 — Desarrollo Web con Java

9. Buenas prácticas en arquitecturas de microservicios

✔ BUENAS PRÁCTICAS
Principios que guían el buen diseño de microservicios:
• Un servicio, una responsabilidad: Cada microservicio gestiona un único dominio de
negocio bien definido. Si un servicio necesita saber demasiado de otro, probablemente
está mal dividido.
• Base de datos por servicio: Cada microservicio tiene su propia base de datos. Nunca dos
servicios comparten la misma BD — eso crea acoplamiento oculto.
• Fallos son inevitables, diseña para ellos: Siempre define fallbacks en Feign Client.
Configura timeouts. Usa Circuit Breaker. Un servicio siempre puede fallar.
• APIs versionadas: Si cambias el contrato de la API de un servicio, pueden romperse otros
servicios que lo consumen. Versiona siempre: /api/v1/, /api/v2/.
• Logs correlacionados: Incluye un ID de traza en cada petición que cruce varios servicios.
Sin esto depurar errores es casi imposible.
• Empieza simple: No diseñes 20 microservicios desde el principio. Empieza con 2-3 bien
definidos y divide más cuando el problema lo justifique.
• Documenta cada API: Cada microservicio debe tener Swagger activado. En una
arquitectura distribuida, la documentación es crítica.
IFCD0182 — Desarrollo Web con Java

10. Actividad práctica guiada

Título Sistema de dos microservicios con Eureka y Feign Client

Duración 60 minutos

Objetivo Crear Eureka Server, dos microservicios registrados y comunicación entre ellos via Feign
Client

Nivel Guiado — el formador explica cada paso antes de implementarlo

Enunciado — Sistema de biblioteca distribuida


Construirás tres proyectos Spring Boot independientes: Eureka Server, libro-service y autor-service. El
servicio de libros necesita mostrar información del autor cuando devuelve un libro, para lo que llamará al
servicio de autores via Feign Client.

1 Crea el Eureka Server


Nuevo proyecto Spring Boot con la dependencia Eureka Server. Clase principal con
@EnableEurekaServer. Puerto 8761. Propiedades para que no se registre en sí mismo. Arranca
y verifica el panel en [Link]

2 Crea autor-service (puerto 8082)


Proyecto Spring Boot con Eureka Client y Spring Web. Entidad Autor (id, nombre, pais,
añoNacimiento) en memoria. API REST en /api/autores con GET /api/autores y GET
/api/autores/{id}. Registra en Eureka con [Link]=autor-service.

3 Crea libro-service (puerto 8081) con Feign Client


Proyecto con Eureka Client, Spring Web y OpenFeign. Entidad Libro (id, titulo, isbn, autorId).
Define AutorClient como @FeignClient(name="autor-service"). En el método GET
/api/libros/{id}, después de obtener el libro, llama a
[Link]([Link]()) y combina ambos en un LibroDetalleDTO.

4 Añade un fallback básico al AutorClient


Implementa un FallbackFactory que devuelva un AutorDTO con nombre="Autor no disponible"
cuando autor-service no responda. Activa el fallback en @FeignClient.

5 Prueba el sistema completo


Arranca en orden: Eureka → autor-service → libro-service. Verifica en el panel de Eureka que
ambos servicios están registrados. Llama a GET /api/libros/1 — debe devolver el libro con los
IFCD0182 — Desarrollo Web con Java

datos del autor. Para el fallback: detén autor-service y vuelve a llamar — debe devolver 'Autor
no disponible' sin error.

🤖 CON AYUDA DE LA IA
Si el Feign Client no resuelve el servicio

"Mi Feign Client lanza UnknownHostException al llamar a autor-service. El servicio autor-service


está registrado en Eureka (lo veo en el panel). El @FeignClient tiene name='autor-service'. Aquí
están mis [Link] y [Link] de libro-service: [pega el contenido]. ¿Qué puede
estar fallando?"
Los problemas de resolución de nombres en Feign son los más frecuentes. La IA puede detectar si
falta alguna dependencia o si el nombre no coincide exactamente.
IFCD0182 — Desarrollo Web con Java

11. Proyecto final del Módulo 2

ℹ NOTA
Esta actividad es el proyecto integrador del Módulo 2. Combina Spring Boot, APIs REST, Spring Cloud
y microservicios. Es deliberadamente más ambiciosa que las anteriores.

Título Sistema de e-commerce distribuido — 3 microservicios + Eureka + Gateway

Duración 120 minutos

Objetivo Construir una arquitectura de microservicios funcional aplicando todo lo aprendido en


el Módulo 2

Nivel Autónomo — documentación Spring Cloud, apuntes e IA disponibles

Entrega 3 proyectos Spring Boot en un repositorio Git. README con diagrama de arquitectura y
orden de arranque

Los cuatro componentes del sistema


• Eureka Server (puerto 8761): El registro de servicios. Sin código de negocio, solo infraestructura.
• producto-service (puerto 8082): API REST completa de productos con CRUD, DTOs, validación y
Swagger. Registrado en Eureka.
• carrito-service (puerto 8083): Gestiona el carrito de la compra. Cada carrito tiene items (productoId +
cantidad). Al añadir un item llama a producto-service via Feign para verificar que el producto existe y
tiene stock suficiente. Si el producto no existe: respuesta 404. Si no hay stock: respuesta 409 Conflict.
• API Gateway (puerto 8080): Enruta /api/productos/** a producto-service y /api/carritos/** a carrito-
service. Es el único punto de entrada para las pruebas en Postman.

Requisitos técnicos
• Feign Client en carrito-service con fallback que devuelve un ProductoDTO vacío si producto-service no
responde.
• GlobalExceptionHandler en ambos servicios con al menos 2 tipos de excepción manejados.
• Swagger activo en ambos microservicios de negocio.
• CORS configurado en el API Gateway para [Link]
• [Link] con: descripción del sistema, diagrama de arquitectura en texto (puede ser ASCII art),
puertos de cada servicio y orden de arranque.
IFCD0182 — Desarrollo Web con Java

Criterios de evaluación
Criterio Descripción Peso

Eureka + registro Ambos servicios registrados y visibles en el panel 15%


de Eureka

API Gateway funcional Las rutas /api/productos y /api/carritos funcionan 20%


desde el Gateway

Feign + fallback carrito-service llama a producto-service y tiene 20%


fallback implementado

APIs REST correctas DTOs, códigos HTTP correctos, errores manejados 20%
en ambos servicios

Swagger Accesible en ambos microservicios de negocio 10%

README y arquitectura Diagrama claro, puertos documentados, orden de 15%


arranque explicado

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir ayuda para resolver errores de Feign/Eureka, preguntar la diferencia
entre predicates en el Gateway, pedir que revise el README.
• No permitido: generar el [Link] completo de ningún servicio, el FeignClient completo
o el GlobalExceptionHandler completo.
• Recomendado: una vez funcione todo, pide a la IA que evalúe si el diseño de
microservicios es correcto y si hay alguna mejora de sostenibilidad que podrías aplicar.
IFCD0182 — Desarrollo Web con Java

12. Resumen de la unidad y cierre del Módulo 2

Concepto Lo esencial

Monolito vs Microservicios El monolito es simple y válido. Los microservicios resuelven problemas de


escala y equipos grandes. No son mejores por defecto.

Spring Cloud Ecosistema de herramientas para microservicios: Eureka, Feign, Gateway,


Config, Resilience4j.

Eureka Server Directorio de servicios. Cada microservicio se registra aquí al arrancar.

Eureka Client Dependencia que permite a un microservicio registrarse en Eureka y


descubrir otros servicios.

Feign Client Interfaz Java que describe los endpoints de otro servicio. Spring genera el
cliente HTTP automáticamente.

@EnableFeignClients Anotación en la clase principal que activa el soporte de Feign Client.

API Gateway Punto de entrada único para clientes externos. Enruta peticiones a los
microservicios correctos.

Circuit Breaker Patrón que evita el fallo en cascada. Abre el circuito cuando un servicio falla
repetidamente.

Fallback Respuesta alternativa que se devuelve cuando un servicio no está disponible.

Orden de arranque Config Server → Eureka → Microservicios → Gateway. Respetar el orden


evita errores de arranque.

Sostenibilidad Escala a cero, dimensionado correcto, caché, timeouts ajustados y


monitorización del consumo.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación del Módulo 2 completo:
5. ¿Cuál es la diferencia entre @Controller y @RestController? ¿Cuándo usas cada uno?
6. ¿Para qué sirve Eureka Server? ¿Qué ocurre si lo arrancas después de los microservicios?
7. ¿Qué ventaja tiene Feign Client frente a hacer la petición HTTP manualmente?
8. ¿Qué es un fallback y por qué es necesario en una arquitectura de microservicios?
9. ¿Qué problema resuelve el API Gateway? ¿Por qué el cliente no debería conocer la URL
de cada microservicio?
10. ¿En qué situación tiene más sentido usar microservicios que un monolito?
11. ¿Qué es el patrón Circuit Breaker y cómo evita el fallo en cascada?
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Revisión completa del Módulo 2 con IA:

"Actúa como entrevistador técnico senior para un puesto de desarrollador Java backend con
Spring Boot. Hazme 10 preguntas de dificultad creciente sobre el Módulo 2 completo: Spring
Framework (IoC, DI), Spring MVC (@Controller, Model, Thymeleaf), APIs REST (@RestController,
ResponseEntity, DTOs, @ControllerAdvice) y Microservicios (Eureka, Feign, Gateway, Circuit
Breaker). Después de cada respuesta, corrígeme y dime qué añadiría un candidato con 2 años de
experiencia."

🏁 CIERRE DEL MÓDULO 2


Cierre del Módulo 2 — Lo que llevas contigo al Módulo 3
Has completado el Módulo 2. Esto es lo que has construido:

• Entiendes Spring Framework: IoC, DI, Beans, el contenedor y cómo gestiona los objetos.
• Construyes aplicaciones web con Spring MVC: controladores, Thymeleaf, formularios y
validación.
• Diseñas y construyes APIs REST completas: @RestController, ResponseEntity, DTOs,
manejo de errores, Swagger.
• Conoces la arquitectura de microservicios y puedes implementar un sistema básico con
Eureka, Feign y Gateway.
• Tienes criterio para elegir entre monolito y microservicios según el problema.

El Módulo 3 lleva todo esto al siguiente nivel: Spring Security para proteger tus APIs con
autenticación JWT, testing profesional con JUnit y Mockito, y CI/CD con GitHub Actions. Es el módulo
que convierte el código que funciona en código que está listo para producción.

Criterios de evaluación
Criterio Descripción Peso

CRUD + códigos 5 endpoints CRUD con códigos HTTP correctos en 25%


todos los casos

DTOs Separación correcta CreateDTO/ResponseDTO con 15%


privacidad de email

Errores GlobalExceptionHandler con 3+ tipos, formato 20%


ApiError consistente

Endpoints extra Búsqueda, stats y paginación funcionando 20%


correctamente

Swagger Documentación accesible y con descripciones en los 10%


endpoints
IFCD0182 — Desarrollo Web con Java

Colección Postman Todas las peticiones probadas y exportadas en 10%


formato .json

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir ayuda con la lógica de paginación manual, preguntar la diferencia entre
@ExceptionHandler y @ControllerAdvice, revisar el diseño de los endpoints.
• No permitido: generar el controller completo, el GlobalExceptionHandler completo o los
DTOs completos.
• Recomendado: cuando termines, pide a la IA que actúe como revisor de API y evalúe si
sigues las convenciones REST correctamente.
IFCD0182 — Desarrollo Web con Java

12. Resumen de la unidad

Concepto Lo esencial

REST Estilo arquitectónico para APIs web. Recursos identificados por URLs, acciones por
métodos HTTP.

@RestController @Controller + @ResponseBody. Devuelve datos JSON, no vistas HTML.

@RequestBody Lee el JSON del cuerpo de la petición y lo convierte al objeto Java. Equivale a
@ModelAttribute para APIs.

ResponseEntity<T> Control total sobre la respuesta: cuerpo, código HTTP y cabeceras. Imprescindible
en APIs REST.

Códigos HTTP 201 crear, 200 leer/actualizar, 204 eliminar, 400 datos inválidos, 404 no
encontrado, 409 conflicto.

DTO Clase que define qué datos entran y salen de la API. Separa el modelo interno de
la interfaz pública.

@ControllerAdvice Clase que intercepta excepciones de toda la API y devuelve respuestas de error
consistentes.

@ExceptionHandler Método que captura un tipo concreto de excepción y devuelve la respuesta HTTP
correspondiente.

Swagger/OpenAPI Documentación automática de la API. Interfaz en /[Link]. Un requisito


en proyectos reales.

CORS Configuración que permite al frontend de otro dominio/puerto llamar a la API.


@CrossOrigin o CorsConfig.

Postman Herramienta para probar APIs REST enviando peticiones con cualquier método,
cabeceras y cuerpo JSON.

Versioning Incluye la versión en la URL: /api/v1/. Permite evolucionar la API sin romper
clientes existentes.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
4. ¿Qué diferencia hay entre @Controller y @RestController? ¿Cuándo usas cada uno?
5. ¿Para qué sirve ResponseEntity? ¿Por qué no basta con devolver el objeto directamente?
6. ¿Qué código HTTP devuelves cuando: creas un recurso, no lo encuentras, los datos son
inválidos, lo eliminas?
7. ¿Qué es un DTO y por qué no se expone la entidad directamente en la API?
8. ¿Qué hace @ControllerAdvice y dónde se aplica?
9. Tienes un frontend en localhost:3000 y tu API en localhost:8080. El frontend no puede
llamar a la API. ¿Qué compruebas primero?
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Prompt de entrevista técnica — APIs REST:

"Actúa como entrevistador técnico para un puesto de desarrollador Java backend. Hazme 7
preguntas sobre APIs REST con Spring Boot: principios REST, @RestController, ResponseEntity,
DTOs, @ControllerAdvice, Swagger y CORS. Después de cada respuesta, corrígeme y dime qué
añadiría un candidato con 2 años de experiencia en APIs REST."

📊 NOTA DE MERCADO LABORAL


Lo que viene en la Unidad 4:
La Unidad 4 introduce los microservicios con Spring Cloud: qué son, por qué las empresas grandes
los usan y cómo construir una arquitectura básica con Eureka (registro de servicios), Feign
(comunicación entre servicios) y Spring Cloud Gateway (API Gateway). Es el nivel que distingue a un
desarrollador backend sénior.

Todo lo que has construido hasta ahora —APIs REST, servicios, repositorios— son exactamente los
bloques que forman un microservicio. La diferencia es que en la Unidad 4 aprenderás a que varios de
esos bloques trabajen juntos.
IFCD0182 — Desarrollo Web con Java

MÓDULO 3 — Seguridad, Testing y DevOps en Aplicaciones Java


UNIDAD 1

Spring Security: autenticación y autorización


Protege tus APIs y aplicaciones web: usuarios, roles, permisos y configuración de seguridad
profesional

Duración estimada 8 horas — unidad extendida con enfoque práctico

Módulo 3 — Seguridad, Testing y DevOps en Aplicaciones Java

Unidad anterior Módulo 2 completado — Spring Boot, APIs REST y Microservicios

Nivel de entrada Heterogéneo — arrancamos suave y construimos progresivamente

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Añadir autenticación y autorización a tus aplicaciones Spring Boot usando Spring
Security 6

🔗 CONEXIÓN CON LO ANTERIOR


Lo que ya sabes y cómo conecta con esta unidad:
En el Módulo 1 viste seguridad básica en JSP: escapar salidas, PreparedStatement y tokens CSRF. En
esta unidad das el salto profesional: Spring Security gestiona todo eso y mucho más de forma
automatizada, robusta y configurable.

Las APIs REST que construiste en el Módulo 2 están ahora completamente abiertas — cualquiera
puede llamarlas. Spring Security es lo que pone la puerta de entrada con llave.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar la diferencia entre autenticación y autorización.
• Añadir Spring Security a un proyecto Spring Boot y entender qué hace automáticamente.
• Configurar la cadena de filtros de seguridad con SecurityFilterChain.
• Implementar autenticación con formulario de login y con HTTP Basic.
• Definir usuarios en memoria y en base de datos con UserDetailsService.
• Cifrar contraseñas con BCrypt — nunca en texto plano.
IFCD0182 — Desarrollo Web con Java

• Aplicar autorización por roles con hasRole() y @PreAuthorize.


• Proteger endpoints específicos según el rol del usuario.
• Configurar logout y gestión segura de sesiones.

1. Autenticación vs Autorización — La diferencia


fundamental

Antes de escribir una sola línea de código de seguridad, necesitas tener claro estos dos conceptos. Son
distintos y se confunden constantemente, incluso entre desarrolladores con experiencia.

🔑 AUTENTICACIÓN 🛡 AUTORIZACIÓN
¿QUIÉN ERES? ¿QUÉ PUEDES HACER?
Verificar la identidad del usuario. ¿Puedo Verificar los permisos del usuario. Sabiendo
confiar en que eres quien dices ser? quién eres, ¿a qué tienes acceso?

Ejemplos: usuario + contraseña, huella dactilar, Ejemplos: rol ADMIN puede borrar usuarios, rol
token de sesión, certificado digital. USER solo puede leer.

👁 FÍJATE
El orden importa:
Primero se autentica (¿quién eres?), después se autoriza (¿qué puedes hacer?). No puedes autorizar
a alguien sin saber quién es. Spring Security siempre sigue este orden.

Analogía: en un edificio de oficinas, primero enseñas tu tarjeta de empleado en la entrada


(autenticación) y luego el sistema comprueba si tienes permiso para entrar a la sala de servidores
(autorización).

📊 NOTA DE MERCADO LABORAL


Spring Security en el mercado:
Spring Security es el estándar de facto para seguridad en aplicaciones Java. Todas las ofertas de
trabajo que incluyen Spring Boot esperan que sepas configurar al menos autenticación básica.
Combinado con JWT (Unidad 2) cubre el 90% de lo que pide el mercado en seguridad backend.
IFCD0182 — Desarrollo Web con Java

2. Cómo funciona Spring Security por dentro

2.1 La cadena de filtros — Filter Chain


Spring Security funciona como una cadena de filtros que intercepta cada petición HTTP antes de que llegue a
tu controlador. Puedes imaginarlo como una serie de puntos de control, cada uno con una función
específica:

Filtro Qué hace

UsernamePasswordAuthenticationFilter Intercepta peticiones de login (POST /login) y verifica usuario y


contraseña

BasicAuthenticationFilter Lee la cabecera Authorization: Basic y extrae las credenciales

BearerTokenAuthenticationFilter Lee la cabecera Authorization: Bearer y valida el token JWT

SecurityContextPersistenceFilter Carga o guarda el contexto de seguridad (quién está


autenticado) en la sesión

ExceptionTranslationFilter Convierte las excepciones de seguridad en respuestas HTTP


(401, 403)

FilterSecurityInterceptor Comprueba si el usuario tiene permisos para acceder al recurso


solicitado

⚙ CÓMO FUNCIONA
El flujo de una petición protegida:
1. La petición HTTP llega al servidor.
2. Spring Security la intercepta antes de que llegue al controlador.
3. La cadena de filtros la procesa en orden: ¿está autenticado el usuario?
4. Si no está autenticado → 401 Unauthorized (o redirige al login).
5. Si está autenticado → ¿tiene permiso para este recurso?
6. Si no tiene permiso → 403 Forbidden.
7. Si tiene permiso → la petición llega al controlador y se procesa normalmente.

2.2 El SecurityContext — Quién está conectado


Cuando un usuario se autentica, Spring Security guarda su información en el SecurityContext. Desde
cualquier parte de tu aplicación puedes saber quién está autenticado:

// Obtener el usuario autenticado desde cualquier componente Spring


public String obtenerUsuarioActual() {
Authentication auth = [Link]().getAuthentication();
// Devuelve el nombre del usuario autenticado
return [Link]();
}
IFCD0182 — Desarrollo Web con Java
// También puedes inyectarlo directamente en un método del controlador:
@GetMapping("/mi-perfil")
public String miPerfil(Authentication auth, Model model) {
[Link]("usuario", [Link]());
[Link]("roles", [Link]());
return "perfil";
}

3. Añadir Spring Security al proyecto

3.1 La dependencia

<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

⚠ ATENCIÓN
Lo que ocurre en cuanto añades esta dependencia:
• Todos los endpoints quedan protegidos inmediatamente. Spring Security deniega el
acceso a todo por defecto.
• Se genera una contraseña aleatoria que aparece en la consola al arrancar: Using
generated security password: a3f8c2d1-...
• Se activa un formulario de login automático en /login.
• El usuario por defecto es user y la contraseña es la generada en la consola.
Esto está pensado para que te des cuenta de que la seguridad está activa. En el siguiente paso la
configurarás tú.

3.2 SecurityFilterChain — Tu configuración de seguridad


La clase de configuración de seguridad es donde defines exactamente qué endpoints están protegidos, qué
tipo de autenticación usas y cómo se gestiona el login y el logout. En Spring Security 6 se hace así:

import [Link];
import [Link];
import [Link];
import
[Link];
import [Link];

@Configuration
@EnableWebSecurity
public class SecurityConfig {
IFCD0182 — Desarrollo Web con Java

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ── AUTORIZACIÓN ── qué puede ver quién ──────────────────
.authorizeHttpRequests(auth -> auth
// Estas URLs son públicas — cualquiera puede verlas
.requestMatchers("/", "/inicio", "/css/**", "/js/**").permitAll()
// Solo ADMIN puede acceder a la sección de administración
.requestMatchers("/admin/**").hasRole("ADMIN")
// El resto requiere estar autenticado
.anyRequest().authenticated()
)
// ── LOGIN ── cómo se autentica el usuario ─────────────────
.formLogin(form -> form
.loginPage("/login") // Tu página de login personalizada
.loginProcessingUrl("/login") // URL donde se envía el formulario
.defaultSuccessUrl("/dashboard", true) // Tras login exitoso
.failureUrl("/login?error") // Si falla la autenticación
.permitAll() // El login es público
)
// ── LOGOUT ── cómo se cierra la sesión ───────────────────
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true) // Destruye la sesión del servidor
.deleteCookies("JSESSIONID") // Borra la cookie de sesión
);

return [Link]();
}
}

👁 FÍJATE
Tres cambios importantes respecto a versiones anteriores de Spring Security:
• No extends WebSecurityConfigurerAdapter: En Spring Security 6 se usa @Bean
SecurityFilterChain en lugar de extender una clase base.
• Lambda DSL: La configuración usa lambdas (auth -> auth...) en lugar de métodos
encadenados directos.
• CSRF activo por defecto: Spring Security activa la protección CSRF automáticamente para
formularios. Para APIs REST stateless la desactivaremos en la Unidad 2.
IFCD0182 — Desarrollo Web con Java

4. Gestión de usuarios y contraseñas

4.1 BCrypt — Nunca guardes contraseñas en texto plano


Antes de ver cómo configurar usuarios, necesitas entender por qué las contraseñas nunca se guardan en
texto plano en una base de datos. Si un atacante accede a la BD y las contraseñas son texto plano, tiene
acceso a todas las cuentas de todos los usuarios.

BCrypt es el algoritmo de hash que usa Spring Security por defecto. Un hash no es cifrado: es una
transformación unidireccional. No puedes «descifrar» un hash de BCrypt. Solo puedes verificar si una
contraseña coincide con el hash.

🛡 SEGURIDAD
Cómo funciona BCrypt:
8. Al registrar: la contraseña "miClave123" se convierte en "$2a$10$X7k3Yz..." (hash
BCrypt).
9. Solo el hash se guarda en la base de datos. La contraseña original nunca se almacena.
10. Al hacer login: BCrypt compara la contraseña introducida con el hash guardado.
11. Si coinciden, el login es exitoso. Si no coinciden, falla. Nunca se revierte el hash.
Aunque dos usuarios tengan la misma contraseña, sus hashes BCrypt serán diferentes (por el salt
aleatorio). Esto hace inútiles los ataques de diccionario con tablas rainbow.

4.2 Configurar el PasswordEncoder

@Bean
public PasswordEncoder passwordEncoder() {
// El número 10 es el factor de coste — más alto = más seguro pero más lento
// En producción 10-12 es un buen equilibrio entre seguridad y rendimiento
return new BCryptPasswordEncoder(10);
}

4.3 Usuarios en memoria — Para desarrollo y pruebas


Para empezar a trabajar con Spring Security sin necesidad de base de datos, puedes definir usuarios
directamente en la configuración. Esto es perfecto para aprender y para las pruebas:

@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
// Creamos usuarios con sus roles
IFCD0182 — Desarrollo Web con Java
// {noop} significa sin encoding — SOLO para pruebas rápidas
// En producción usa siempre [Link]("contraseña")
UserDetails user = [Link]()
.username("alumno")
.password([Link]("1234"))
.roles("USER") // Spring añade automáticamente el prefijo
ROLE_
.build();

UserDetails admin = [Link]()


.username("admin")
.password([Link]("admin123"))
.roles("ADMIN", "USER") // El admin también puede hacer lo que USER
.build();

return new InMemoryUserDetailsManager(user, admin);


}

4.4 Usuarios en base de datos — UserDetailsService personalizado


En una aplicación real los usuarios están en la base de datos. Debes implementar la interfaz
UserDetailsService para que Spring Security sepa cómo cargarlos:

La entidad Usuario
@Entity
@Table(name = "usuarios")
public class Usuario {
@Id @GeneratedValue(strategy = [Link])
private Long id;

@Column(unique = true, nullable = false)


private String username;

@Column(nullable = false)
// En la BD se guarda el hash BCrypt, nunca la contraseña original
private String password;

private String rol; // Ej: "ROLE_USER" o "ROLE_ADMIN"

// Getters y setters...
}

La implementación de UserDetailsService
import [Link].*;

// @Service: Spring lo gestiona como un Bean


@Service
public class UsuarioDetailsService implements UserDetailsService {

private final UsuarioRepository repo;


IFCD0182 — Desarrollo Web con Java
public UsuarioDetailsService(UsuarioRepository repo) { [Link] = repo; }

@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {

// Busca el usuario en la BD por su nombre de usuario


Usuario usuario = [Link](username)
.orElseThrow(() -> new UsernameNotFoundException(
"Usuario no encontrado: " + username));

// Convierte nuestra entidad al objeto UserDetails que Spring Security


entiende
return [Link]()
.username([Link]())
.password([Link]()) // Ya es el hash BCrypt
.roles([Link]().replace("ROLE_", "")) // Quita el prefijo si
existe
.build();
}
}

✔ BUENAS PRÁCTICAS
Cuando registras un usuario nuevo, debes cifrar su contraseña antes de guardarla en la BD. Nunca
guardes la contraseña tal como la escribe el usuario:

@Service
public class RegistroService {
private final UsuarioRepository repo;
private final PasswordEncoder encoder;

public RegistroService(UsuarioRepository repo, PasswordEncoder encoder) {


[Link] = repo; [Link] = encoder;
}

public void registrar(String username, String passwordSinCifrar) {


Usuario u = new Usuario();
[Link](username);
// SIEMPRE cifrar antes de guardar
[Link]([Link](passwordSinCifrar));
[Link]("ROLE_USER");
[Link](u);
}
}
IFCD0182 — Desarrollo Web con Java

5. Autorización por roles — Quién puede hacer qué

5.1 Roles vs Authorities — La diferencia


Spring Security distingue entre roles y authorities (permisos). Los roles son groups de autoridades con el
prefijo ROLE_. En la práctica, para la mayoría de aplicaciones los roles son suficientes:

Concepto En Spring Security Ejemplo

Role Grupo de permisos. Spring añade el prefijo hasRole("ADMIN") → busca


ROLE_ automáticamente. ROLE_ADMIN

Authority Permiso específico. Se usa tal cual, sin prefijo. hasAuthority("ROLE_ADMIN") →


exactamente ese string

Diferencia roles("ADMIN") añade ROLE_. Elige uno y mantenlo consistente


authorities("ADMIN") lo deja como está. en toda la app

5.2 Autorización en SecurityFilterChain


La forma más directa de autorizar es en la propia configuración de seguridad, en el bloque
authorizeHttpRequests:

.authorizeHttpRequests(auth -> auth


// Público — sin autenticación
.requestMatchers("/", "/inicio", "/registro").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers([Link], "/api/productos").permitAll() // Solo GET
público

// Solo ADMIN
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers([Link], "/api/**").hasRole("ADMIN")

// Autenticados con cualquier rol


.requestMatchers("/api/perfil").authenticated()

// Todo lo demás requiere autenticación


.anyRequest().authenticated()
)

⚠ ATENCIÓN
El orden de las reglas importa MUCHO:
Spring Security evalúa las reglas en el orden en que están escritas y aplica la primera que coincide. Si
pones anyRequest().authenticated() antes de las reglas específicas, bloqueas todo y las reglas
siguientes nunca se evalúan.

Regla general: las más específicas primero, la más general (anyRequest) al final.
IFCD0182 — Desarrollo Web con Java

5.3 Autorización con anotaciones — @PreAuthorize


También puedes controlar el acceso directamente en los métodos de los controladores con anotaciones.
Esto es más granular y más cercano al código que lo usa:

Activar las anotaciones en la configuración


@Configuration
@EnableWebSecurity
// @EnableMethodSecurity activa @PreAuthorize, @PostAuthorize y @Secured
@EnableMethodSecurity
public class SecurityConfig { ... }

Usar @PreAuthorize en los métodos


@RestController
@RequestMapping("/api/usuarios")
public class UsuarioController {

// Solo ADMIN puede listar todos los usuarios


@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public List<UsuarioResponseDTO> listar() { ... }

// ADMIN puede ver cualquier usuario; USER solo puede ver el suyo
@PreAuthorize("hasRole('ADMIN') or #id == [Link]")
@GetMapping("/{id}")
public UsuarioResponseDTO obtener(@PathVariable Long id) { ... }

// Solo ADMIN puede eliminar


@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ResponseEntity<Void> eliminar(@PathVariable Long id) { ... }

// Cualquier usuario autenticado puede ver su propio perfil


@PreAuthorize("isAuthenticated()")
@GetMapping("/perfil")
public UsuarioResponseDTO miPerfil(Authentication auth) { ... }
}

💡 CONSEJO
¿SecurityFilterChain o @PreAuthorize? Úsalos juntos:
• SecurityFilterChain: para reglas globales y de URL. Es la primera línea de defensa.
• @PreAuthorize: para lógica de autorización más granular y específica de cada método.
En proyectos reales se usan ambos: la FilterChain bloquea accesos masivos y @PreAuthorize afina
los casos específicos.
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Diseña tu matriz de permisos con IA
Antes de escribir código de autorización, define en papel qué puede hacer cada rol. La IA puede
ayudarte:

"Tengo una aplicación de gestión de empleados con Spring Security. Tengo tres roles: ADMIN
(gestiona todo), MANAGER (gestiona su departamento) y EMPLOYEE (solo ve su propia
información). Diseña la matriz de permisos para estos endpoints: GET /empleados, GET
/empleados/{id}, POST /empleados, PUT /empleados/{id}, DELETE /empleados/{id}. Para cada
endpoint indica qué rol puede acceder y sugiere si usar SecurityFilterChain o @PreAuthorize."

6. Página de login personalizada con Thymeleaf

6.1 El formulario de login


El formulario de login automático de Spring Security es funcional pero feo. En cuanto tu aplicación tenga
usuarios reales querrás una página propia. La configuración ya la viste en la sección 3 (loginPage("/login")).
Ahora solo necesitas el controlador y la vista:

El controlador — LoginController
@Controller
public class LoginController {

// Muestra la página de login


// Spring Security procesa el POST automáticamente en /login
@GetMapping("/login")
public String login() {
return "auth/login";
}

// Si el usuario ya está autenticado y va a /login, redirige al dashboard


@GetMapping("/dashboard")
public String dashboard(Authentication auth, Model model) {
[Link]("usuario", [Link]());
return "dashboard";
}
}

La vista Thymeleaf — templates/auth/[Link]


<!DOCTYPE html>
<html xmlns:th="[Link] lang="es">
<head><title>Iniciar sesión</title></head>
<body>
IFCD0182 — Desarrollo Web con Java
<h1>Iniciar sesión</h1>

<!-- Mensajes de error y éxito gestionados por Spring Security -->


<div th:if="${[Link]}" style="color:red">
Usuario o contraseña incorrectos.
</div>
<div th:if="${[Link]}" style="color:green">
Has cerrado sesión correctamente.
</div>

<!-- action debe coincidir con loginProcessingUrl de la configuración -->


<!-- Spring Security intercepta este POST y gestiona la autenticación -->
<form th:action="@{/login}" method="post">

<!-- th:field no funciona aquí — usamos name directamente -->


<!-- Spring Security espera los campos 'username' y 'password' por defecto -->
<label>Usuario:</label>
<input type="text" name="username" required/>

<label>Contraseña:</label>
<input type="password" name="password" required/>

<!-- Thymeleaf añade automáticamente el token CSRF -->


<button type="submit">Entrar</button>
</form>
</body>
</html>

✔ BUENAS PRÁCTICAS
Thymeleaf añade el token CSRF al formulario automáticamente cuando usas th:action. No tienes que
hacer nada extra. Spring Security lo valida al recibir el POST.
IFCD0182 — Desarrollo Web con Java

7. Registro de nuevos usuarios

El flujo de registro es independiente de Spring Security: tú gestionas el formulario y guardas el usuario.


Spring Security solo interviene en el momento del login. Lo que sí debes hacer es cifrar la contraseña antes
de guardarla.
RegistroController
@Controller
public class RegistroController {

private final RegistroService registroService;


public RegistroController(RegistroService rs) { [Link] = rs; }

// GET: muestra el formulario de registro


@GetMapping("/registro")
public String mostrarRegistro(Model model) {
[Link]("registro", new RegistroDTO());
return "auth/registro";
}

// POST: procesa el formulario — ruta pública en SecurityFilterChain


@PostMapping("/registro")
public String registrar(@Valid @ModelAttribute("registro") RegistroDTO dto,
BindingResult r) {
if ([Link]()) return "auth/registro";

if (![Link]().equals([Link]())) {
[Link]("confirmPassword", "error", "Las contraseñas no
coinciden");
return "auth/registro";
}

[Link]([Link](), [Link]());
return "redirect:/login?registrado";
}
}

RegistroDTO con validaciones


public class RegistroDTO {
@NotBlank(message = "El nombre de usuario es obligatorio")
@Size(min = 3, max = 20, message = "Entre 3 y 20 caracteres")
private String username;

@NotBlank(message = "La contraseña es obligatoria")


@Size(min = 8, message = "Mínimo 8 caracteres")
private String password;

@NotBlank(message = "Debes confirmar la contraseña")


private String confirmPassword;

// Getters y setters...
IFCD0182 — Desarrollo Web con Java
}

8. Spring Security en APIs REST — HTTP Basic

Las APIs REST son stateless: no usan sesiones ni cookies. Para protegerlas con Spring Security antes de
implementar JWT (Unidad 2), puedes usar HTTP Basic Authentication: el cliente envía las credenciales
codificadas en Base64 en cada petición.

⚠ ATENCIÓN
HTTP Basic solo es seguro con HTTPS:
Con HTTP Basic las credenciales van en cada petición codificadas en Base64, que es fácilmente
reversible. Sin HTTPS, cualquiera que intercepte el tráfico ve las credenciales. En producción usa
siempre HTTPS. En desarrollo local (localhost) es aceptable para aprender.

En la Unidad 2 implementaremos JWT, que es la solución correcta para APIs REST en producción.

8.1 Configurar HTTP Basic para APIs REST

@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
// HTTP Basic: el cliente envía Authorization: Basic base64(user:pass)
.httpBasic([Link]())
// Las APIs REST son stateless — sin sesiones
.sessionManagement(sess -> sess
.sessionCreationPolicy([Link])
)
// Desactivamos CSRF para APIs REST stateless
// (Las APIs no usan formularios HTML ni cookies de sesión)
.csrf(csrf -> [Link]());

return [Link]();
}

8.2 Probar HTTP Basic con Postman


Para probar una API protegida con HTTP Basic en Postman:

12. Abre tu petición en Postman.


13. Ve a la pestaña Authorization.
14. Selecciona el tipo Basic Auth.
15. Introduce el username y el password.
IFCD0182 — Desarrollo Web con Java

16. Postman añade automáticamente la cabecera Authorization: Basic base64(user:pass).


17. Envía la petición — si las credenciales son correctas, recibirás la respuesta.

🤖 CON AYUDA DE LA IA
Comprende la diferencia entre HTTP Basic y JWT con IA

"Explícame la diferencia entre HTTP Basic Authentication y JWT para proteger una API REST. ¿Por
qué HTTP Basic no es suficiente para producción? ¿Qué ventajas tiene JWT sobre HTTP Basic en
términos de seguridad, escalabilidad y experiencia de usuario? Muéstrame con ejemplos de
cabeceras HTTP cómo se ven ambos tipos de autenticación."
IFCD0182 — Desarrollo Web con Java

9. Errores frecuentes con Spring Security

Error / Síntoma Causa habitual Solución

403 Forbidden en endpoints que La URL no está en la lista de Verifica que la URL exacta está en
deberían ser públicos permitAll() o el orden de las reglas requestMatchers().permitAll() y que va
es incorrecto ANTES de anyRequest().authenticated()

El login siempre falla aunque las La contraseña en la BD no está Verifica que al registrar usas
credenciales sean correctas cifrada con BCrypt o el [Link](password) y que el
PasswordEncoder del @Bean PasswordEncoder es el mismo
UserDetailsService no coincide en toda la app
con el que se usó para guardar

Error 403 en formularios POST: El formulario no incluye el token Usa th:action en Thymeleaf (añade
Invalid CSRF token CSRF o se desactivó CSRF por CSRF automáticamente) o añade el
error campo hidden manualmente: <input
type='hidden' th:name='_csrf'
th:value='${_csrf.token}'/>

@PreAuthorize no bloquea nada Falta @EnableMethodSecurity en Añade @EnableMethodSecurity junto a


— todos pueden acceder la clase de configuración @EnableWebSecurity en tu
SecurityConfig

UsernameNotFoundException El método findByUsername del Verifica el nombre del campo en la


pero el usuario existe en la BD repositorio no coincide con el entidad y que el método del repositorio
campo de la entidad o usa busca por ese campo exactamente
mayúsculas/minúsculas distintas

Bucle infinito de redirecciones La URL /login no está en Asegúrate de que


en /login permitAll() o hay un conflicto con .requestMatchers("/login").permitAll()
el loginPage configurado está antes de
.anyRequest().authenticated() y que
.formLogin().permitAll() está activo

Spring Security bloquea /h2- H2 Console usa frames y Spring Añade a la config: .headers(h ->
console en desarrollo Security los bloquea con X-Frame- [Link](fo -> [Link]())) y
Options permite /h2-console en
requestMatchers
IFCD0182 — Desarrollo Web con Java

10. Buenas prácticas con Spring Security

✔ BUENAS PRÁCTICAS
Aplica estas prácticas desde el primer proyecto con seguridad:
• Nunca texto plano: Toda contraseña que se guarda en la BD debe pasar por
[Link]() antes. Sin excepciones.
• Principio de mínimo privilegio: Cada usuario y cada servicio solo debe tener los permisos
estrictamente necesarios. No des ROLE_ADMIN por comodidad.
• Deniega todo por defecto: Empieza con anyRequest().denyAll() y añade solo lo que
necesitas abrir. Es más seguro que abrir todo y cerrar lo sensible.
• Mensajes de error genéricos: No digas 'contraseña incorrecta' ni 'usuario no encontrado'
— da pistas a los atacantes. Di siempre 'credenciales incorrectas'.
• Protege los recursos estáticos correctamente: CSS, JS e imágenes deben ser públicos.
Usa requestMatchers("/css/**", "/js/**", "/images/**").permitAll().
• Separa la config de seguridad web y API: Si tienes tanto una web con formularios como
una API REST, define dos SecurityFilterChain separados con diferentes patrones.
• Activa HTTPS en producción: Configura un certificado SSL. Con HTTP todo el tráfico es
interceptable, incluidos tokens y contraseñas.
• Registra los intentos fallidos de login: Usa AuthenticationEventPublisher para detectar
ataques de fuerza bruta.
IFCD0182 — Desarrollo Web con Java

11. Actividad práctica guiada

Título Sistema de gestión con login, roles y autorización por URL

Duración 75 minutos

Objetivo Añadir Spring Security completo a una aplicación Spring MVC existente: login
personalizado, roles USER y ADMIN, autorización por URL y por anotación, y registro
con BCrypt

Nivel Guiado — el formador explica cada bloque antes de implementarlo

Enunciado — Sistema de gestión de artículos


Toma el CRUD de artículos del Módulo 2 (o crea uno nuevo) y añade Spring Security con estas reglas:

1 Añade la dependencia y configura SecurityFilterChain


Dependencia spring-boot-starter-security. Crea SecurityConfig con: / y /registro públicos,
/admin/** solo ADMIN, /articulos/** autenticado, /login público.

2 Configura el PasswordEncoder y usuarios en memoria


Bean BCryptPasswordEncoder. Dos usuarios en InMemoryUserDetailsManager: 'lector' con
ROLE_USER y 'editor' con ROLE_ADMIN. Contraseñas cifradas con el encoder.

3 Crea la página de login personalizada


LoginController con GET /login. Vista templates/auth/[Link] con el formulario
(name='username' y name='password'). Mensajes para ?error y ?logout. th:action para el
token CSRF automático.

4 Aplica @PreAuthorize en el controlador de artículos


GET /articulos → autenticado. POST /articulos → solo ADMIN. PUT /articulos/{id} → solo
ADMIN. DELETE /articulos/{id} → solo ADMIN. Activa @EnableMethodSecurity.

5 Añade el menú de usuario en la plantilla principal


En el layout Thymeleaf añade: el nombre del usuario autenticado (sec:authentication='name'),
enlace a /admin si tiene rol ADMIN, y botón de logout con formulario POST a /logout.

Necesitas la dependencia Thymeleaf Security extras: thymeleaf-extras-springsecurity6.

6 Prueba todos los escenarios


IFCD0182 — Desarrollo Web con Java

Sin autenticación → redirige a /login. Con 'lector' → puede ver artículos pero no
crear/editar/borrar (403). Con 'editor' → acceso completo. Logout → limpia la sesión.

🤖 CON AYUDA DE LA IA
Depura errores de configuración con IA

"Mi SecurityFilterChain tiene esta configuración [pega el código]. Cuando accedo a /articulos sin
autenticación, en lugar de redirigirme a /login me da un 403 directamente. ¿Qué está mal en mi
configuración?"
Los errores de configuración de Spring Security suelen ser sutiles: orden de reglas, URL incorrecta en
loginPage o falta de permitAll() en el login. La IA puede identificarlos rápidamente.
IFCD0182 — Desarrollo Web con Java

12. Actividad práctica autónoma

Título Portal de noticias con tres roles y registro real con BCrypt

Duración 90 minutos

Objetivo Implementar autónomamente un sistema completo de autenticación y autorización


con usuarios en BD y tres niveles de acceso

Nivel Autónomo — documentación Spring Security, apuntes e IA disponibles

Entrega Proyecto en .zip con un documento [Link] explicando las decisiones de


configuración tomadas

Enunciado — Portal de noticias


Desarrolla una aplicación web Spring Boot con Spring Security para un portal de noticias. Las noticias tienen:
título, contenido, categoría y autor (username del creador).
Los tres roles y sus permisos

Acción VISITANTE (no LECTOR (registrado) REDACTOR (admin)


registrado)

Ver lista de noticias ✔ Público ✔ ✔

Ver noticia completa ✔ Público ✔ ✔

Registrarse ✔ Público ✗ ✗

Ver perfil propio ✗ ✔ ✔

Crear noticia ✗ ✗ ✔

Editar cualquier noticia ✗ ✗ ✔

Editar solo la propia ✗ ✔ ✔

Eliminar noticia ✗ ✗ ✔

Requisitos técnicos
• Usuarios en H2 (o MySQL) con contraseñas cifradas con BCrypt. UserDetailsService personalizado.
• Registro de nuevos usuarios: el rol asignado es siempre LECTOR. REDACTOR solo puede crearse
manualmente en la BD con un script SQL de inicio.
• Login y logout con páginas personalizadas. Mensaje de error genérico (no revela si el usuario existe o
no).
• @PreAuthorize para la regla 'editar solo la propia noticia' — el LECTOR puede editar la noticia si su
username coincide con el autor de la noticia.
• Menú de navegación que muestra opciones distintas según el rol del usuario autenticado.
• [Link]: explica qué protege cada regla, por qué elegiste SecurityFilterChain vs
@PreAuthorize para cada caso y qué pasaría si alguien intentara acceder directamente a
/noticias/{id}/edit sin autenticación.
IFCD0182 — Desarrollo Web con Java

Criterios de evaluación
Criterio Descripción Peso

Autenticación Login/logout funcional, BCrypt en BD, 20%


UserDetailsService implementado

Autorización por URL SecurityFilterChain correcto — visitante, lector y 20%


redactor

@PreAuthorize 'Editar propia noticia' funciona correctamente con la 20%


lógica del username

Registro Registro cifra contraseña, asigna LECTOR, valida 15%


campos, evita username duplicado

UI adaptativa El menú muestra opciones distintas según el rol 15%

[Link] Explicación clara de las decisiones de configuración 10%

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir ayuda con la expresión de @PreAuthorize para el username, preguntar
cómo cargar roles desde la BD en UserDetailsService, depurar errores de configuración.
• No permitido: generar la SecurityConfig completa, el UserDetailsService completo o el
[Link] completo.
• Obligatorio: el archivo [Link] debe estar escrito por el alumno, no por la IA.
Puedes usar la IA para revisar si tus explicaciones son correctas técnicamente.
IFCD0182 — Desarrollo Web con Java

13. Resumen de la unidad

Concepto Lo esencial

Autenticación ¿Quién eres? Verificar la identidad. Siempre va antes que la autorización.

Autorización ¿Qué puedes hacer? Verificar permisos según la identidad ya verificada.

SecurityFilterChain Bean que configura toda la seguridad: qué URLs proteger, cómo autenticar,
login y logout.

@EnableWebSecurity Activa Spring Security en la aplicación.

@EnableMethodSecurity Activa las anotaciones @PreAuthorize, @PostAuthorize y @Secured en los


métodos.

BCryptPasswordEncoder Algoritmo de hash para contraseñas. NUNCA texto plano en la BD.

UserDetailsService Interfaz que Spring Security usa para cargar usuarios. Implementa
loadUserByUsername().

InMemoryUserDetailsManager Implementación para usuarios en memoria — solo para desarrollo y pruebas.

hasRole('ADMIN') Comprueba si el usuario tiene el rol ROLE_ADMIN. Usar en requestMatchers


o @PreAuthorize.

@PreAuthorize Controla el acceso a nivel de método. Más granular que la configuración por
URL.

STATELESS Para APIs REST: [Link] desactiva las sesiones en el


servidor.

permitAll() / denyAll() Permite o deniega el acceso sin importar la autenticación.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
18. ¿Qué diferencia hay entre autenticación y autorización? ¿Cuál va primero?
19. ¿Por qué nunca se guarda una contraseña en texto plano? ¿Qué hace BCrypt que la hace
segura?
20. ¿Qué ocurre en cuanto añades la dependencia spring-boot-starter-security sin ninguna
otra configuración?
21. ¿Por qué el orden de las reglas en authorizeHttpRequests() importa?
22. ¿Cuándo usarías SecurityFilterChain y cuándo @PreAuthorize? ¿Pueden usarse juntos?
23. ¿Qué hace [Link] y cuándo se usa?

🤖 CON AYUDA DE LA IA
Prompt de entrevista técnica — Spring Security:

"Actúa como entrevistador técnico para un puesto de desarrollador Java con Spring Boot. Hazme
6 preguntas sobre Spring Security: diferencia autenticación/autorización, BCrypt,
UserDetailsService, SecurityFilterChain, @PreAuthorize y configuración de APIs REST stateless.
IFCD0182 — Desarrollo Web con Java

Corrige mis respuestas e indica qué añadiría un candidato con experiencia real en Spring
Security."

📊 NOTA DE MERCADO LABORAL


Lo que viene en la Unidad 2:
En la Unidad 2 implementarás JWT (JSON Web Tokens): el estándar de la industria para autenticar
APIs REST sin sesiones. Todo lo que has aprendido en esta unidad —filtros, UserDetailsService,
autorización por roles— es la base exacta sobre la que se construye la autenticación JWT con Spring
Security.

Con Spring Security + JWT tendrás la combinación de seguridad que pide el 90% de las ofertas de
trabajo de desarrollador Java backend.
IFCD0182 — Desarrollo Web con Java

MÓDULO 3 — Seguridad, Testing y DevOps en Aplicaciones Java


UNIDAD 2

JWT — Autenticación sin sesiones para APIs REST


JSON Web Tokens: el estándar de la industria para autenticar APIs modernas de forma
segura y escalable

Duración estimada 8 horas — unidad extendida con enfoque práctico

Módulo 3 — Seguridad, Testing y DevOps en Aplicaciones Java

Unidad anterior Unidad 1 — Spring Security: autenticación y autorización

Nivel de entrada Spring Security configurado. UserDetailsService implementado. BCrypt


funcionando.

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Implementar autenticación JWT completa: login que devuelve token, filtro que
valida token, endpoints protegidos por token

🔗 CONEXIÓN CON LO ANTERIOR


Lo que ya sabes y cómo conecta:
En la Unidad 1 protegiste tu aplicación con sesiones: el servidor recuerda quién está autenticado
entre peticiones. En esta unidad eliminas esa dependencia de las sesiones. Con JWT el servidor no
recuerda nada: toda la información de quién eres viaja dentro del token en cada petición.

Resultado: una API que puede escalar a miles de servidores sin problema de sincronización de
sesiones, y que puede ser consumida por cualquier cliente (web, móvil, otro servicio) de forma
segura.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué es un JWT, su estructura y cómo funciona.
• Describir el flujo completo de autenticación con JWT.
• Implementar el endpoint de login que genera y devuelve un JWT.
IFCD0182 — Desarrollo Web con Java

• Crear el filtro JWT que valida el token en cada petición.


• Integrar el filtro JWT en la cadena de filtros de Spring Security.
• Proteger endpoints por roles usando JWT como fuente de autenticación.
• Implementar refresh tokens para renovar tokens expirados.
• Probar la autenticación JWT completa con Postman.
IFCD0182 — Desarrollo Web con Java

1. ¿Qué es JWT y por qué se usa en APIs REST?

1.1 El problema de las sesiones en APIs REST


Recuerda la Unidad 1: cuando un usuario se autentica, Spring Security guarda su información en una sesión
del servidor. El navegador recibe una cookie JSESSIONID y la envía en cada petición para identificarse.

Esto funciona bien para aplicaciones web tradicionales, pero tiene un problema grave para APIs REST
escalables:

✘ ERROR FRECUENTE
El problema de las sesiones en arquitecturas escalables:
• Si tienes 3 servidores balanceados, la sesión guardada en el servidor A no existe en el B ni
en el C.
• Si el usuario con sesión en el servidor A recibe su siguiente petición en el servidor B,
tiene que volver a autenticarse.
• La solución clásica (sesiones compartidas en Redis) añade complejidad y un punto de
fallo extra.
• Las apps móviles y los SPAs no funcionan bien con cookies de sesión.

1.2 JWT — La solución stateless


JWT (JSON Web Token, pronunciado 'yot') es un estándar abierto (RFC 7519) para transmitir información de
forma segura entre partes como un objeto JSON compacto y firmado digitalmente.

La idea es simple: en lugar de que el servidor recuerde quién eres, tú llevas contigo una prueba firmada de
quién eres. En cada petición presentas esa prueba. El servidor solo necesita verificar la firma, sin consultar
ninguna BD ni sesión.

👁 FÍJATE
Analogía para entender JWT:
Sin JWT: cada vez que entras a un edificio de oficinas enseñas tu DNI, el guardia llama a RRHH para
verificar que trabajas allí, RRHH confirma y te dejan pasar. El guardia no recuerda nada.

Con JWT: la empresa te da un pase de empleado firmado digitalmente. Cada vez que entras, el
guardia solo verifica que la firma del pase es auténtica. No llama a nadie. Si la firma es válida, entras.

El pase tiene fecha de caducidad. Cuando expira, necesitas renovarlo.


IFCD0182 — Desarrollo Web con Java

📊 NOTA DE MERCADO LABORAL


JWT en el mercado:
JWT es el mecanismo de autenticación más usado en APIs REST modernas. Prácticamente todas las
aplicaciones con frontend separado (React, Angular, app móvil) usan JWT. Es lo que más buscan las
empresas cuando contratan desarrolladores backend con experiencia en Spring Security.
IFCD0182 — Desarrollo Web con Java

2. Estructura de un JWT

Un JWT tiene exactamente tres partes separadas por puntos. Cada parte está codificada en Base64URL (no
es cifrado — es codificación, que es reversible):

HEADER PAYLOAD SIGNATURE


eyJhbGciOiJIUzI1NiJ9 eyJzdWIiOiJ1c3VhcmlvIn0 SflKxwRJSMeKKF2QT4...
Tipo de token y algoritmo de firma Los datos (claims): usuario, roles, Firma HMAC: Header + Payload +
expiración Clave secreta

2.1 El Header — Metadatos del token


{
"alg": "HS256",
// Algoritmo de firma: HMAC SHA-256
"typ": "JWT"
// Tipo de token
}

2.2 El Payload — Los datos del token


El payload contiene los claims: datos sobre el usuario y el token. Hay tres tipos:

• Claims registrados (estándar): sub (subject/usuario), iat (issued at/cuándo se creó), exp
(expiration/cuándo expira), iss (issuer/quién lo emitió).
• Claims públicos: definidos por la comunidad (roles, email...).
• Claims privados: específicos de tu aplicación.

{
"sub": "alumno",
// subject — el username del usuario
"roles": ["ROLE_USER"],
// claim personalizado — roles del usuario
"iat": 1714300000,
// issued at — timestamp de creación (segundos Unix)
"exp": 1714386400
// expiration — timestamp de expiración
}

⚠ ATENCIÓN
El payload NO es seguro para datos sensibles:
IFCD0182 — Desarrollo Web con Java

El payload solo está codificado en Base64URL, no cifrado. Cualquiera puede decodificarlo y leer su
contenido. Nunca incluyas contraseñas, números de tarjeta ni datos sensibles en el payload. El JWT
garantiza integridad (nadie puede modificarlo sin que se detecte), no confidencialidad.

2.3 La Signature — La parte que lo hace seguro


La firma se genera combinando el Header codificado, el Payload codificado y una clave secreta que solo
conoce el servidor, usando el algoritmo especificado en el Header:

HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
claveSecreta
)

🛡 SEGURIDAD
Si alguien modifica el payload (por ejemplo, cambia el rol de USER a ADMIN), la firma ya no coincide
con el nuevo payload. El servidor detecta la manipulación y rechaza el token con 401 Unauthorized.
La clave secreta nunca sale del servidor.
IFCD0182 — Desarrollo Web con Java

3. El flujo completo de autenticación con JWT

Antes de escribir código, entiende el flujo completo. Es el mismo en cualquier aplicación que use JWT,
independientemente del lenguaje o framework:

1 El usuario envía sus credenciales


POST /api/auth/login con JSON: {"username": "alumno", "password": "1234"}

Este endpoint es público — no requiere autenticación.

2 El servidor verifica las credenciales


Spring Security compara username y password con los datos de la BD (usando
UserDetailsService y BCrypt).

3 Si son correctas, el servidor genera un JWT y lo devuelve


El servidor crea el JWT con: sub=username, roles=lista de roles, iat=ahora, exp=ahora+24h. Lo
firma con la clave secreta y lo devuelve al cliente.

{
"token": "[Link]...",
"tipo": "Bearer",
"expiracion": "2025-05-29T10:00:00"
}

4 El cliente guarda el token


El cliente (app React, Postman, app móvil) guarda el token. Para aplicaciones web: en memoria
o localStorage. Para apps móviles: en almacenamiento seguro del dispositivo.

5 En cada petición siguiente, el cliente envía el token


El cliente incluye el token en la cabecera HTTP Authorization:

Authorization: Bearer [Link]...


Es el estándar Bearer Token (RFC 6750).

6 El filtro JWT del servidor valida el token


Antes de que la petición llegue al controlador, el filtro JWT: extrae el token de la cabecera,
verifica la firma, comprueba que no ha expirado, extrae el usuario y los roles, y los carga en el
SecurityContext.
IFCD0182 — Desarrollo Web con Java

7 Spring Security aplica la autorización normalmente


Con el usuario ya cargado en el SecurityContext, Spring Security aplica las reglas de
autorización (@PreAuthorize, requestMatchers...) exactamente igual que en la Unidad 1.

🔗 CONEXIÓN CON LO ANTERIOR


Los pasos 2 y 7 son exactamente lo que viste en la Unidad 1. JWT solo cambia los pasos 1, 3, 4, 5 y 6.
La autorización sigue siendo Spring Security de siempre.
IFCD0182 — Desarrollo Web con Java

4. Implementación JWT paso a paso

4.1 Dependencia — La librería JJWT


Usaremos JJWT (Java JWT), la librería más popular y actualizada para trabajar con JWT en Java:

<dependency>
<groupId>[Link]</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>

4.2 Configuración en [Link]

# Clave secreta para firmar los JWT


# Debe ser una cadena larga y aleatoria — en producción usa variables de entorno
[Link]=miClaveSecretaSuperLargaYSeguraQueNadiePuedeAdivinar2025!!

# Duración del token en milisegundos: 86400000 = 24 horas


[Link]=86400000

⚠ ATENCIÓN
La clave secreta NUNCA debe ir en el repositorio Git:
En development puedes tenerla en [Link], pero en production debe estar en una
variable de entorno o un servicio de secretos (AWS Secrets Manager, Vault...). Si alguien obtiene la
clave secreta puede generar tokens válidos para cualquier usuario.

Añade [Link] al .gitignore para evitar subir credenciales de producción.

4.3 JwtService — Generar y validar tokens

import [Link].*;
IFCD0182 — Desarrollo Web con Java
import [Link];
import [Link];
import [Link];
import [Link];

import [Link];
import [Link];
import [Link];
import [Link];

@Service
public class JwtService {

@Value("${[Link]}")
private String secreto;

@Value("${[Link]}")
private long expiracion;

// Convierte el String de configuración en una SecretKey criptográfica


private SecretKey getClave() {
return [Link]([Link]());
}

// ── GENERAR TOKEN ────────────────────────────────────────────


public String generarToken(UserDetails usuario) {
Map<String, Object> claims = new HashMap<>();
// Añadimos los roles al payload del token
[Link]("roles", [Link]().toString());

return [Link]()
// Sub: el nombre de usuario
.subject([Link]())
// Claims adicionales: roles
.claims(claims)
// Iat: cuándo se creó
.issuedAt(new Date())
// Exp: cuándo expira
.expiration(new Date([Link]() + expiracion))
// Firma con nuestra clave secreta
.signWith(getClave())
.compact();
}

// ── EXTRAER USERNAME ─────────────────────────────────────────


public String extraerUsername(String token) {
return parsearToken(token).getPayload().getSubject();
}

// ── VALIDAR TOKEN ─────────────────────────────────────────────


public boolean esTokenValido(String token, UserDetails usuario) {
try {
String username = extraerUsername(token);
boolean noExpirado = !parsearToken(token).getPayload().getExpiration()
.before(new Date());
IFCD0182 — Desarrollo Web con Java
return [Link]([Link]()) && noExpirado;
} catch (JwtException e) {
// Firma inválida, token manipulado, token expirado...
return false;
}
}

// ── PARSEAR TOKEN ─────────────────────────────────────────────


private Jws<Claims> parsearToken(String token) {
return [Link]()
.verifyWith(getClave())
.build()
.parseSignedClaims(token);
}
}

4.4 JwtAuthFilter — El filtro que valida el token


Este filtro intercepta cada petición, extrae el token del header Authorization, lo valida y carga el usuario en
el SecurityContext si es válido:

import [Link];
import [Link].*;
import
[Link];
import [Link];
import [Link];
import
[Link];
import [Link];
import [Link];

// OncePerRequestFilter: garantiza que el filtro se ejecuta una sola vez por petición
@Component
public class JwtAuthFilter extends OncePerRequestFilter {

private final JwtService jwtService;


private final UsuarioDetailsService userDetailsService;

public JwtAuthFilter(JwtService jwtService,


UsuarioDetailsService userDetailsService) {
[Link] = jwtService;
[Link] = userDetailsService;
}

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws [Link], [Link] {

// 1. Extraemos la cabecera Authorization


String authHeader = [Link]("Authorization");
IFCD0182 — Desarrollo Web con Java

// 2. Si no hay cabecera o no empieza por Bearer, continuamos sin autenticar


if (authHeader == null || ![Link]("Bearer ")) {
[Link](request, response);
return;
}

// 3. Extraemos el token (quitamos el prefijo "Bearer ")


String token = [Link](7);

// 4. Extraemos el username del token


String username = [Link](token);

// 5. Si hay username y aún no hay autenticación en el contexto


if (username != null && [Link]()
.getAuthentication() == null) {

// 6. Cargamos el usuario desde la BD


UserDetails userDetails =
[Link](username);

// 7. Validamos el token contra el usuario


if ([Link](token, userDetails)) {

// 8. Creamos el objeto de autenticación


UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null, // credentials
[Link]() // roles
);
[Link](
new WebAuthenticationDetailsSource().buildDetails(request));

// 9. Lo guardamos en el SecurityContext
// A partir de aquí, Spring Security sabe quién es el usuario
[Link]().setAuthentication(authToken);
}
}

// 10. Continuamos con la cadena de filtros


[Link](request, response);
}
}
IFCD0182 — Desarrollo Web con Java

4.5 SecurityConfig actualizada para JWT


Ahora actualiza la configuración de Spring Security para usar el filtro JWT en lugar de sesiones:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

private final JwtAuthFilter jwtAuthFilter;


private final UsuarioDetailsService userDetailsService;

public SecurityConfig(JwtAuthFilter jwtAuthFilter,


UsuarioDetailsService userDetailsService) {
[Link] = jwtAuthFilter;
[Link] = userDetailsService;
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
// Sin sesiones — JWT es stateless
.sessionManagement(sess -> sess
.sessionCreationPolicy([Link])
)
// Sin CSRF — no hay formularios HTML ni cookies de sesión
.csrf(csrf -> [Link]())
// Añadimos nuestro filtro JWT ANTES del filtro de usuario/contraseña
.addFilterBefore(jwtAuthFilter,
[Link]);

return [Link]();
}

@Bean
public AuthenticationManager authManager(
AuthenticationConfiguration config) throws Exception {
return [Link]();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
}
IFCD0182 — Desarrollo Web con Java

5. El endpoint de login — POST /api/auth/login

El endpoint de login es donde el usuario envía sus credenciales y, si son correctas, recibe el JWT. Es el único
endpoint que no requiere autenticación previa:

DTOs para login


// Lo que recibe el endpoint
public class LoginRequest {
@NotBlank private String username;
@NotBlank private String password;
// Getters y setters...
}

// Lo que devuelve el endpoint


public class LoginResponse {
private String token;
private String tipo = "Bearer";
private String username;
private List<String> roles;
// Constructor, getters...
}

AuthController — el controlador de autenticación


@RestController
@RequestMapping("/api/auth")
public class AuthController {

private final AuthenticationManager authManager;


private final JwtService jwtService;
private final UsuarioDetailsService userDetailsService;

public AuthController(AuthenticationManager am,


JwtService js,
UsuarioDetailsService uds) {
[Link] = am;
[Link] = js;
[Link] = uds;
}

// POST /api/auth/login — público, no requiere token


@PostMapping("/login")
public ResponseEntity<LoginResponse> login(
@Valid @RequestBody LoginRequest request) {

try {
// 1. Intentamos autenticar con Spring Security
// Esto llama a UserDetailsService y verifica la contraseña con BCrypt
[Link](
new UsernamePasswordAuthenticationToken(
IFCD0182 — Desarrollo Web con Java
[Link](),
[Link]()
)
);

// 2. Si llegamos aquí, las credenciales son correctas


UserDetails usuario = userDetailsService
.loadUserByUsername([Link]());

// 3. Generamos el JWT
String token = [Link](usuario);

// 4. Devolvemos el token al cliente


List<String> roles = [Link]().stream()
.map(a -> [Link]())
.collect([Link]());

return [Link](new LoginResponse(


token, [Link](), roles));

} catch (AuthenticationException e) {
// Credenciales incorrectas — mensaje genérico por seguridad
return [Link]([Link])
.body(null);
}
}
}

🤖 CON AYUDA DE LA IA
Comprende el flujo completo con IA
Una vez que el código funciona, usa este prompt para afianzar la comprensión:

"Tengo implementado JWT con Spring Security. Cuando el usuario hace POST /api/auth/login con
sus credenciales, ¿qué clase de Spring Security se activa primero? Traza el flujo completo: desde
que llega la petición hasta que el token JWT llega al cliente, pasando por
AuthenticationManager, UserDetailsService, BCrypt y JwtService. Explícamelo en orden de
ejecución."
IFCD0182 — Desarrollo Web con Java

6. Refresh Tokens — Renovar el token sin volver a


loguearse

6.1 El problema de los tokens de corta duración


Un JWT de 24 horas es práctico pero no ideal para producción. Si en algún momento el token es
comprometido, el atacante tiene acceso durante 24 horas. Si lo acortas a 15 minutos, el usuario tiene que
volver a loguearse cada cuarto de hora — una experiencia terrible.

La solución es usar dos tipos de token: un access token de corta duración (15-60 minutos) y un refresh token
de larga duración (7-30 días) que solo sirve para obtener un nuevo access token.

Token Duración típica Para qué sirve Dónde se guarda

Access Token 15-60 minutos Autenticar peticiones a la API Memoria del cliente (JS)
— nunca en localStorage
por XSS

Refresh Token 7-30 días Obtener un nuevo access token Cookie HttpOnly — el
cuando expira navegador no puede
acceder via JS

6.2 Implementación básica del Refresh Token

La entidad RefreshToken en BD
@Entity
public class RefreshToken {
@Id @GeneratedValue(strategy = [Link])
private Long id;
private String token; // UUID aleatorio
private String username;
private Instant expiracion;
private boolean revocado = false;
// Getters y setters...
}

El endpoint de refresh
// POST /api/auth/refresh
// El cliente envía el refresh token y recibe un nuevo access token
@PostMapping("/refresh")
public ResponseEntity<LoginResponse> refresh(
@RequestBody Map<String, String> body) {

String refreshToken = [Link]("refreshToken");


IFCD0182 — Desarrollo Web con Java
RefreshToken stored = [Link](refreshToken)
.orElseThrow(() -> new RuntimeException("Token no encontrado"));

if ([Link]() ||
[Link]().isBefore([Link]())) {
return [Link]([Link]).build();
}

UserDetails usuario = userDetailsService


.loadUserByUsername([Link]());
String nuevoToken = [Link](usuario);

return [Link](new LoginResponse(nuevoToken, [Link](),


null));
}

ℹ NOTA
En proyectos reales el refresh token viaja en una cookie HttpOnly: el servidor la establece al hacer
login y el navegador la envía automáticamente. Esto evita que JavaScript pueda robar el refresh
token mediante XSS. La implementación completa con cookies va más allá del alcance de esta unidad
pero es importante conocer el concepto.
IFCD0182 — Desarrollo Web con Java

7. Probar la autenticación JWT con Postman

Probar JWT con Postman requiere seguir un flujo de dos pasos: primero obtener el token y luego usarlo en
las siguientes peticiones.

1 Hacer login y obtener el token


Nueva petición: Método POST. URL: [Link]

Body → raw → JSON:

{"username": "alumno", "password": "1234"}


Envía. Deberías recibir 200 OK con el JSON que contiene el token.

2 Copiar el token de la respuesta


De la respuesta copia el valor del campo 'token' (sin las comillas). Es la cadena larga que
empieza por eyJ...

3 Usar el token en una petición protegida


Nueva petición: Método GET. URL: [Link]

Ve a la pestaña Authorization. Tipo: Bearer Token. Pega el token en el campo Token.

Postman añade automáticamente la cabecera: Authorization: Bearer eyJ...

Envía. Deberías recibir 200 OK con los productos.

4 Verificar que sin token recibes 401


En la misma petición, elimina el token del campo Bearer Token y envía. Deberías recibir 401
Unauthorized.

5 Usar variables de entorno en Postman para el token


En Postman crea una variable de entorno llamada jwt_token. En la respuesta del login, añade
un script en la pestaña Tests:

const resp = [Link]();


[Link]('jwt_token', [Link]);
Ahora en las otras peticiones usa {{jwt_token}} en el campo Bearer Token. Se actualiza
automáticamente al hacer login.

🤖 CON AYUDA DE LA IA
IFCD0182 — Desarrollo Web con Java

Decodifica y analiza tu JWT con IA


Una vez que tienes un token, pégalo en [Link] para ver su contenido decodificado. También puedes
preguntarle a la IA:

"Tengo este JWT generado por mi aplicación Spring Boot: [pega el token]. Decodifica el header y
el payload (recuerda que es Base64URL, no cifrado) y dime: cuándo expira, qué roles tiene el
usuario, si el algoritmo de firma es seguro y qué ocurriría si alguien intenta modificar el payload y
enviar el token manipulado a mi API."
IFCD0182 — Desarrollo Web con Java

8. Errores frecuentes con JWT en Spring Security

Error / Síntoma Causa habitual Solución

401 Unauthorized aunque el El filtro JWT no se está ejecutando Verifica que el filtro está añadido en
token sea válido o no llega al SecurityContext SecurityConfig con addFilterBefore().
Añade un log en doFilterInternal para
confirmar que se ejecuta.

[Link] La clave secreta usada para Verifica que la clave en


ureException: JWT signature validar no coincide con la usada [Link] es la misma que
does not match para firmar, o el token fue se usa en JwtService. Nunca modifiques
manipulado un token manualmente.

ExpiredJwtException: JWT El token ha superado su fecha de Implementa refresh tokens. En


expired at... expiración desarrollo puedes aumentar la
expiración temporalmente. Captura
esta excepción en el filtro y devuelve
401.

El endpoint de login devuelve La URL /api/auth/login no está en Añade


403 Forbidden la lista de rutas públicas .requestMatchers("/api/auth/**").per
(permitAll) mitAll() ANTES de
.anyRequest().authenticated() en tu
SecurityFilterChain

NullPointerException en El token llega mal formado o sin el Verifica que el cliente envía
JwtAuthFilter al extraer el prefijo Bearer Authorization: Bearer [token] (con
username espacio). En el filtro, comprueba que
authHeader != null y startsWith('Bearer
')

Los roles del JWT no coinciden Los roles en el token tienen Verifica el formato de los roles en el
con los esperados en formato distinto al que Spring payload del JWT y en las anotaciones.
@PreAuthorize Security espera (ej: ROLE_USER vs hasRole('USER') busca ROLE_USER en
USER) los authorities.

MalformedJwtException: Unable El token está truncado o corrupto Verifica que el cliente no está cortando
to read JSON value el token al guardarlo o enviarlo. Los
tokens JWT no tienen un límite de
longitud estándar pero pueden ser
largos.
IFCD0182 — Desarrollo Web con Java

9. Buenas prácticas con JWT

✔ BUENAS PRÁCTICAS
Aplica estas prácticas desde el primer proyecto con JWT:
• Clave secreta de mínimo 256 bits: Usa una cadena aleatoria larga (32+ caracteres).
Genera con: openssl rand -hex 32 en terminal.
• Clave secreta en variables de entorno: Nunca en el código fuente ni en el repositorio Git.
Usa @Value('${[Link]}') apuntando a una variable de entorno en producción.
• Access tokens de corta duración: 15-60 minutos para producción. Combina con refresh
tokens para la experiencia de usuario.
• No guardes datos sensibles en el payload: El payload es legible por cualquiera. Solo
incluye username, roles e información no sensible.
• Implementa revocación en casos críticos: Si un usuario cambia su contraseña o cierra
todas las sesiones, los tokens existentes deben invalidarse. Necesitas una blacklist en BD
o Redis.
• HTTPS obligatorio: Sin HTTPS cualquiera puede interceptar el token Bearer. En desarrollo
localhost es suficiente, en producción nunca.
• Valida siempre en el servidor: Nunca confíes en validaciones del cliente. El filtro JWT del
servidor es la única fuente de verdad.
• Registra los fallos de validación: Firma inválida, tokens expirados, intentos de tokens
manipulados — son señales de ataque. Regístralos y monitoriza.
IFCD0182 — Desarrollo Web con Java

10. Actividad práctica guiada

Título API REST segura con JWT — login, token y endpoints protegidos por rol

Duración 75 minutos

Objetivo Implementar la autenticación JWT completa sobre la API REST del Módulo 2:
JwtService, filtro JWT, SecurityConfig actualizada y endpoint de login

Nivel Guiado — el formador explica cada clase antes de implementarla

Enunciado — API de tareas con JWT


Añade autenticación JWT a una API REST de gestión de tareas (o crea una nueva con ese propósito). La API
debe tener los endpoints GET /api/tareas, POST /api/tareas, PUT /api/tareas/{id} y DELETE /api/tareas/{id}.

1 Añade las dependencias y configura [Link]


Las tres dependencias de JJWT al [Link]. Añade [Link] con una clave larga y
[Link]=3600000 (1 hora para pruebas).

2 Implementa JwtService
Las cuatro operaciones: getClave(), generarToken(UserDetails), extraerUsername(String),
esTokenValido(String, UserDetails). Copia el código de la sección 4.3 y asegúrate de que
compila sin errores.

3 Implementa JwtAuthFilter
Extiende OncePerRequestFilter. El flujo de 10 pasos de la sección 4.4. Presta especial atención
al paso donde añades la autenticación al SecurityContext.

4 Actualiza SecurityConfig
Cambia a STATELESS, deshabilita CSRF, permite /api/auth/**, añade el filtro JWT con
addFilterBefore. Crea el Bean AuthenticationManager.

5 Crea AuthController con POST /api/auth/login


Usa [Link](). Si es exitoso, genera el token con JwtService y
devuelve LoginResponse con 200 OK. Si falla, devuelve 401.

6 Protege los endpoints con @PreAuthorize


IFCD0182 — Desarrollo Web con Java

GET /api/tareas → autenticado. POST /api/tareas → ROLE_USER o ROLE_ADMIN. DELETE


/api/tareas/{id} → solo ROLE_ADMIN.

7 Prueba el flujo completo con Postman


Sin token → 401. Con credenciales incorrectas en /login → 401. Con credenciales correctas →
token JWT. Con token en Bearer → 200. Con token y rol insuficiente → 403.

🤖 CON AYUDA DE LA IA
Depura el filtro JWT con IA

"Mi JwtAuthFilter se ejecuta pero sigue devolviendo 401 aunque el token sea válido. El método
esTokenValido devuelve true. Aquí está el código del filtro: [pega el código]. ¿En qué punto puede
estar fallando la carga del SecurityContext?"
La causa más frecuente de este error es que el authentication se crea pero no se guarda
correctamente en el SecurityContext. La IA puede identificar la línea exacta del problema.
IFCD0182 — Desarrollo Web con Java

11. Actividad práctica autónoma

Título Sistema de gestión de notas con JWT, refresh token básico y logout

Duración 90 minutos

Objetivo Implementar autónomamente JWT completo con registro, login, refresh token básico,
logout y endpoints protegidos por rol

Nivel Autónomo — documentación JJWT, apuntes e IA disponibles

Entrega Proyecto en .zip con colección Postman que incluya todos los flujos de autenticación
probados

Enunciado — API de notas personales


Construye una API REST para gestionar notas personales. Las notas tienen: título, contenido, etiqueta
(trabajo/personal/estudio) y propietario (username del creador).

Endpoints requeridos
• Autenticación pública: POST /api/auth/registro (crea usuario con rol USER), POST /api/auth/login
(devuelve access token + refresh token), POST /api/auth/refresh (renueva el access token), POST
/api/auth/logout (invalida el refresh token).
• Notas (requieren access token válido): GET /api/notas (solo las del usuario autenticado), GET
/api/notas/{id}, POST /api/notas, PUT /api/notas/{id} (solo si es el propietario), DELETE
/api/notas/{id} (propietario o ADMIN).
• Admin (requiere rol ADMIN): GET /api/admin/notas (todas las notas de todos los usuarios), GET
/api/admin/usuarios.

Requisitos técnicos
• Access token con expiración de 15 minutos. Refresh token almacenado en BD (H2) con expiración de
7 días.
• Logout invalida el refresh token en BD (campo revocado=true). El access token sigue siendo válido
hasta que expire (esto es correcto — es una limitación conocida de JWT que se acepta).
• El endpoint PUT /api/notas/{id} debe verificar con @PreAuthorize que el username del token
coincide con el propietario de la nota.
• Manejo de errores JWT centralizado: token expirado → 401 con mensaje 'Token expirado', token
inválido → 401 con mensaje 'Token inválido'.
• Colección Postman con todos los flujos: registro → login → usar access token → esperar expiración →
refresh → usar nuevo token → logout → intentar refresh después del logout (debe fallar).
IFCD0182 — Desarrollo Web con Java

Criterios de evaluación
Criterio Descripción Peso

JWT funcional Login genera token válido, filtro valida 25%


correctamente, 401 sin token

Refresh token Refresh genera nuevo access token, logout invalida el 20%
refresh

Autorización por nota @PreAuthorize verifica propietario en PUT — otros 20%


usuarios reciben 403

Registro completo Registro cifra contraseña, asigna ROLE_USER, evita 15%


duplicados

Manejo de errores JWT Token expirado y token inválido devuelven 401 con 10%
mensajes distintos

Colección Postman Todos los flujos probados y documentados, variables 10%


de entorno para el token

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir ayuda con la lógica de @PreAuthorize para el propietario, preguntar
cómo capturar JwtException en el filtro, depurar por qué el refresh token no se invalida.
• No permitido: generar JwtService completo, JwtAuthFilter completo o SecurityConfig
completo.
• Obligatorio: la colección Postman debe mostrar que el alumno ha probado cada flujo
manualmente, no que la IA la ha generado.
IFCD0182 — Desarrollo Web con Java

12. Resumen de la unidad

Concepto Lo esencial

JWT JSON Web Token. Token firmado digitalmente que permite autenticación
stateless.

Header Primera parte del JWT. Indica el tipo (JWT) y el algoritmo de firma (HS256).

Payload Segunda parte. Contiene los claims: sub (username), roles, iat (creación), exp
(expiración).

Signature Tercera parte. HMAC(Header+Payload, claveSecreta). Garantiza integridad —


no confidencialidad.

JJWT La librería Java más usada para crear y validar JWTs. Tres dependencias: api,
impl, jackson.

JwtService Tu clase de servicio: generarToken(), extraerUsername(), esTokenValido().

JwtAuthFilter Filtro que intercepta cada petición, extrae el token del header y lo valida.

OncePerRequestFilter Clase base del filtro JWT. Garantiza que se ejecuta una sola vez por petición.

Bearer Token El estándar para enviar JWT: Authorization: Bearer eyJ...

STATELESS [Link]: sin sesiones. JWT reemplaza la sesión.

Access Token Token de corta duración (15-60 min) para autenticar peticiones a la API.

Refresh Token Token de larga duración (7-30 días) para obtener nuevos access tokens.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
1. ¿Por qué JWT es mejor que las sesiones para APIs REST escalables?
2. ¿Qué contiene cada una de las tres partes de un JWT?
3. ¿Por qué no debes guardar datos sensibles en el payload del JWT?
4. ¿Qué hace el JwtAuthFilter exactamente? ¿En qué momento de la cadena de filtros se
ejecuta?
5. ¿Qué diferencia hay entre access token y refresh token? ¿Por qué se usan los dos?
6. Si alguien consigue tu JWT antes de que expire, ¿qué pueden hacer? ¿Cómo lo mitigas?

🤖 CON AYUDA DE LA IA
Prompt de entrevista técnica — JWT:

"Actúa como entrevistador técnico para un puesto de desarrollador Java backend con Spring
Security y JWT. Hazme 7 preguntas sobre: qué es JWT y sus tres partes, por qué el payload no es
seguro para datos sensibles, cómo funciona el filtro JWT en Spring Security, diferencia entre
access y refresh token, qué ocurre cuando un JWT expira, cómo revocar un JWT antes de su
expiración y buenas prácticas de seguridad. Corrige mis respuestas."
IFCD0182 — Desarrollo Web con Java

📊 NOTA DE MERCADO LABORAL


Lo que viene en la Unidad 3:
La Unidad 3 introduce el testing profesional con JUnit 5 y Mockito: cómo escribir pruebas unitarias e
integración que verifiquen automáticamente que tu código funciona correctamente. Es la habilidad
que más diferencia a un desarrollador que puede trabajar en producción de uno que no.

Con Spring Security + JWT + Testing tendrás las tres habilidades de seguridad más demandadas en el
mercado Java.
IFCD0182 — Desarrollo Web con Java

MÓDULO 3 — Seguridad, Testing y DevOps en Aplicaciones Java


UNIDAD 3

Testing con JUnit 5 y Mockito


Pruebas unitarias, de integración y del controlador web: el código que garantiza que tu
código funciona

Duración estimada 8 horas — unidad extendida con enfoque muy práctico

Módulo 3 — Seguridad, Testing y DevOps en Aplicaciones Java

Unidad anterior Unidad 2 — JWT: autenticación sin sesiones para APIs REST

Nivel de entrada API REST con Spring Boot funcionando. Spring Security configurado.

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Escribir pruebas automáticas que verifiquen que tu código funciona
correctamente: unitarias con JUnit y Mockito, de integración con Spring Boot y del
controlador con MockMvc

📊 NOTA DE MERCADO LABORAL


Testing en el mercado laboral:
Saber escribir pruebas automáticas es una de las habilidades que más diferencia a un desarrollador
junior de uno que puede trabajar en proyectos reales. Muchas empresas exigen cobertura de tests
en sus pull requests. El conocimiento de JUnit y Mockito aparece constantemente en las
descripciones de puestos Java.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar por qué los tests automáticos son fundamentales en el desarrollo profesional.
• Distinguir entre pruebas unitarias, de integración y end-to-end.
• Escribir pruebas unitarias con JUnit 5 y sus anotaciones principales.
• Usar el patrón AAA (Arrange-Act-Assert) para estructurar pruebas claras.
• Crear mocks con Mockito para aislar el código que se está probando.
• Verificar comportamientos con when(), thenReturn() y verify().
IFCD0182 — Desarrollo Web con Java

• Escribir pruebas de integración con @SpringBootTest.


• Probar controladores REST con @WebMvcTest y MockMvc.
• Medir la cobertura de pruebas con JaCoCo.

1. ¿Por qué escribir pruebas automáticas?

1.1 El problema de probar manualmente


Hasta ahora has probado tu código arrancando la aplicación, abriendo Postman y haciendo peticiones a
mano. Eso funciona para verificar que algo funciona en un momento concreto. Pero tiene problemas graves
en proyectos reales:

✘ ERROR FRECUENTE
Los problemas de las pruebas manuales:
• Son lentas: arrancar la app, abrir Postman, introducir datos, verificar... para cada cambio.
• No escalan: con 50 endpoints no puedes probar todos manualmente tras cada cambio.
• No detectan regresiones: arreglas un bug pero introduces otro en otro sitio sin saberlo.
• No son repetibles: los resultados dependen de los datos que haya en la BD en ese
momento.
• No se pueden automatizar en un pipeline CI/CD.

1.2 Las ventajas de los tests automáticos


Los tests automáticos son código que prueba tu código. Se ejecutan en segundos, en cualquier momento, sin
necesidad de arrancar la aplicación completa, y siempre dan el mismo resultado dado el mismo código.

✔ BUENAS PRÁCTICAS
Lo que consigues con buenos tests:
• Confianza para refactorizar: Puedes mejorar el código sabiendo que los tests te avisarán
si algo deja de funcionar.
• Documentación viva: Un test bien escrito describe exactamente qué debe hacer el
código. Es la documentación más actualizada que existe.
• Detección temprana de bugs: Encuentras los errores inmediatamente, no cuando el
cliente los reporta en producción.
• CI/CD: Los tests se ejecutan automáticamente en cada push al repositorio. Nadie puede
subir código roto sin que el pipeline lo detecte.
IFCD0182 — Desarrollo Web con Java

1.3 Tipos de pruebas — La pirámide de testing

Tipo Qué prueba Velocidad Coste Cuántas hacer

Unitarias Una clase o método de forma aislada. Muy rápida (ms) Muy bajo Muchas — 70%
Sin BD, sin HTTP, sin Spring.

Integración Varias capas juntas: Service + Repository Media (segundos) Medio Algunas — 20%
+ BD real o en memoria.

End-to-End El sistema completo desde el cliente Lenta (minutos) Alto Pocas — 10%
hasta la BD.

👁 FÍJATE
Esta distribución se llama la Pirámide de Testing. La base (muchas pruebas unitarias) es barata y
rápida. La cima (pocas pruebas E2E) es cara y lenta. No inviertas la pirámide: no es eficiente tener
más E2E que unitarias.

2. JUnit 5 — Fundamentos de pruebas unitarias

2.1 La dependencia — spring-boot-starter-test


Spring Boot incluye todo lo necesario para testing en una sola dependencia. Ya viene en los proyectos
generados con Spring Initializr:

<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

ℹ NOTA
El scope test significa que esta dependencia solo existe en el entorno de pruebas, no se incluye en el
.jar de producción. Esta dependencia trae: JUnit 5, Mockito, AssertJ, Spring Test (MockMvc,
@SpringBootTest) y más.
IFCD0182 — Desarrollo Web con Java

2.2 Las anotaciones principales de JUnit 5

Anotación Para qué sirve Cuándo usar

@Test Marca un método como caso de prueba Siempre — en cada método de


prueba

@DisplayName Nombre descriptivo del test (se muestra en Cuando el nombre del método
informes) no es suficientemente claro

@BeforeEach Se ejecuta antes de cada prueba en la clase Para preparar el estado inicial
(inicializar objetos)

@AfterEach Se ejecuta después de cada prueba Para limpiar recursos

@BeforeAll Se ejecuta una sola vez antes de todos los Para setup costoso (conexión a
tests de la clase BD de pruebas)

@AfterAll Se ejecuta una sola vez al final de todos los Para liberar recursos
tests compartidos

@ParameterizedTest Ejecuta el test con diferentes conjuntos de Para probar el mismo


datos comportamiento con distintos
inputs

@ValueSource Proporciona valores simples a un Para iterar sobre una lista de


@ParameterizedTest valores primitivos

@MethodSource Proporciona datos complejos desde un Para parametrizar con objetos o


método estático múltiples parámetros

@Disabled Deshabilita temporalmente un test Cuando un test está en


construcción o hay un bug
conocido

@Nested Agrupa tests relacionados en clases anidadas Para organizar tests de una clase
compleja

@ExtendWith Extiende JUnit con funcionalidades externas @ExtendWith(MockitoExtension.


class) para usar Mockito

2.3 El patrón AAA — Arrange, Act, Assert


Todo test bien escrito sigue la misma estructura de tres partes. Es el patrón más importante del testing:

Fase Nombre Qué haces

ARRANGE Preparar Configura el estado inicial: crea objetos, define el


comportamiento de los mocks, prepara los datos de
entrada.

ACT Actuar Ejecuta el método que estás probando. Una sola llamada
— si necesitas más, probablemente es más de una prueba.

ASSERT Verificar Comprueba que el resultado es el esperado usando


assertions (assertEquals, assertTrue, assertThrows...).
IFCD0182 — Desarrollo Web con Java

2.4 Tu primer test con JUnit 5

La clase que vamos a probar — CalculadoraService


@Service
public class CalculadoraService {

public double sumar(double a, double b) { return a + b; }


public double restar(double a, double b) { return a - b; }
public double multiplicar(double a, double b) { return a * b; }

public double dividir(double a, double b) {


if (b == 0) throw new ArithmeticException("División por cero no permitida");
return a / b;
}
}

La clase de test — CalculadoraServiceTest


import [Link].*;
import static [Link].*;

// Las clases de test NO necesitan @SpringBootTest si no usan Spring


// Esto hace los tests más rápidos
class CalculadoraServiceTest {

private CalculadoraService calc;

// @BeforeEach: se ejecuta antes de cada @Test


@BeforeEach
void setUp() {
// ARRANGE compartido: creamos la instancia antes de cada prueba
calc = new CalculadoraService();
}

@Test
@DisplayName("Suma dos números positivos correctamente")
void sumar_dosPositivos_devuelveResultadoCorrecto() {
// ARRANGE: preparamos los datos de entrada
double a = 5.0, b = 3.0;

// ACT: ejecutamos el método a probar


double resultado = [Link](a, b);

// ASSERT: verificamos que el resultado es el esperado


assertEquals(8.0, resultado, "5 + 3 debe ser 8");
}

@Test
@DisplayName("Dividir por cero lanza ArithmeticException")
void dividir_denominadorCero_lanzaExcepcion() {
// assertThrows verifica que se lanza la excepción esperada
IFCD0182 — Desarrollo Web con Java
ArithmeticException ex = assertThrows(
[Link],
() -> [Link](10.0, 0.0)
);
// También verificamos el mensaje de la excepción
assertEquals("División por cero no permitida", [Link]());
}

@Test
void sumar_numeroNegativo_devuelveResultadoCorrecto() {
assertEquals(-2.0, [Link](-5.0, 3.0));
}

// @ParameterizedTest: ejecuta el test con múltiples conjuntos de datos


@ParameterizedTest
@DisplayName("Sumar con diferentes valores")
@MethodSource("datosParaSumar")
void sumar_variosValores_resultadoCorrecto(double a, double b, double esperado) {
assertEquals(esperado, [Link](a, b), 0.001);
}

static Stream<Arguments> datosParaSumar() {


return [Link](
[Link](1.0, 2.0, 3.0),
[Link](-1.0, 1.0, 0.0),
[Link](0.0, 0.0, 0.0),
[Link](100.0, -50.0, 50.0)
);
}
}

2.5 Las assertions más importantes

Assertion Qué verifica Ejemplo

assertEquals(esperado, actual) Que dos valores son iguales assertEquals(8.0, [Link](5,3))

assertNotEquals(no_esperado, actual) Que dos valores son distintos assertNotEquals(0, [Link]())

assertTrue(condicion) Que una condición es verdadera assertTrue(resultado > 0)

assertFalse(condicion) Que una condición es falsa assertFalse([Link]())

assertNull(objeto) Que el objeto es null assertNull([Link](999))

assertNotNull(objeto) Que el objeto no es null assertNotNull([Link](dto))

assertThrows([Link], lambda) Que una operación lanza la assertThrows([Link], () ->


excepción esperada dividir(1,0))

assertDoesNotThrow(lambda) Que una operación NO lanza assertDoesNotThrow(() ->


ninguna excepción [Link](valid))

assertAll(lambdas...) Ejecuta múltiples assertions y assertAll(() -> assertX, () -> assertY)


reporta todos los fallos
IFCD0182 — Desarrollo Web con Java

3. Mockito — Aislar el código con mocks

3.1 ¿Qué es un mock y para qué sirve?


Cuando pruebas un servicio que depende de un repositorio de base de datos, no quieres que la prueba haga
consultas reales a la BD. Eso la haría lenta, dependiente de datos externos y difícil de controlar.

Un mock es un objeto falso que simula el comportamiento de una dependencia real. Le dices exactamente
qué debe devolver cuando se le llame con ciertos parámetros. El código que estás probando no sabe la
diferencia.

👁 FÍJATE
Analogía para entender los mocks:
Imagina que estás probando a un cocinero. No necesitas un restaurante real ni clientes reales. Le das
ingredientes de prueba (los datos de arrange), ejecutas la receta (act) y verificas que el plato
resultante es el correcto (assert).

Mockito te da los 'ingredientes de prueba': objetos falsos que devuelven exactamente lo que
necesitas para tu prueba, sin necesitar la BD ni los servicios reales.

3.2 Las tres anotaciones de Mockito

Anotación Qué crea Cuándo usar

@Mock Un objeto completamente falso de una Para dependencias que quieres


clase/interfaz controlar completamente

@InjectMocks La instancia real de la clase bajo prueba, con los Para la clase que estás probando —
@Mock inyectados siempre una por test

@Spy Un objeto real con la capacidad de espiar Cuando quieres el comportamiento


llamadas real pero verificar que se llamó a
ciertos métodos
IFCD0182 — Desarrollo Web con Java

3.3 Prueba unitaria completa con Mockito


El servicio que vamos a probar
@Service
public class ProductoService {
private final ProductoRepository repo;
public ProductoService(ProductoRepository repo) { [Link] = repo; }

public List<Producto> findAll() { return [Link](); }

public Producto findById(Long id) {


return [Link](id)
.orElseThrow(() -> new RuntimeException("Producto no encontrado: " +
id));
}

public Producto save(ProductoCreateDTO dto) {


if ([Link]() < 0)
throw new IllegalArgumentException("El precio no puede ser negativo");
Producto p = new Producto([Link](), [Link](), [Link]());
return [Link](p);
}
}

La clase de test con Mockito


import [Link].*;
import [Link];
import [Link].*;
import [Link];
import static [Link].*;
import static [Link].*;

// @ExtendWith([Link]): activa Mockito en esta clase de test


@ExtendWith([Link])
class ProductoServiceTest {

// @Mock: Mockito crea un ProductoRepository falso


@Mock
private ProductoRepository repo;

// @InjectMocks: Mockito crea el ProductoService real inyectando el @Mock


@InjectMocks
private ProductoService service;

// ── TEST 1: findAll devuelve la lista del repositorio ──────────


@Test
@DisplayName("findAll devuelve todos los productos del repositorio")
void findAll_repositorioConProductos_devuelveLista() {
// ARRANGE: definimos qué devuelve el mock cuando se le llame
List<Producto> listaMock = [Link](
new Producto("Teclado", 89.99, 10),
new Producto("Monitor", 299.00, 5)
);
when([Link]()).thenReturn(listaMock);
IFCD0182 — Desarrollo Web con Java

// ACT
List<Producto> resultado = [Link]();

// ASSERT
assertEquals(2, [Link]());
assertEquals("Teclado", [Link](0).getNombre());
// Verificamos que el [Link]() fue llamado exactamente una vez
verify(repo, times(1)).findAll();
}

// ── TEST 2: findById con ID inexistente lanza excepción ─────────


@Test
@DisplayName("findById con ID inexistente lanza RuntimeException")
void findById_idNoExiste_lanzaExcepcion() {
// ARRANGE: el repo devuelve Optional vacío para cualquier ID
when([Link](anyLong())).thenReturn([Link]());

// ACT + ASSERT
RuntimeException ex = assertThrows(
[Link],
() -> [Link](99L)
);
assertTrue([Link]().contains("99"));
}

// ── TEST 3: save con precio negativo lanza excepción ────────────


@Test
@DisplayName("save con precio negativo lanza IllegalArgumentException")
void save_precioNegativo_lanzaExcepcion() {
ProductoCreateDTO dto = new ProductoCreateDTO("Test", -10.0, 5);

assertThrows([Link], () -> [Link](dto));


// Verificamos que el [Link]() NUNCA fue llamado (la excepción salió
antes)
verify(repo, never()).save(any());
}

// ── TEST 4: save correcto llama al repositorio y devuelve el producto


@Test
@DisplayName("save correcto guarda el producto y lo devuelve")
void save_datosCorrectos_guardaYDevuelveProducto() {
ProductoCreateDTO dto = new ProductoCreateDTO("Ratón", 29.99, 20);
Producto esperado = new Producto("Ratón", 29.99, 20);
[Link](1L);

// any([Link]): acepta cualquier Producto como argumento


when([Link](any([Link]))).thenReturn(esperado);

Producto resultado = [Link](dto);

assertNotNull(resultado);
assertEquals("Ratón", [Link]());
assertEquals(29.99, [Link](), 0.001);
verify(repo, times(1)).save(any([Link]));
IFCD0182 — Desarrollo Web con Java
}
}

3.4 Los métodos Mockito más usados

Método Qué hace Ejemplo

when([Link]()).thenReturn(valor Define qué devuelve el mock when([Link]()).thenRetu


) cuando se llama al método rn(lista)

when([Link]()).thenThrow(ex) Define que el mock lanza una when([Link](any())).thenT


excepción hrow(new
RuntimeException())

verify(mock).metodo() Verifica que el método fue llamado verify(repo).save(any())


exactamente una vez

verify(mock, times(n)).metodo() Verifica que fue llamado n veces verify(repo, times(2)).findAll()

verify(mock, never()).metodo() Verifica que nunca fue llamado verify(repo,


never()).deleteAll()

any() Argumentmatcher: acepta when([Link](any(Producto


cualquier objeto .class)))

eq(valor) Argumentmatcher: acepta verify(repo).findById(eq(1L))


exactamente ese valor

anyLong() / anyString() Argumentmatchers para tipos when([Link](anyLong()


primitivos y String ))

doNothing().when(mock).metodo() Para métodos void — no hace nada doNothing().when(repo).delet


cuando se llama eById(anyLong())

🤖 CON AYUDA DE LA IA
Aprende Mockito con IA como tutor de tests
Cuando no sabes cómo mockear un caso concreto, describe el problema:

"Tengo un servicio Spring Boot que tiene un método enviarEmail() que llama a un JavaMailSender
externo. Cuando hago los tests, no quiero que se envíen emails reales. ¿Cómo mockeo el
JavaMailSender con Mockito para que el test verifique que se intentó enviar el email sin enviarlo
realmente? Muéstrame el test completo con el patrón AAA."
IFCD0182 — Desarrollo Web con Java

4. Pruebas de integración con @SpringBootTest

4.1 Cuándo usar @SpringBootTest


Las pruebas unitarias prueban clases de forma aislada. Las pruebas de integración prueban que las capas
funcionan correctamente juntas: Service + Repository + Base de datos en memoria.

Aspecto @ExtendWith([Link]) @SpringBootTest

Qué arranca Solo la clase bajo prueba con mocks El contexto completo de Spring (todos los
beans)

Velocidad Muy rápido (milisegundos) Más lento (segundos — arranca Spring)

BD No hay BD — todo es mock H2 en memoria o BD de test

Cuándo usar Para lógica de negocio pura sin Para probar que las capas se integran
dependencias reales correctamente

Costo Muy bajo Medio — arrancar Spring tiene coste

4.2 Test de integración del repositorio

import [Link].*;
import [Link];

// @DataJpaTest: arranca solo la capa JPA con H2 en memoria


// Mucho más rápido que @SpringBootTest completo
@DataJpaTest
class ProductoRepositoryTest {

@Autowired
private ProductoRepository repo;

@Test
@DisplayName("Guardar y recuperar un producto por ID")
void save_productoValido_seGuardaYRecupera() {
// ARRANGE
Producto p = new Producto("Teclado", 89.99, 10);

// ACT
Producto guardado = [Link](p);
Optional<Producto> recuperado = [Link]([Link]());

// ASSERT
assertTrue([Link]());
assertEquals("Teclado", [Link]().getNombre());
assertEquals(89.99, [Link]().getPrecio(), 0.001);
}
IFCD0182 — Desarrollo Web con Java
@Test
@DisplayName("findAll devuelve todos los productos guardados")
void findAll_variosProductos_devuelveTodos() {
[Link](new Producto("Producto 1", 10.0, 5));
[Link](new Producto("Producto 2", 20.0, 3));
[Link](new Producto("Producto 3", 30.0, 8));

List<Producto> todos = [Link]();

assertEquals(3, [Link]());
}
}

✔ BUENAS PRÁCTICAS
@DataJpaTest es mucho más rápido que @SpringBootTest porque solo arranca la capa de
persistencia JPA, sin controladores, sin seguridad, sin el resto de beans. Úsalo siempre que solo
necesites probar repositorios.
IFCD0182 — Desarrollo Web con Java

5. Probar controladores REST con MockMvc

5.1 ¿Qué es MockMvc?


MockMvc es una herramienta de Spring Test que simula peticiones HTTP a tus controladores sin necesidad
de arrancar un servidor real. Puedes probar GET, POST, PUT, DELETE, verificar el código de respuesta, las
cabeceras y el cuerpo JSON, todo en memoria.

5.2 @WebMvcTest — El slice de test para controladores


@WebMvcTest arranca solo la capa web de Spring: los controladores, filtros y configuración MVC, pero no
los servicios ni los repositorios. Las dependencias del controlador deben ser mockeadas con @MockBean:

import [Link].*;
import [Link].*;
import [Link];
import [Link];
import [Link];
import static [Link].*;
import static [Link].*;
import static [Link].*;

// @WebMvcTest: arranca solo la capa web para ProductoController


@WebMvcTest([Link])
class ProductoControllerTest {

@Autowired
// MockMvc: el cliente HTTP simulado para las pruebas
private MockMvc mockMvc;

// @MockBean: crea un mock de Spring para inyectar en el contexto


// Diferencia con @Mock de Mockito: este se registra en el contexto de Spring
@MockBean
private ProductoService service;

@Autowired
private ObjectMapper objectMapper; // Para serializar/deserializar JSON

// ── TEST 1: GET /api/productos devuelve 200 con la lista ─────────


@Test
@DisplayName("GET /api/productos devuelve 200 y la lista de productos")
void getAll_hayProductos_devuelve200ConLista() throws Exception {
// ARRANGE
List<ProductoResponseDTO> productos = [Link](
new ProductoResponseDTO(1L, "Teclado", 89.99, 10),
new ProductoResponseDTO(2L, "Monitor", 299.00, 5)
);
IFCD0182 — Desarrollo Web con Java
when([Link]()).thenReturn(productos);

// ACT + ASSERT: perform() ejecuta la petición, andExpect() verifica


[Link](get("/api/productos")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
// jsonPath: verifica campos del JSON de respuesta
.andExpect(jsonPath("$.length()").value(2))
.andExpect(jsonPath("$[0].nombre").value("Teclado"))
.andExpect(jsonPath("$[1].precio").value(299.00));
}

// ── TEST 2: GET /api/productos/{id} con ID inexistente devuelve 404


@Test
@DisplayName("GET /api/productos/99 con ID inexistente devuelve 404")
void getById_idNoExiste_devuelve404() throws Exception {
when([Link](99L))
.thenThrow(new RuntimeException("No encontrado"));

[Link](get("/api/productos/99"))
.andExpect(status().isNotFound());
}

// ── TEST 3: POST /api/productos con datos válidos devuelve 201 ────


@Test
@DisplayName("POST /api/productos con datos válidos crea el producto y devuelve
201")
void crear_datosValidos_devuelve201ConProducto() throws Exception {
// ARRANGE
ProductoCreateDTO dto = new ProductoCreateDTO("Ratón", 29.99, 20);
ProductoResponseDTO creado = new ProductoResponseDTO(3L, "Ratón", 29.99, 20);
when([Link](any())).thenReturn(creado);

// Convertimos el DTO a JSON para el cuerpo de la petición


String dtoJson = [Link](dto);

[Link](post("/api/productos")
.contentType(MediaType.APPLICATION_JSON)
.content(dtoJson))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(3L))
.andExpect(jsonPath("$.nombre").value("Ratón"));
}

// ── TEST 4: POST con datos inválidos devuelve 400 ───────────────


@Test
@DisplayName("POST /api/productos con nombre vacío devuelve 400")
void crear_nombreVacio_devuelve400() throws Exception {
ProductoCreateDTO dto = new ProductoCreateDTO("", 29.99, 20);
String dtoJson = [Link](dto);

[Link](post("/api/productos")
.contentType(MediaType.APPLICATION_JSON)
.content(dtoJson))
.andExpect(status().isBadRequest());
IFCD0182 — Desarrollo Web con Java
}
}

5.3 Los matchers de MockMvc más usados

Matcher Qué verifica

status().isOk() Código de respuesta 200 OK

status().isCreated() Código de respuesta 201 Created

status().isNotFound() Código de respuesta 404 Not Found

status().isBadRequest() Código de respuesta 400 Bad Request

status().isUnauthorized() Código de respuesta 401 Unauthorized

status().isForbidden() Código de respuesta 403 Forbidden

jsonPath("$.campo").value(v) Que el campo del JSON tiene ese valor

jsonPath("$.length()").value(n) Que el array JSON tiene n elementos

content().contentType(MT.APPLICATION_JSO Que el Content-Type es JSON


N)

header().string("nombre", "valor") Que una cabecera tiene ese valor


IFCD0182 — Desarrollo Web con Java

6. Cobertura de código con JaCoCo

6.1 ¿Qué es la cobertura de código?


La cobertura (code coverage) mide qué porcentaje del código fuente está siendo ejecutado por las pruebas.
JaCoCo (Java Code Coverage) es la herramienta estándar para medirla en proyectos Java con Maven.

⚠ ATENCIÓN
Una cobertura alta no garantiza buenos tests:
Puedes tener un 100% de cobertura sin ninguna assertion significativa. La cobertura mide que el
código se ejecutó, no que se verificó correctamente. Es una métrica útil para encontrar código sin
probar, no para garantizar calidad. Apunta a 70-80% con assertions significativas.

6.2 Configurar JaCoCo en Maven

<build>
<plugins>
<plugin>
<groupId>[Link]</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals><goal>report</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Para generar el informe de cobertura ejecuta en la terminal:

mvn test

El informe HTML se genera en target/site/jacoco/[Link]. Puedes abrirlo en el navegador para ver qué
líneas están cubiertas (verde) y cuáles no (rojo).
IFCD0182 — Desarrollo Web con Java

7. Errores frecuentes en testing

Error / Síntoma Causa habitual Solución

NullPointerException en el test Se está usando @Mock pero no Añade


— la dependencia mockeada es @ExtendWith(MockitoExtension.c @ExtendWith([Link])
null lass), o se usa @MockBean fuera para tests con @Mock. Usa
de un test de Spring @MockBean solo con @WebMvcTest o
@SpringBootTest

El mock devuelve null aunque El argumento pasado al método Usa anyLong() / any() para aceptar
hayas configurado no coincide con el matcher — ej: cualquier valor, o verifica que el valor
when().thenReturn() pasas id=1L pero el mock espera exacto coincide con el configurado en
eq(2L) when()

UnnecessaryStubbingException: Configuraste un Elimina el when() innecesario o usa


comportamiento mockeado que when().thenReturn() que el @MockitoSettings(strictness =
nunca se usa código nunca llega a ejecutar [Link]) si es intencional

El @WebMvcTest devuelve 403 @WebMvcTest incluye la Añade


aunque no tengas Spring configuración de seguridad por @AutoConfigureMockMvc(addFilters=f
Security defecto si Spring Security está en alse) o crea una configuración de
el classpath seguridad para tests que permita todo

Los tests pasan localmente pero El test depende de datos Cada test debe ser independiente. Usa
fallan en el pipeline CI persistidos por otro test (los tests @BeforeEach para preparar y
no deben depender entre sí) @AfterEach para limpiar el estado. Con
@DataJpaTest cada test se ejecuta en
una transacción que se revierte.

jsonPath() no encuentra el El nombre del campo en jsonPath Verifica el JSON real con .andDo(print())
campo aunque el JSON lo tiene no coincide exactamente para ver la respuesta completa.
(mayúsculas, typo) o el campo es Configura Jackson para incluir campos
null y no se serializa null si es necesario.
IFCD0182 — Desarrollo Web con Java

8. Buenas prácticas de testing

✔ BUENAS PRÁCTICAS
Escribe tests que valgan la pena:
• Un test, un comportamiento: Cada método de test prueba una sola cosa. Si el nombre
del test necesita 'y' en el medio, probablemente son dos tests.
• Nombres descriptivos: nombreMetodo_condición_resultadoEsperado. Ej:
findById_idNoExiste_lanzaExcepcion. Al fallar, sabes exactamente qué está roto.
• Tests independientes entre sí: Ningún test debe depender del resultado de otro. Cada
uno prepara su propio estado en @BeforeEach.
• Prueba el comportamiento, no la implementación: No pruebes que se llamó a un
método privado. Prueba que el resultado observable es correcto.
• Prueba los casos límite: Cero, null, lista vacía, valor máximo, valor mínimo, strings vacíos.
Los bugs suelen esconderse en los bordes.
• Prueba los caminos de error: ¿Qué pasa cuando algo falla? El código de manejo de
errores también necesita pruebas.
• Mantén los tests rápidos: Un test lento no se ejecuta. Si @SpringBootTest es necesario,
minimiza el número de pruebas que lo usan.
• No pruebes getters y setters triviales: No tienen lógica. Los tests deben probar lógica de
negocio, no wiring de datos.
IFCD0182 — Desarrollo Web con Java

9. Actividad práctica guiada

Título Suite de tests completa para el servicio y controlador de libros

Duración 75 minutos

Objetivo Escribir una suite de tests que cubra el servicio de libros con Mockito y el controlador
con MockMvc, verificando los casos correctos y los de error

Nivel Guiado — el formador explica cada test antes de que lo implementes

Enunciado
Usa el servicio de libros (API REST del Módulo 2 o el de la Unidad 1 del Módulo 3) y escribe la suite de tests
completa.

1 LibroServiceTest — 5 tests unitarios con Mockito


Test 1: findAll con repositorio con libros → devuelve lista correcta.

Test 2: findById con ID existente → devuelve el libro.

Test 3: findById con ID inexistente → lanza excepción con el ID en el mensaje.

Test 4: save con ISBN duplicado → lanza IsbnDuplicadoException y nunca llama a [Link]().

Test 5: save con datos válidos → llama a [Link]() exactamente una vez y devuelve el libro
guardado.

2 LibroControllerTest — 4 tests con MockMvc


Test 1: GET /api/v1/libros → 200 OK con la lista de libros en JSON.

Test 2: GET /api/v1/libros/{id} con ID existente → 200 OK con el libro.

Test 3: GET /api/v1/libros/{id} con ID inexistente → 404 Not Found.

Test 4: POST /api/v1/libros con título vacío (validación) → 400 Bad Request.

3 LibroRepositoryTest — 2 tests con @DataJpaTest


Test 1: save y findById → el libro guardado se recupera correctamente.

Test 2: findByIsbn → devuelve el libro correcto cuando el ISBN existe.

4 Ejecutar y medir cobertura


Ejecuta mvn test en la terminal. Todos los tests deben pasar (barra verde). Si JaCoCo está
configurado, abre el informe HTML y verifica qué líneas están cubiertas.
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Usa IA para revisar la calidad de tus tests

"Revisa estos tests de mi LibroService [pega el código de los tests]. ¿Están bien escritos según el
patrón AAA? ¿Hay casos límite que no estoy probando? ¿Las assertions verifican el
comportamiento real o son triviales? ¿Hay algún test redundante que pueda eliminar?"
La IA puede señalar tests que no aportan valor real (como probar solo getters) y sugerir casos que
has olvidado probar (null, lista vacía, valores límite).
IFCD0182 — Desarrollo Web con Java

10. Actividad práctica autónoma

Título Suite de tests completa para el sistema de empleados con cobertura documentada

Duración 90 minutos

Objetivo Desarrollar autónomamente una suite de tests que cubra servicio, controlador y
repositorio con casos correctos, casos de error y tests parametrizados

Nivel Autónomo — JUnit 5, Mockito, Spring Test e IA disponibles

Entrega Proyecto con los tests + captura del informe JaCoCo + [Link] con los casos elegidos
y por qué

Enunciado
Escribe una suite de tests completa para el sistema de gestión de empleados que hayas desarrollado en
unidades anteriores (o uno nuevo si es necesario).

Tests requeridos
• EmpleadoServiceTest (mínimo 6 tests con Mockito): findAll con lista vacía, findById exitoso, findById
fallido con mensaje correcto, save con email duplicado (no llama a repo), save con datos válidos,
buscarPorDepartamento con resultados y sin resultados.
• EmpleadoControllerTest (mínimo 5 tests con MockMvc): GET lista 200 OK, GET por ID 200 OK, GET
por ID 404, POST válido 201 Created con JSON correcto, POST inválido (nombre vacío) 400 Bad
Request.
• EmpleadoRepositoryTest (mínimo 3 tests con @DataJpaTest): save y recover, findByEmail existente,
findByDepartamento con múltiples resultados.
• Test parametrizado con @ParameterizedTest: Al menos un test que verifique la validación de salario
con varios valores (negativo, cero, positivo pequeño, positivo grande).

[Link] obligatorio
Incluye un archivo con:

• La lista de todos los tests con una frase que describa qué comportamiento prueban.
• Qué casos límite elegiste probar y por qué.
• El porcentaje de cobertura obtenido (captura del informe JaCoCo).
• Al menos un caso que pensabas probar pero decidiste no incluir, con la justificación.
IFCD0182 — Desarrollo Web con Java

Criterios de evaluación
Criterio Descripción Peso

Tests de servicio 6+ tests unitarios con Mockito correctamente 25%


escritos, AAA claro

Tests de controlador 5+ tests con MockMvc verificando códigos HTTP y 25%


JSON

Tests de repositorio 3+ tests con @DataJpaTest, independientes entre sí 15%

Test parametrizado Al menos uno funcional con @ParameterizedTest 10%

Todos los tests pasan mvn test completo en verde sin fallos 15%

[Link] Razonamiento claro sobre qué se prueba y por qué 10%

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir a la IA que sugiera casos límite que no hayas considerado, preguntar
cómo mockear un caso concreto, pedir revisión de si un test sigue el patrón AAA.
• No permitido: generar la clase de test completa o la mayoría de los métodos de test.
• Obligatorio: el [Link] debe estar escrito por el alumno. Muestra qué has aprendido
sobre qué vale la pena probar y por qué.
IFCD0182 — Desarrollo Web con Java

11. Resumen de la unidad

Concepto Lo esencial

Tests unitarios Prueban una clase aislada. Sin Spring, sin BD. Con Mockito para simular
dependencias.

Tests de integración Prueban varias capas juntas. @DataJpaTest para repositorios, @SpringBootTest
para todo.

Tests de controlador Prueban la capa web. @WebMvcTest + MockMvc para simular peticiones HTTP.

Pirámide de testing Muchas unitarias (70%), algunas integración (20%), pocas E2E (10%).

Patrón AAA Arrange (preparar), Act (ejecutar), Assert (verificar). Siempre en este orden.

@ExtendWith(Mockito) Activa Mockito en la clase de test. Necesario para @Mock y @InjectMocks.

@Mock Crea un objeto falso de una dependencia para controlar su comportamiento.

@InjectMocks Crea la instancia real de la clase bajo prueba con los @Mock inyectados.

@MockBean Mock de Spring para contexto de Spring Test (@WebMvcTest,


@SpringBootTest).

when().thenReturn() Define qué devuelve el mock cuando se llama a cierto método.

verify() Verifica que el mock fue llamado (o no) con ciertos argumentos.

MockMvc Cliente HTTP simulado para probar controladores sin servidor real.

jsonPath() Verifica campos específicos del JSON de respuesta con expresiones JSONPath.

@DataJpaTest Arranca solo la capa JPA con H2 en memoria. Rápido para probar repositorios.

JaCoCo Herramienta de cobertura de código. El informe muestra qué líneas se


ejecutaron.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
1. ¿Cuál es la diferencia entre @Mock y @MockBean? ¿Cuándo usas cada uno?
2. ¿Qué significa el patrón AAA y cuál es la fase más importante?
3. ¿Por qué verify(repo, never()).save(any()) es una assertion válida y útil?
4. ¿Qué diferencia hay entre @WebMvcTest y @SpringBootTest? ¿Cuál es más rápido?
5. ¿Por qué una cobertura del 100% no garantiza que el código funcione correctamente?
6. ¿Qué hace when([Link](anyLong())).thenReturn([Link]()) exactamente?
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Prompt de entrevista técnica — Testing:

"Actúa como entrevistador técnico para un puesto de desarrollador Java con Spring Boot. Hazme
7 preguntas sobre testing: diferencia pruebas unitarias vs integración, patrón AAA, qué es un
mock y para qué sirve, diferencia @Mock vs @MockBean, cómo probar un controlador REST, qué
es la cobertura de código y por qué no es suficiente con tener alta cobertura. Corrige mis
respuestas."

📊 NOTA DE MERCADO LABORAL


Lo que viene en la Unidad 4:
La Unidad 4 introduce CI/CD con Maven, Gradle, Jenkins y GitHub Actions: cómo automatizar la
compilación, los tests y el despliegue de tu aplicación. Los tests que has escrito en esta unidad son el
corazón de cualquier pipeline CI/CD — sin ellos, el pipeline no puede garantizar que el código que se
despliega funciona.
IFCD0182 — Desarrollo Web con Java

MÓDULO 3 — Seguridad, Testing y DevOps en Aplicaciones Java


UNIDAD 4

CI/CD — Integración y despliegue continuo


Maven, Gradle, Jenkins y GitHub Actions: automatiza la compilación, los tests y el
despliegue de tu aplicación

Duración estimada 8 horas — unidad extendida con enfoque muy práctico

Módulo 3 — Seguridad, Testing y DevOps en Aplicaciones Java

Unidad anterior Unidad 3 — Testing con JUnit 5 y Mockito

Nivel de entrada Tests automatizados funcionando. Proyecto Spring Boot completo con Maven.

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Automatizar la compilación, los tests y el despliegue de tu aplicación con pipelines
CI/CD profesionales

🔗 CONEXIÓN CON LO ANTERIOR


La conexión con las unidades anteriores:
En la Unidad 3 escribiste tests automáticos con JUnit y Mockito. Esos tests son el corazón de CI/CD:
el pipeline los ejecuta automáticamente en cada push al repositorio. Si los tests pasan, el código
puede avanzar al siguiente paso. Si fallan, el pipeline se detiene y alerta al equipo.

Maven (que llevas usando desde el Módulo 1) es la herramienta que orquesta todo: compilar,
ejecutar tests, empaquetar y desplegar. Esta unidad te enseña a automatizarlo.

📊 NOTA DE MERCADO LABORAL


CI/CD en el mercado:
CI/CD es una de las competencias más valoradas en el mercado DevOps y backend. El perfil
'desarrollador que sabe configurar pipelines' es mucho más cotizado que el que solo escribe código.
GitHub Actions en particular es una habilidad concreta que aparece en muchísimas ofertas de
trabajo Java.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué es CI/CD y qué problema resuelve en el desarrollo profesional.
• Usar Maven para compilar, testear y empaquetar un proyecto Spring Boot.
IFCD0182 — Desarrollo Web con Java

• Comparar Maven y Gradle y entender cuándo usar cada uno.


• Crear un pipeline CI/CD completo con GitHub Actions.
• Ejecutar tests automáticamente en cada push al repositorio.
• Generar el .jar ejecutable de la aplicación en el pipeline.
• Desplegar automáticamente en un entorno de pruebas.
• Publicar el informe de cobertura de tests como artefacto del pipeline.

1. ¿Qué es CI/CD y por qué existe?

1.1 El problema que CI/CD resuelve


Imagina un equipo de 5 desarrolladores trabajando en el mismo proyecto durante dos semanas. Cada uno
trabaja en su rama, en su ordenador. Al final de las dos semanas intentan juntar el código. El resultado se
llama 'integration hell': conflictos, código que no compila, tests que fallan, bugs que aparecen de la
interacción entre partes.

CI/CD resuelve este problema automatizando la integración y el despliegue de forma continua, en pequeños
pasos frecuentes en lugar de grandes batches poco frecuentes.

Concepto Qué significa Qué automatiza

CI — Continuous Integración Continua En cada push al repositorio: compilar el código,


Integration ejecutar todos los tests, verificar que no se ha
roto nada

CD — Continuous Delivery Entrega Continua Si CI pasa: generar el artefacto desplegable (.jar,


imagen Docker) y dejarlo listo para desplegar
manualmente

CD — Continuous Despliegue Continuo Si CI pasa: desplegar automáticamente en


Deployment producción sin intervención humana

👁 FÍJATE
En la práctica, la mayoría de equipos implementan CI y Continuous Delivery. El Continuous
Deployment total (despliegue automático a producción sin revisión humana) solo se usa cuando la
cobertura de tests y los procesos de revisión son muy maduros.
IFCD0182 — Desarrollo Web con Java

1.2 El flujo de un pipeline CI/CD típico

1 Desarrollador hace push al repositorio Git


El desarrollador completa una funcionalidad, hace commit y push a su rama. O hace
merge/pull request a la rama principal.

2 El pipeline se dispara automáticamente


GitHub Actions, Jenkins u otra herramienta detecta el push y arranca el pipeline. Sin
intervención humana.

3 Compilar el proyecto
mvn compile o gradle build. Si el código no compila, el pipeline falla inmediatamente y notifica
al desarrollador.

4 Ejecutar todos los tests


mvn test. Si algún test falla, el pipeline se detiene. Nadie puede hacer merge de código que
rompe los tests.

5 Análisis de calidad (opcional)


JaCoCo para cobertura, SonarQube para análisis estático. El pipeline puede fallar si la
cobertura baja del umbral configurado.

6 Empaquetar la aplicación
mvn package. Genera el .jar ejecutable que se puede desplegar en cualquier servidor.

7 Desplegar (Continuous Delivery/Deployment)


Copia el .jar al servidor de staging, o construye y publica una imagen Docker, o despliega en la
nube. Automático si todos los pasos anteriores han pasado.
IFCD0182 — Desarrollo Web con Java

2. Maven — El gestor de proyectos Java

2.1 Qué hace Maven exactamente


Maven llevas usándolo desde el Módulo 1, pero probablemente solo para añadir dependencias al [Link] y
dejar que IntelliJ lo gestione. En un pipeline CI/CD Maven se ejecuta desde la línea de comandos y hace
mucho más.

2.2 El ciclo de vida de Maven


Maven organiza la construcción del proyecto en fases ordenadas. Cada fase ejecuta automáticamente todas
las anteriores:

Fase Comando Qué hace

validate mvn validate Verifica que el [Link] es correcto y el proyecto está bien
estructurado

compile mvn compile Compila el código fuente de src/main/java a .class en


target/classes

test mvn test Ejecuta los tests de src/test/java. Compila el código de test
primero.

package mvn package Empaqueta el código compilado en un .jar (o .war) en target/

verify mvn verify Ejecuta tests de integración y verifica que el paquete es correcto

install mvn install Instala el .jar en el repositorio local de Maven (~/.m2)

deploy mvn deploy Publica el .jar en un repositorio remoto (Nexus, Artifactory...)

💡 CONSEJO
En un pipeline CI/CD el comando habitual es mvn verify o mvn package -DskipTests (si los tests ya se
ejecutaron en un paso anterior separado). Para desarrollo local, mvn test es el más usado.

2.3 Comandos Maven más usados en CI/CD

# Compilar y ejecutar todos los tests


mvn test

# Generar el .jar saltando los tests (cuando los tests ya pasaron antes)
mvn package -DskipTests

# Compilar + tests + empaquetar + verificar en un solo comando


mvn verify
IFCD0182 — Desarrollo Web con Java

# Limpiar los artefactos anteriores antes de compilar


mvn clean package

# Ejecutar un test específico


mvn test -Dtest=ProductoServiceTest

# Ejecutar con perfil específico (dev, test, prod)


mvn package -P produccion

# Ver las dependencias del proyecto en árbol


mvn dependency:tree

2.4 El [Link] — Configuración esencial para CI/CD


Además de las dependencias, el [Link] tiene configuración importante para los pipelines:

<project>
<groupId>[Link]</groupId>
<artifactId>mi-app</artifactId>
<version>1.0.0</version>

<properties>
<[Link]>17</[Link]>
<!-- Codificación para evitar problemas en CI con caracteres especiales -->
<[Link]>UTF-8</[Link]>
</properties>

<build>
<plugins>
<!-- Plugin de Spring Boot: genera el .jar ejecutable con Tomcat embebido -->
<plugin>
<groupId>[Link]</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

<!-- Plugin de Maven Surefire: configura cómo se ejecutan los tests -->
<plugin>
<groupId>[Link]</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<!-- Genera informes XML de tests para los pipelines -->
<reportsDirectory>${[Link]}/surefire-
reports</reportsDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>
IFCD0182 — Desarrollo Web con Java

3. Gradle — La alternativa moderna a Maven

3.1 Maven vs Gradle — La comparativa honesta

Aspecto Maven Gradle

Configuración XML ([Link]) — verboso pero Groovy o Kotlin DSL — conciso y flexible
predecible

Rendimiento Reconstruye todo en cada ejecución Incremental: solo recompila lo que


cambió. 2-10x más rápido.

Curva aprendizaje Más sencillo para empezar — estructura Más flexible pero más complejo al
fija principio

Ecosistema Android No se usa en Android Es el gestor oficial de proyectos Android

Ecosistema Spring El estándar en Spring Initializr por Opción disponible, cada vez más usada
defecto en proyectos nuevos

Popularidad actual Muy alto — legacy y nuevos proyectos Creciendo — proyectos nuevos y
empresas grandes

CI/CD mvn verify / mvn package ./gradlew build / ./gradlew test

3.2 [Link] — La configuración básica


plugins {
id '[Link]' version '3.2.0'
id '[Link]-management' version '1.1.4'
id 'java'
}

group = '[Link]'
version = '1.0.0'

java {
sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
mavenCentral()
}

dependencies {
implementation '[Link]:spring-boot-starter-web'
implementation '[Link]:spring-boot-starter-security'
testImplementation '[Link]:spring-boot-starter-test'
}

[Link]('test') {
// Usa JUnit Platform (JUnit 5)
useJUnitPlatform()
}
IFCD0182 — Desarrollo Web con Java

3.3 Comandos Gradle equivalentes a Maven

Maven Gradle Qué hace

mvn compile ./gradlew compileJava Compilar el código

mvn test ./gradlew test Ejecutar los tests

mvn package ./gradlew bootJar Generar el .jar ejecutable de Spring Boot

mvn clean package ./gradlew clean build Limpiar y construir todo

mvn verify ./gradlew build Compilar + tests + empaquetar

mvn dependency:tree ./gradlew dependencies Ver árbol de dependencias

⚠ ATENCIÓN
¿Cuál usar en tu proyecto?
En este curso usamos Maven porque es el que genera Spring Initializr por defecto y es el más
extendido en el mercado Java actual. Si un proyecto en el que trabajas usa Gradle, los conceptos son
los mismos — solo cambia la sintaxis. Ambos se integran perfectamente con GitHub Actions y
Jenkins.
IFCD0182 — Desarrollo Web con Java

4. GitHub Actions — Tu primer pipeline CI/CD

4.1 ¿Qué es GitHub Actions?


GitHub Actions es la plataforma de CI/CD integrada en GitHub. Permite automatizar flujos de trabajo
directamente en tu repositorio: en cada push, pull request o en un horario programado, GitHub ejecuta un
conjunto de tareas que tú defines en archivos YAML.

La ventaja principal es que no necesitas instalar ni configurar ningún servidor adicional: GitHub proporciona
servidores virtuales (runners) que ejecutan tu pipeline en la nube.

📊 NOTA DE MERCADO LABORAL


GitHub Actions en el mercado:
GitHub Actions ha superado a Jenkins como la herramienta de CI/CD más usada en proyectos de
código abierto y en muchas empresas medianas. Para nuevos proyectos es la primera opción en la
mayoría de equipos. Conocerla bien es una ventaja competitiva real en entrevistas.

4.2 Conceptos clave de GitHub Actions

Concepto Descripción Ejemplo

Workflow El pipeline completo definido en un archivo .github/workflows/[Link]


YAML

Trigger Evento que dispara el workflow on: push, pull_request, schedule

Job Un conjunto de pasos que se ejecutan en el jobs: build-and-test


mismo runner

Step Una tarea individual dentro de un job - name: Ejecutar tests

Action Una acción reutilizable (de la comunidad o uses: actions/checkout@v4


propia)

Runner El servidor donde se ejecuta el job runs-on: ubuntu-latest

Artifact Archivo generado que se puede descargar del El .jar o el informe de tests
pipeline

Secret Variable de entorno cifrada para datos Contraseñas, tokens de API


sensibles
IFCD0182 — Desarrollo Web con Java

4.3 Tu primer workflow — CI para Spring Boot


Crea el archivo .github/workflows/[Link] en la raíz de tu repositorio:

# Nombre del workflow — aparece en la pestaña Actions de GitHub


name: CI — Spring Boot

# Cuándo se dispara este workflow


on:
push:
# En pushes a las ramas main y develop
branches: [ main, develop ]
pull_request:
# En pull requests hacia main
branches: [ main ]

jobs:
# Un único job que compila y testea
build-and-test:
# Tipo de máquina virtual donde corre el job
runs-on: ubuntu-latest

steps:
# 1. Descarga el código del repositorio
- name: Descargar código
uses: actions/checkout@v4

# 2. Instala el JDK 17 en el runner


- name: Configurar JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

# 3. Cachea las dependencias Maven para acelerar runs posteriores


- name: Cachear dependencias Maven
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ [Link] }}-maven-${{ hashFiles('**/[Link]') }}

# 4. Ejecuta la verificación completa: compilar + tests + empaquetar


- name: Compilar y ejecutar tests
run: mvn clean verify

# 5. Publica el informe de tests como artefacto descargable


- name: Publicar resultados de tests
uses: actions/upload-artifact@v4
if: always()
# 'if: always()' publica el informe incluso si los tests fallan
with:
name: test-results
path: target/surefire-reports/
IFCD0182 — Desarrollo Web con Java
# 6. Publica el .jar generado como artefacto
- name: Publicar .jar
uses: actions/upload-artifact@v4
if: success()
with:
name: app-jar
path: target/*.jar

✔ BUENAS PRÁCTICAS
Cómo funciona este workflow:
1. Haces push a main o develop, o abres un PR.
2. GitHub detecta el evento y arranca el job build-and-test en una VM ubuntu-latest.
3. La VM descarga tu código, instala Java 17, carga el caché de Maven y ejecuta mvn clean
verify.
4. Si los tests pasan, publica el informe y el .jar como artefactos descargables.
5. Si algo falla, el pipeline se marca en rojo y puedes ver exactamente qué paso falló.

4.4 Workflow más completo — Multi-stage con matrix


En proyectos reales es habitual probar en múltiples versiones de Java. La [Link] permite ejecutar el
mismo job en paralelo con diferentes configuraciones:

# Pipeline con tests en Java 17 y 21 en paralelo


name: CI Multi-version

on:
push:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
# matrix: ejecuta el job una vez por cada combinación de valores
matrix:
java: [ '17', '21' ]

steps:
- uses: actions/checkout@v4

- name: Setup Java ${{ [Link] }}


uses: actions/setup-java@v4
with:
# Usa el valor de la matrix en la configuración
java-version: ${{ [Link] }}
distribution: 'temurin'

- name: Tests
run: mvn test
IFCD0182 — Desarrollo Web con Java

4.5 Variables, secretos y entornos


Nunca incluyas contraseñas, tokens o claves en el archivo YAML. GitHub tiene un sistema de secretos
cifrados:

Configurar un secreto en GitHub


6. Ve a tu repositorio en GitHub.
7. Settings → Secrets and variables → Actions.
8. Haz clic en 'New repository secret'.
9. Nombre: DB_PASSWORD. Valor: tu contraseña.
10. Haz clic en 'Add secret'.

Usar el secreto en el workflow


- name: Ejecutar tests con BD
run: mvn test
env:
# ${{ [Link] }} referencia el secreto cifrado
SPRING_DATASOURCE_PASSWORD: ${{ secrets.DB_PASSWORD }}
SPRING_DATASOURCE_URL: ${{ secrets.DB_URL }}

🤖 CON AYUDA DE LA IA
Genera workflows con IA como punto de partida
El YAML de GitHub Actions tiene muchas opciones. La IA puede darte un punto de partida:

"Necesito un workflow de GitHub Actions para un proyecto Spring Boot con Maven que: 1) Se
dispare en push a main y en pull requests, 2) Use Java 17, 3) Ejecute los tests, 4) Genere el
informe de cobertura JaCoCo y lo publique, 5) Si los tests pasan en main, construya una imagen
Docker y la publique en GitHub Container Registry. Genera el archivo .yml completo con
comentarios explicando cada sección."
Analiza el YAML generado línea a línea antes de usarlo. Entiende qué hace cada sección — eso es lo
que te pedirán en una entrevista.
IFCD0182 — Desarrollo Web con Java

5. Jenkins — El servidor CI/CD clásico

5.1 ¿Qué es Jenkins y cuándo se usa?


Jenkins es el servidor de CI/CD de código abierto más veterano y más extendido en empresas grandes con
infraestructura propia. A diferencia de GitHub Actions, Jenkins se instala en tu propio servidor y tienes
control total sobre él.

Aspecto GitHub Actions Jenkins

Dónde corre En los servidores de GitHub (cloud) En tu propio servidor (on-premise o cloud
propio)

Configuración Archivo YAML en el repositorio Jenkinsfile en el repositorio o UI web

Instalación Nada — está integrado en GitHub Instalación y mantenimiento propios

Coste runners Gratuito para repos públicos, minutos Coste del servidor propio, sin límite de
limitados privados minutos

Ecosistema Miles de Actions del marketplace Más de 1800 plugins disponibles

Uso actual Predominante en nuevos proyectos Muy extendido en empresas con


infraestructura legacy

Curva aprendizaje Baja — solo YAML Media — interfaz web + Jenkinsfile +


administración

5.2 El Jenkinsfile — Pipeline declarativo


Igual que GitHub Actions usa archivos YAML, Jenkins usa un Jenkinsfile escrito en Groovy con sintaxis
declarativa. Se coloca en la raíz del repositorio:

pipeline {
// Dónde se ejecuta el pipeline
agent any

// Variables de entorno disponibles en todo el pipeline


environment {
JAVA_HOME = tool 'JDK-17'
MVN_HOME = tool 'Maven-3.9'
}

stages {
// ── STAGE 1: Compilar ──────────────────────────────────────────
stage('Compilar') {
steps {
sh 'mvn clean compile'
}
}
IFCD0182 — Desarrollo Web con Java
// ── STAGE 2: Tests unitarios ───────────────────────────────────
stage('Tests') {
steps {
sh 'mvn test'
}
post {
// Siempre publica el informe de tests (incluso si fallan)
always {
junit 'target/surefire-reports/*.xml'
}
}
}

// ── STAGE 3: Empaquetar ────────────────────────────────────────


stage('Empaquetar') {
steps {
sh 'mvn package -DskipTests'
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}

// ── STAGE 4: Desplegar en staging (solo en rama main) ──────────


stage('Desplegar en Staging') {
when {
branch 'main'
}
steps {
sh 'scp target/*.jar usuario@staging-server:/apps/'
sh 'ssh usuario@staging-server "systemctl restart mi-app"'
}
}
}

post {
// Notificación al equipo según el resultado
success {
echo 'Pipeline completado correctamente'
}
failure {
echo 'El pipeline ha fallado — revisar los logs'
}
}
}

ℹ NOTA
Para este módulo trabajaremos principalmente con GitHub Actions porque no requiere instalación y
es lo más relevante en el mercado actual. Jenkins es importante conocerlo conceptualmente porque
encontrarás muchos proyectos legacy que lo usan.
IFCD0182 — Desarrollo Web con Java

6. Docker en el pipeline CI/CD

6.1 Por qué Dockerizar la aplicación


El .jar generado por Maven funciona en cualquier máquina con Java instalado. Pero en entornos de
producción modernos se usa Docker: empaquetas la aplicación junto con su entorno (JVM, configuración) en
una imagen que se puede ejecutar de forma idéntica en cualquier servidor.

6.2 El Dockerfile para Spring Boot

# Imagen base con JDK 17


FROM eclipse-temurin:17-jre-alpine

# Directorio de trabajo dentro del contenedor


WORKDIR /app

# Copia el .jar generado por Maven al contenedor


# El nombre exacto depende de tu artifactId y version en [Link]
COPY target/[Link] [Link]

# Puerto que expone la aplicación


EXPOSE 8080

# Comando que se ejecuta al arrancar el contenedor


ENTRYPOINT ["java", "-jar", "[Link]"]

6.3 Construir y ejecutar la imagen

# Construir la imagen Docker


docker build -t mi-app:1.0.0 .

# Ejecutar el contenedor
docker run -p 8080:8080 mi-app:1.0.0

# Ejecutar con variables de entorno (para la configuración de BD, etc.)


docker run -p 8080:8080 \
-e SPRING_DATASOURCE_URL=jdbc:mysql://db-host:3306/mibd \
-e SPRING_DATASOURCE_PASSWORD=secreto \
mi-app:1.0.0
IFCD0182 — Desarrollo Web con Java

6.4 Añadir Docker al workflow de GitHub Actions

# Workflow completo: tests + build Docker + push a registry


name: CI/CD con Docker

on:
push:
branches: [ main ]

jobs:
build-test-docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

# 1. Compilar y ejecutar tests


- name: Tests
run: mvn clean verify

# 2. Iniciar sesión en GitHub Container Registry


- name: Login a GHCR
uses: docker/login-action@v3
with:
registry: [Link]
# GITHUB_TOKEN es un secreto automático de GitHub Actions
username: ${{ [Link] }}
password: ${{ secrets.GITHUB_TOKEN }}

# 3. Construir y publicar la imagen Docker


- name: Construir y publicar imagen
uses: docker/build-push-action@v5
with:
context: .
push: true
# Tag: [Link]/tuusuario/mi-app:sha_del_commit
tags: [Link]/${{ [Link] }}:${{ [Link] }}
IFCD0182 — Desarrollo Web con Java

7. Estrategias de despliegue en la nube

7.1 Opciones de despliegue


Una vez que tienes el .jar o la imagen Docker, puedes desplegarlo de múltiples formas. Estas son las más
comunes en el mercado:

Estrategia Descripción Herramientas Cuándo usar

Servidor propio (VPS) Copias el .jar al servidor y lo SSH, SCP, systemd Proyectos
arrancas manualmente o con pequeños,
systemd presupuesto
limitado

Platform as a Service La plataforma gestiona el servidor Railway, Render, Heroku, Startups, demos,
(PaaS) — tú solo subes el código [Link] proyectos
personales

Contenedores en la Subes la imagen Docker a un AWS ECS, Google Cloud Producción con
nube servicio gestionado Run, Azure Container volumen medio
Instances

Kubernetes Orquestación de contenedores AWS EKS, GKE, AKS, k3s Sistemas grandes,
para alta disponibilidad alta disponibilidad

7.2 Despliegue básico en Railway (gratuito para demos)


Railway es una plataforma PaaS que detecta automáticamente proyectos Spring Boot y los despliega sin
configuración. Es perfecta para demos y proyectos de prácticas:

11. Crea una cuenta en [Link].


12. Haz clic en 'New Project' → 'Deploy from GitHub repo'.
13. Selecciona tu repositorio Spring Boot.
14. Railway detecta el [Link] automáticamente y ejecuta mvn package.
15. En pocos minutos tu app está disponible en una URL pública.
16. Configura las variables de entorno en el panel de Railway (contraseñas, claves...).

✔ BUENAS PRÁCTICAS
Para el proyecto final del módulo usaremos Railway o Render como destino de despliegue. El
objetivo es que al acabar la unidad tengas un pipeline completo: push a GitHub → tests automáticos
→ imagen Docker → despliegue automático en la nube.
IFCD0182 — Desarrollo Web con Java

8. Monitorización y logging básico

8.1 Spring Boot Actuator — Salud de la aplicación


Spring Boot Actuator añade endpoints automáticos que informan sobre el estado de la aplicación. Son
usados por los sistemas de monitorización y por los pipelines para verificar que el despliegue fue correcto:

<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Configuración básica en [Link]


# Exponer los endpoints de salud y métricas
[Link]=health,info,metrics
[Link]-details=when-authorized

# Información de la aplicación
[Link]=Mi Aplicacion
[Link]=1.0.0

Endpoints disponibles
Endpoint URL Qué devuelve

/actuator/health GET /actuator/health Estado de la app: UP o DOWN. Incluye estado de


BD, cache...

/actuator/info GET /actuator/info Información configurada en


[Link]

/actuator/metrics GET /actuator/metrics Métricas: peticiones/seg, memoria, tiempo de


respuesta...

/actuator/env GET /actuator/env Variables de entorno activas (protegido por


defecto)

⚠ ATENCIÓN
Protege los endpoints de Actuator en producción:
Por defecto, /actuator/** está expuesto públicamente. En producción debes protegerlos con Spring
Security o limitar la exposición solo a /health. Añade a tu SecurityFilterChain:
.requestMatchers("/actuator/health").permitAll() y
.requestMatchers("/actuator/**").hasRole("ADMIN").
IFCD0182 — Desarrollo Web con Java

8.2 Logging con SLF4J y Logback


Spring Boot usa SLF4J como fachada de logging y Logback como implementación por defecto. El logging
correcto es fundamental para detectar problemas en producción:

import [Link];
import [Link];

@Service
public class ProductoService {

// El logger se crea una vez por clase


private static final Logger log = [Link]([Link]);

public Producto save(ProductoCreateDTO dto) {


// LOG: información sobre el flujo normal
[Link]("Guardando producto: {}", [Link]());

try {
Producto guardado = [Link]([Link](dto));
// LOG: confirmación de éxito con el ID generado
[Link]("Producto guardado correctamente con ID: {}", [Link]());
return guardado;
} catch (Exception e) {
// LOG: error con el stack trace completo
[Link]("Error al guardar producto {}: {}", [Link](),
[Link](), e);
throw e;
}
}
}

8.3 Niveles de log

Nivel Cuándo usarlo En producción

TRACE Información muy detallada para depuración Desactivado


profunda

DEBUG Información útil para depurar en desarrollo Desactivado

INFO Eventos importantes del flujo normal: inicio, Activado


peticiones, etc.

WARN Situaciones inesperadas pero no errores: Activado


reintentos, config...

ERROR Errores que impiden una operación pero no Activado + alertas


tumban la app

# [Link] — nivel de log por paquete


[Link]=INFO
[Link]=DEBUG
[Link]=WARN
IFCD0182 — Desarrollo Web con Java

9. Errores frecuentes en CI/CD

Error / Síntoma Causa habitual Solución

El workflow no se dispara El nombre del archivo no está en Verifica que el archivo está
después de hacer push .github/workflows/ o tiene exactamente en
extensión incorrecta .github/workflows/[Link]. GitHub
solo busca workflows en esa ruta.

Error: 'mvn: command not Maven no está en el PATH del Usa la action actions/setup-java que
found' en el runner runner o no se configuró instala Maven automáticamente.
correctamente Añade también 'cache: maven' para
acelerar.

Los tests pasan localmente pero El pipeline no tiene acceso a Usa H2 en memoria para tests.
fallan en GitHub Actions recursos locales: BD local, Configura secretos en GitHub para
archivos, variables de entorno variables de entorno. Verifica que no
hay rutas absolutas en el código.

El pipeline dura 10+ minutos No hay caché configurado para las Añade el step de actions/cache para
porque descarga Maven en cada dependencias ~/.m2 antes de ejecutar Maven. El
run segundo run será 5x más rápido.

Error al construir la imagen El .jar no se generó antes de Asegúrate de ejecutar mvn package o
Docker: 'No such file: intentar construir la imagen mvn verify ANTES del step de Docker
target/*.jar' build.

El endpoint /actuator/health Spring Boot no puede conectar Revisa los logs del servidor. El mensaje
devuelve DOWN en producción con la BD o algún servicio del que de error de health indica exactamente
depende qué servicio no responde.

Secretos de GitHub no llegan al El nombre del secreto en el YAML Los nombres de secretos son case-
workflow no coincide exactamente con el sensitive. DB_PASSWORD en Settings
definido en Settings debe ser ${{ secrets.DB_PASSWORD }}
en el YAML.
IFCD0182 — Desarrollo Web con Java

10. Buenas prácticas de CI/CD

✔ BUENAS PRÁCTICAS
Aplica estas prácticas desde tu primer pipeline:
• Haz commits pequeños y frecuentes: Un commit = un cambio coherente. Facilita
identificar exactamente qué rompió el pipeline.
• Nunca credentials en el YAML: Todas las contraseñas, tokens y claves van en secretos de
GitHub o variables de entorno. Jamás en el archivo de workflow.
• Cachea las dependencias: Añade el step de caché de Maven o Gradle. Reduce el tiempo
del pipeline de 5 minutos a menos de 1.
• Fail fast: Ejecuta primero los tests más rápidos. Si los unitarios fallan, no pierdas tiempo
ejecutando los de integración.
• Mantén los workflows simples: Un workflow que hace demasiado es difícil de depurar.
Divide en jobs si el pipeline crece.
• Protege la rama main: En Settings → Branches → Branch protection rules. Requiere que
el pipeline pase antes de hacer merge. Nadie puede subir código roto.
• Versiona las Actions: Usa actions/checkout@v4 no actions/checkout@latest. Las
versiones son predecibles.
• Notifica al equipo: Configura notificaciones de Slack o email cuando el pipeline falla. El
equipo debe saber inmediatamente.
IFCD0182 — Desarrollo Web con Java

11. Actividad práctica guiada

Título Pipeline CI/CD completo para la API REST del curso

Duración 75 minutos

Objetivo Crear un pipeline GitHub Actions funcional que compile, testee, genere el .jar y lo
publique como artefacto en cada push a main

Nivel Guiado — el formador explica cada step antes de implementarlo

Enunciado
Usa el proyecto de la API REST del Módulo 2 (o cualquier proyecto Spring Boot con tests). Crea un repositorio
en GitHub (si no lo tienes) y configura el pipeline CI/CD.

1 Preparar el repositorio Git


Si el proyecto no está en Git: git init, git add ., git commit -m 'Initial commit'. Crea un
repositorio en [Link] y haz push.

2 Crear el archivo de workflow


Crea la carpeta .github/workflows/ en la raíz del proyecto. Crea el archivo [Link] con el
workflow de la sección 4.3. Ajusta el nombre del artifact al de tu proyecto.

3 Verificar que el .gitignore es correcto


El .gitignore de Spring Boot ya incluye target/. Verifica que no estás subiendo el .jar ni las
dependencias de Maven al repositorio.

4 Hacer push y ver el pipeline ejecutarse


git add .github/workflows/[Link] && git commit -m 'Add CI pipeline' && git push. Ve a la
pestaña Actions de tu repositorio en GitHub. Deberías ver el workflow ejecutándose en tiempo
real.

5 Verificar los artefactos generados


Cuando el pipeline termine (barra verde), haz clic en el run. En la sección Artifacts encontrarás
el test-results (informe JUnit) y el app-jar (tu .jar). Descárgalos y verifica su contenido.
IFCD0182 — Desarrollo Web con Java

6 Provocar un fallo intencionado


Introduce un error en el código (rompe un test) y haz push. Verifica que el pipeline falla en rojo
en el step de tests. Corrige el error y haz push de nuevo. El pipeline debe volver a verde.

🤖 CON AYUDA DE LA IA
Depura errores del pipeline con IA

"Mi GitHub Actions workflow falla con este error en el log: [pega el error exacto]. El archivo [Link]
tiene este contenido: [pega el YAML]. ¿Qué está causando el error y cómo lo corrijo?"
Los errores de pipeline suelen ser concretos y fáciles de depurar con el log completo. Copia siempre
el mensaje de error completo, no solo la primera línea.
IFCD0182 — Desarrollo Web con Java

12. Actividad práctica autónoma

Título Pipeline CI/CD completo con Docker, cobertura y despliegue automático

Duración 90 minutos

Objetivo Implementar un pipeline completo que incluya tests, cobertura JaCoCo, construcción
Docker y despliegue en Railway o Render

Nivel Autónomo — documentación GitHub Actions, Docker e IA disponibles

Entrega Repositorio GitHub con el código, el pipeline funcional y un [Link] con capturas
del pipeline en verde y la URL de la app desplegada

Enunciado
Extiende el pipeline de la actividad guiada para que sea un pipeline de producción completo con estas
etapas:

• Job 1 — test: Compilar + tests + generar informe JaCoCo + publicar informe como artefacto. Si la
cobertura cae por debajo del 60%, el job debe fallar (configúralo en el [Link] de JaCoCo).
• Job 2 — build-docker (solo si test pasa y es push a main): Construir la imagen Docker usando el
Dockerfile. Publicarla en GitHub Container Registry con el tag del SHA del commit y también con el
tag 'latest'.
• Job 3 — deploy (solo si build-docker pasa): Desplegar en Railway usando el CLI de Railway desde el
workflow, o hacer un webhook a Render para disparar el redespliegue automático.
• Actuator: Añadir spring-boot-starter-actuator al proyecto. Verificar que /actuator/health devuelve UP
en la app desplegada.

[Link] obligatorio
• Diagrama del pipeline en texto (puede ser ASCII art o tabla) mostrando los 3 jobs y las dependencias
entre ellos.
• Captura de pantalla del pipeline en verde en GitHub Actions.
• URL de la aplicación desplegada en Railway/Render con /actuator/health accesible.
• Una reflexión de 5-10 líneas: ¿Qué problema resuelve este pipeline? ¿Qué pasaría sin él?
IFCD0182 — Desarrollo Web con Java

Criterios de evaluación
Criterio Descripción Peso

Tests en pipeline Job de tests pasa en verde, artefacto JaCoCo 20%


publicado

Cobertura mínima Pipeline falla si cobertura < 60% (config JaCoCo en 15%
[Link])

Docker funcional Imagen construida y publicada en GHCR 20%


correctamente

Despliegue automático La app está accesible públicamente tras el push a 20%


main

Actuator health /actuator/health devuelve UP en la app desplegada 10%

[Link] Diagrama, captura, URL y reflexión completos 15%

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir a la IA que genere el Dockerfile inicial, preguntar cómo configurar el
despliegue en Railway, pedir ayuda para entender un error del pipeline.
• No permitido: pedir a la IA que genere el workflow completo de 3 jobs.
• Obligatorio: el [Link] debe estar escrito por el alumno. La reflexión debe ser
propia.
IFCD0182 — Desarrollo Web con Java

13. Resumen de la unidad

Concepto Lo esencial

CI Continuous Integration: en cada push, compilar y ejecutar tests automáticamente.

CD Continuous Delivery/Deployment: generar el artefacto y desplegarlo


automáticamente.

Pipeline La secuencia automatizada de pasos: compilar → test → empaquetar →


desplegar.

Maven lifecycle validate → compile → test → package → verify → install → deploy.

mvn verify El comando habitual en CI: compila + tests + empaqueta + verifica.

Gradle Alternativa a Maven: más rápido (incremental), DSL Groovy/Kotlin, popular en


Android.

GitHub Actions Plataforma CI/CD integrada en GitHub. Workflows en YAML, runners en la nube.

Workflow Archivo YAML en .github/workflows/. Define triggers, jobs y steps.

Runner La VM donde corre el job. ubuntu-latest es el más habitual.

Secrets Variables cifradas para datos sensibles. Nunca en el YAML directamente.

Jenkinsfile El pipeline de Jenkins en Groovy declarativo. Usado en empresas con


infraestructura propia.

Dockerfile Define cómo construir la imagen Docker de la aplicación Spring Boot.

Actuator Endpoints automáticos de Spring Boot para monitorización: /health, /metrics,


/info.

Logging SLF4J + Logback. Niveles: TRACE < DEBUG < INFO < WARN < ERROR.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
17. ¿Qué diferencia hay entre Continuous Integration, Continuous Delivery y Continuous
Deployment?
18. ¿Cuál es la diferencia entre mvn test y mvn verify?
19. ¿Qué hace el step 'actions/cache' en un workflow y por qué es importante?
20. ¿Por qué nunca debes poner una contraseña directamente en el archivo .yml del
workflow?
21. ¿Cuándo preferirías Jenkins sobre GitHub Actions? ¿Y al revés?
22. ¿Qué niveles de log activarías en producción y por qué?
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Prompt de entrevista técnica — CI/CD:

"Actúa como entrevistador técnico para un puesto de desarrollador Java backend con
conocimientos de DevOps. Hazme 7 preguntas sobre: qué es CI/CD y qué problema resuelve,
diferencia entre Maven y Gradle, cómo funciona un workflow de GitHub Actions, qué son los
secretos de GitHub, diferencia entre Jenkins y GitHub Actions, qué hace Docker en el pipeline y
cómo funciona Actuator. Corrige mis respuestas con el nivel de un perfil junior-medio."

📊 NOTA DE MERCADO LABORAL


Lo que viene en la Unidad 5 — La última del Módulo 3:
La Unidad 5 cierra el Módulo 3 con monitorización avanzada, logging estructurado, optimización
energética en infraestructura TI y el proyecto integrador final del módulo que combina seguridad
(JWT), testing y CI/CD en un sistema completo listo para producción.
IFCD0182 — Desarrollo Web con Java

MÓDULO 3 — Seguridad, Testing y DevOps en Aplicaciones Java


UNIDAD 5 — CIERRE DEL MÓDULO 3

Monitorización, Logging y Sostenibilidad TI


Observar tu aplicación en producción y programar con responsabilidad medioambiental

Duración estimada 6 horas (unidad de cierre del Módulo 3)

Módulo 3 — Seguridad, Testing y DevOps (unidad final)

Unidad anterior Unidad 4 — CI/CD con Maven, Gradle, Jenkins y GitHub Actions

Nivel de entrada Pipeline CI/CD funcionando. Tests automáticos escritos y ejecutados.

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Observar tu aplicación en producción con logging y métricas, y aplicar criterios de
sostenibilidad energética en el código y el despliegue

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar por qué el logging es imprescindible en aplicaciones en producción.
• Configurar y usar SLF4J con Logback en Spring Boot.
• Aplicar los niveles de log correctamente (DEBUG, INFO, WARN, ERROR).
• Monitorizar una aplicación con Spring Boot Actuator.
• Entender qué es y para qué sirve Micrometer y Prometheus.
• Aplicar principios de sostenibilidad energética en el código y el despliegue.
• Identificar patrones de código ineficientes que consumen más recursos.
• Consolidar y conectar todo lo aprendido en el Módulo 3.
IFCD0182 — Desarrollo Web con Java

1. Logging — Ver qué ocurre en tu aplicación

1.1 ¿Por qué el logging es fundamental?


Cuando tu aplicación está desplegada en producción no puedes depurarla con un IDE. No puedes poner
breakpoints ni ver variables en tiempo real. El único mecanismo que tienes para saber qué está pasando son
los logs.

Un log es un registro cronológico de eventos de la aplicación: qué peticiones llegaron, qué errores
ocurrieron, cuánto tardó una operación, qué usuario hizo qué. Sin logs, un error en producción es casi
imposible de diagnosticar.

⚠ ATENCIÓN
[Link]() NO es logging:
Muchos desarrolladores junior usan [Link]() para depurar. En producción esto es un
problema: no se puede filtrar por nivel, no incluye metadatos (fecha, clase, hilo), no se puede
enrutar a ficheros o sistemas externos, y no se puede desactivar sin tocar el código.

Usa siempre un framework de logging. En Spring Boot ya viene configurado: SLF4J con Logback.

1.2 Los niveles de log — Cuándo usar cada uno


Los frameworks de logging tienen niveles de severidad. En producción se configura un nivel mínimo y solo se
registran los mensajes de ese nivel o superior:

Nivel Cuándo usarlo Ejemplo real

TRACE Información muy detallada del flujo de Cada iteración de un bucle, cada llamada a
ejecución. Solo en desarrollo. método interno

DEBUG Información útil para depurar. Desactivado en Valores de variables intermedias, estado de
producción. objetos

INFO Eventos importantes del negocio. Siempre Usuario registrado, pedido procesado,
activo en producción. servicio arrancado

WARN Situaciones anómalas que no son un error pero Token próximo a expirar, tiempo de
hay que vigilar. respuesta alto, reintento

ERROR Errores que impiden una operación. Siempre Fallo al conectar a la BD, excepción no
registrar con la excepción. controlada

💡 CONSEJO
IFCD0182 — Desarrollo Web con Java

Regla práctica: INFO para lo que quieres saber siempre, WARN para lo que te preocupa, ERROR para
lo que falla. DEBUG y TRACE solo cuando estás investigando un problema específico y los desactivas
después.

1.3 SLF4J y Logback en Spring Boot


Spring Boot usa SLF4J como API de logging y Logback como implementación por defecto. Ya está configurado
— solo necesitas declarar el logger en cada clase:

import [Link];
import [Link];
import [Link];

@Service
public class ProductoService {

// Logger: una instancia por clase, siempre private static final


private static final Logger log = [Link]([Link]);

public Producto findById(Long id) {


// INFO: evento de negocio relevante
[Link]("Buscando producto con id: {}", id);

return [Link](id).orElseThrow(() -> {


// WARN: recurso no encontrado — no es error del sistema
[Link]("Producto con id {} no encontrado en la BD", id);
return new RuntimeException("Producto no encontrado: " + id);
});
}

public Producto save(Producto p) {


try {
Producto guardado = [Link](p);
[Link]("Producto guardado correctamente. Id: {}, Nombre: {}",
[Link](), [Link]());
return guardado;
} catch (Exception e) {
// ERROR: incluimos siempre la excepción como segundo argumento
// Logback registra el stack trace completo automáticamente
[Link]("Error al guardar el producto: {}", [Link](), e);
throw e;
}
}
}

✔ BUENAS PRÁCTICAS
Buenas prácticas de logging:
IFCD0182 — Desarrollo Web con Java

• Usa {} para los parámetros: [Link]("Usuario: {}", nombre) en lugar de concatenar


strings. Más eficiente: si el nivel no está activo, el string no se construye.
• En ERROR, pasa la excepción: [Link]("Mensaje", excepcion) registra el stack trace
completo. Sin esto, el log de error no tiene utilidad para depurar.
• No loguees datos sensibles: Contraseñas, tokens JWT, números de tarjeta y datos
personales nunca en los logs. El GDPR lo prohíbe y es un riesgo de seguridad.
• Un mensaje de log debe ser accionable: "Error" no dice nada. "Error al procesar pago del
usuario 42: timeout en el servicio de pagos" sí.

1.4 Configurar el nivel de log en [Link]

# Nivel de log de toda la aplicación


[Link]=INFO

# Nivel de log específico para nuestros paquetes (más detallado en desarrollo)


[Link]=DEBUG

# Nivel de log para Spring Framework (menos verboso)


[Link]=WARN
[Link]=WARN

# Guardar los logs en un fichero


[Link]=logs/[Link]

# Tamaño máximo del fichero de log antes de rotar


[Link]-file-size=10MB
[Link]-history=30

🤖 CON AYUDA DE LA IA
Analiza tus logs con IA
Cuando tengas un error en producción, la IA puede ayudarte a interpretar el stack trace:

"Tengo este error en los logs de mi aplicación Spring Boot en producción [pega el stack trace
completo]. ¿Qué significa este error exactamente? ¿En qué línea de mi código está el origen del
problema? ¿Qué pasos debo seguir para reproducirlo en local y arreglarlo?"
Esta es una habilidad real del día a día: leer logs, extraer el error relevante y diagnosticar la causa
raíz.
IFCD0182 — Desarrollo Web con Java

2. Spring Boot Actuator — Monitorización integrada

2.1 ¿Qué es Spring Boot Actuator?


Actuator es un módulo de Spring Boot que expone automáticamente endpoints HTTP con información sobre
el estado y las métricas de tu aplicación. Con una sola dependencia obtienes visibilidad completa de lo que
ocurre dentro de la aplicación en producción.

<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2.2 Los endpoints principales de Actuator


Endpoint URL Qué muestra

/health /actuator/health Estado de la aplicación: UP/DOWN. Incluye


estado de BD, disco, servicios externos.

/info /actuator/info Información personalizada del build: versión,


descripción, Git commit.

/metrics /actuator/metrics Lista de métricas disponibles: CPU, memoria,


peticiones HTTP, JVM.

/metrics/{name} /actuator/metrics/[Link] Valor concreto de una métrica específica.


d

/env /actuator/env Propiedades de configuración activas


(cuidado: puede exponer datos sensibles).

/loggers /actuator/loggers Niveles de log activos. Permite cambiarlos en


caliente sin reiniciar.

/threaddump /actuator/threaddump Estado de todos los hilos de la JVM. Útil para


detectar deadlocks.

/httptrace /actuator/httptrace Últimas 100 peticiones HTTP recibidas con


tiempos de respuesta.

2.3 Configurar Actuator de forma segura


# Activar todos los endpoints (desactivados por defecto por seguridad)
[Link]=health,info,metrics,loggers

# En produccion: proteger con Spring Security (solo ADMIN)


[Link]-path=/actuator

# Mostrar detalles de salud (solo en desarrollo)


[Link]-details=when-authorized

# Informacion del build en /actuator/info


[Link]=true
[Link]=Mi Aplicacion Spring Boot
[Link]=1.0.0
IFCD0182 — Desarrollo Web con Java
[Link]=Gestion de productos

💡 CONSEJO
En producción, nunca expongas /actuator/** públicamente. Los endpoints como /env o
/threaddump revelan información interna de la aplicación. Protégelos con Spring Security
requiriendo ROLE_ADMIN.

2.4 Health checks personalizados


Puedes añadir tus propios indicadores de salud para incluir el estado de servicios externos que usa tu
aplicación:

import [Link].*;
import [Link];

@Component
public class ServicioExternoHealthIndicator implements HealthIndicator {

private final ServicioExternoClient client;

public ServicioExternoHealthIndicator(ServicioExternoClient c) {
[Link] = c;
}

@Override
public Health health() {
try {
boolean disponible = [Link]();
if (disponible) {
return [Link]()
.withDetail("servicio", "externo-pagos")
.withDetail("estado", "conectado")
.build();
}
return [Link]()
.withDetail("servicio", "externo-pagos")
.withDetail("error", "No responde")
.build();
} catch (Exception e) {
return [Link](e).build();
}
}
}
IFCD0182 — Desarrollo Web con Java

3. Métricas con Micrometer y Prometheus

3.1 Qué son las métricas y para qué sirven


Las métricas son mediciones numéricas del comportamiento de tu aplicación a lo largo del tiempo: cuántas
peticiones por segundo recibe, cuánto tarda en responder, cuánta memoria usa, cuántos errores produce.
Son la base de la monitorización proactiva.

La diferencia entre logging y métricas: los logs te dicen qué ocurrió en un momento concreto. Las métricas te
muestran tendencias a lo largo del tiempo y te permiten detectar degradación de rendimiento antes de que
cause problemas.

Herramienta Qué hace En Spring Boot

Micrometer Librería de métricas para Java. Es el 'SLF4J de Incluida en spring-boot-starter-


las métricas': una API que funciona con actuator
múltiples backends.

Prometheus Base de datos de series temporales que Dependencia micrometer-registry-


almacena las métricas y permite consultarlas. prometheus

Grafana Herramienta de visualización que crea Herramienta externa, se conecta a


dashboards con los datos de Prometheus. Prometheus

3.2 Añadir métricas personalizadas con Micrometer

<dependency>
<groupId>[Link]</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

import [Link].*;
import [Link];

@Service
public class ProductoService {

private final MeterRegistry registry;


private final Counter productosCreados;
private final Timer tiempoBusqueda;

public ProductoService(ProductoRepository repo, MeterRegistry registry) {


[Link] = registry;
// Counter: cuenta eventos (monotónicamente creciente)
[Link] = [Link]("[Link]")
.description("Total de productos creados")
.register(registry);
// Timer: mide duraciones
IFCD0182 — Desarrollo Web con Java
[Link] = [Link]("[Link]")
.description("Tiempo de búsqueda de productos")
.register(registry);
}

public Producto save(Producto p) {


Producto guardado = [Link](p);
// Incrementa el contador cada vez que se crea un producto
[Link]();
return guardado;
}

public List<Producto> findAll() {


// El Timer registra cuánto tarda la operación
return [Link](() -> [Link]());
}
}

# En [Link]: exponer el endpoint de Prometheus


[Link]=health,metrics,prometheus
[Link]=true

👁 FÍJATE
Con esta configuración, Prometheus puede hacer scraping de
[Link] cada 15 segundos y almacenar todas las métricas.
Grafana las visualiza en dashboards en tiempo real. Este stack (Spring Boot + Micrometer +
Prometheus + Grafana) es el estándar de monitorización en la industria.
IFCD0182 — Desarrollo Web con Java

4. Sostenibilidad en el software — Green IT

4.1 El impacto ambiental del software


El sector de las tecnologías de la información consume aproximadamente el 2-3% de la electricidad global,
con una huella de carbono similar a la aviación comercial. Y sigue creciendo. Como desarrolladores, las
decisiones de diseño y arquitectura que tomamos tienen un impacto real en el consumo energético.

Green IT (TI sostenible) es la práctica de diseñar, desarrollar y operar software y hardware de manera que
minimice el impacto ambiental. No es un tema futuro: ya es un requisito en muchos pliegos de contratación
pública en España y Europa.

📊 NOTA DE MERCADO LABORAL


Sostenibilidad en el mercado laboral:
El Reglamento Europeo de Taxonomía Verde y la directiva de reporting de sostenibilidad corporativa
(CSRD) están haciendo que las empresas necesiten medir y reducir la huella digital de sus sistemas.
Cada vez más ofertas de trabajo incluyen conocimientos de eficiencia energética en TI como
requisito o valorado positivamente.

4.2 Sostenibilidad en el código Java


Las decisiones de código tienen impacto energético. Estas son las prácticas más relevantes en el contexto de
este curso:

🌿 SOSTENIBILIDAD
Prácticas de código sostenible en Java:
• Evita operaciones innecesarias en bucles: Cada iteración consume CPU. Mueve fuera del
bucle todo lo que no cambia en cada iteración.
• Usa caché para datos que no cambian frecuentemente: Spring Cache (@Cacheable)
evita consultas repetidas a la BD para los mismos datos. Menos consultas = menos CPU y
menos red.
• Paginación siempre en listas grandes: No traigas 10.000 registros de la BD si el usuario
solo ve 20. Page<T> en Spring Data JPA.
• Cierra los recursos correctamente: Conexiones de BD, streams, clientes HTTP. Usar try-
with-resources garantiza que se cierran aunque haya excepciones.
• Elige los tipos de datos adecuados: Un int consume 4 bytes, un long 8. Para IDs
pequeños, int es suficiente. Para grandes volúmenes de datos, cada byte cuenta.
• Lazy loading vs Eager loading en JPA: Carga solo lo que necesitas. [Link] para
relaciones que no siempre se usan evita traer datos innecesarios.
IFCD0182 — Desarrollo Web con Java

4.3 Sostenibilidad en los tests


Los tests automáticos se ejecutan miles de veces: en cada commit, en cada pipeline CI. El tiempo que tardan
tiene un coste energético acumulado significativo en proyectos grandes.

Práctica Impacto energético Cómo implementarla

Minimizar el uso de Arranca el contexto completo: Usar @WebMvcTest, @DataJpaTest o


@SpringBootTest consume más CPU y tiempo @ExtendWith(MockitoExtension) cuando
sea suficiente

Tests en paralelo Reduce el tiempo total aunque maven-surefire-plugin con


usa la misma CPU parallel=methods y threadCount=4

Caché de dependencias Descargar dependencias en cada actions/setup-java con cache: maven en


Maven en CI pipeline consume red y GitHub Actions
almacenamiento

Evitar sleeps en tests [Link]() desperdicia Usar Awaitility o mecanismos de


tiempo y recursos sin hacer nada sincronización apropiados

Tests de integración solo Los tests con BD real son lentos Reserva @SpringBootTest para los tests de
cuando es necesario — minimízalos integración críticos

4.4 Sostenibilidad en el despliegue


🌿 SOSTENIBILIDAD
Decisiones de infraestructura con impacto ambiental:
• Escala a cero cuando no hay tráfico: AWS Lambda, Cloud Run de Google o Azure
Functions permiten que el servicio no consuma recursos cuando nadie lo usa. Ideal para
servicios con tráfico irregular.
• Dimensiona correctamente los contenedores: Asigna los recursos (CPU, memoria) según
la carga real, no la máxima teórica. Un contenedor sobredimensionado malgasta energía
continuamente.
• Elige regiones del cloud con energía renovable: AWS, Azure y Google Cloud publican
mapas de su mix energético. Elegir una región con más renovables puede reducir la
huella de carbono un 40-80%.
• Caché agresiva para contenido estático: CSS, JS, imágenes con Cache-Control largo.
Menos peticiones al servidor = menos CPU = menos energía.
• Compresión de respuestas HTTP: [Link]-mappings=true y
[Link]=true. Menos datos en red = menos energía de transmisión.
• Monitoriza el consumo real: Herramientas como Cloud Carbon Footprint (open source)
calculan la huella de CO₂ de tu infraestructura cloud y ayudan a optimizarla.
IFCD0182 — Desarrollo Web con Java

4.5 Configurar compresión HTTP en Spring Boot

# [Link] — compresión de respuestas HTTP


[Link]=true
[Link]-types=application/json,application/xml,text/html,text/css
[Link]-response-size=1024

# Cache para recursos estáticos


[Link]-age=365d
[Link]-public=true

🤖 CON AYUDA DE LA IA
Audita la sostenibilidad de tu código con IA
Usa la IA para identificar oportunidades de optimización energética:

"Analiza este servicio Spring Boot [pega el código] desde el punto de vista de la eficiencia
energética y el rendimiento. ¿Hay consultas a la BD que podrían cachearse? ¿Hay operaciones
innecesariamente costosas en los bucles? ¿El uso de fetchType es el más eficiente para este caso?
Sugiere mejoras concretas con el código corregido."
IFCD0182 — Desarrollo Web con Java

5. Errores frecuentes en logging y monitorización

Error / Síntoma Causa habitual Solución

Los logs no aparecen aunque se El nivel configurado es superior al Verifica [Link]=INFO


llame a [Link]() usado. Si el nivel es WARN, los (o DEBUG) en [Link]
INFO no se registran

El log de error no incluye el stack Se usa [Link]("Mensaje: " + Usa [Link]("Mensaje", excepcion) —
trace de la excepción [Link]()) en lugar de pasar el segundo argumento es la excepción,
la excepción como argumento no un String

Datos sensibles aparecen en los Se están logueando objetos Implementa toString() en tus entidades
logs completos que incluyen excluyendo los campos sensibles, o
contraseñas o tokens loguea solo los campos seguros

El endpoint /actuator/health La dependencia spring-boot- Añade la dependencia y


devuelve 404 starter-actuator no está en el [Link].i
[Link] o los endpoints no están nclude=health en
expuestos [Link]

El endpoint /actuator/metrics Las métricas personalizadas no Verifica que inyectas MeterRegistry y


muestra muchas métricas pero están registradas en el registras el Counter/Timer en el
no las personalizadas MeterRegistry constructor o con @PostConstruct

Los logs en producción son El nivel de log está en DEBUG para En producción usa INFO para tu código
demasiado verbosos y llenan el toda la aplicación en producción y WARN para las librerías. Configura
disco rotación de logs con max-history
IFCD0182 — Desarrollo Web con Java

6. Actividad práctica guiada

Título Añadir logging, Actuator y métricas a la API del Módulo 3

Duración 60 minutos

Objetivo Instrumentar la aplicación con logging profesional, Actuator y métricas personalizadas


de Micrometer

Nivel Guiado — el formador explica cada bloque antes de implementarlo

Enunciado
Toma la API REST de cualquiera de las unidades anteriores (libros, empleados, productos) y añade
observabilidad completa.

1 Añadir logging profesional a todas las clases de servicio


Declara el Logger en cada @Service. Añade [Link]() en las operaciones de negocio (crear,
actualizar, eliminar). Añade [Link]() cuando un recurso no se encuentra. Añade [Link]()
con la excepción en todos los bloques catch.

2 Configurar los niveles de log en [Link]


Root: INFO. Tu paquete [Link]: DEBUG. Spring Framework: WARN. Guarda logs en
logs/[Link] con rotación diaria y máximo 30 días de historial.

3 Añadir Spring Boot Actuator


Dependencia en [Link]. Exponer health, info, metrics y loggers. Añade información de la
aplicación en [Link] (nombre, versión, descripción). Abre /actuator/health y
/actuator/metrics en el navegador.

4 Añadir métricas personalizadas con Micrometer


Un Counter para total de recursos creados. Un Timer para el tiempo de la operación findAll().
Verifica que aparecen en /actuator/metrics.

5 Aplicar al menos 3 mejoras de sostenibilidad


Activa la compresión HTTP. Añade @Cacheable a al menos un método de servicio que consulte
datos que no cambian frecuentemente. Asegúrate de que todas las consultas JPA usan
paginación en lugar de findAll() sin límite.
IFCD0182 — Desarrollo Web con Java

7. Proyecto integrador del Módulo 3

ℹ NOTA
Esta actividad es el proyecto final del Módulo 3 completo. Integra seguridad (Spring Security + JWT),
testing (JUnit + Mockito), CI/CD (GitHub Actions) y observabilidad (logging + Actuator). Es la pieza
que demuestra que dominas el módulo.

Título API segura, testeada, automatizada y observable — Proyecto final Módulo 3

Duración 90 minutos

Objetivo Construir o completar una API Spring Boot que integre todos los aspectos del Módulo 3

Nivel Autónomo — todos los apuntes del módulo e IA disponibles

Entrega Repositorio GitHub público con README completo que incluya: descripción,
instrucciones de uso, badge del pipeline CI y decisiones de sostenibilidad aplicadas

Requisitos del proyecto


• Seguridad (Unidades 1 y 2): Spring Security con JWT. Login que devuelve token. Endpoints protegidos
por rol. BCrypt para contraseñas.
• Testing (Unidad 3): Mínimo 10 tests: unitarios con Mockito, de controlador con MockMvc y al menos
un test parametrizado. Todos en verde.
• CI/CD (Unidad 4): Workflow GitHub Actions que compile, ejecute los tests y falle si alguno falla.
Badge de estado del pipeline en el README.
• Observabilidad (Unidad 5): Logging con SLF4J en todas las clases de servicio con los niveles correctos.
Actuator con health y metrics. Al menos una métrica personalizada.
• Sostenibilidad: Al menos 3 prácticas de Green IT aplicadas y documentadas en el README.
Criterios de evaluación

Criterio Descripción Peso

Seguridad JWT Login funcional, filtro JWT correcto, roles aplicados 20%
con @PreAuthorize

Tests en verde 10+ tests pasando, AAA claro, casos límite y casos de 20%
error cubiertos

Pipeline CI/CD GitHub Actions ejecuta los tests en cada push, badge 20%
visible en README

Logging y Actuator SLF4J con niveles correctos, Actuator expuesto, 20%


métrica personalizada

Sostenibilidad 3+ prácticas Green IT implementadas y 10%


documentadas

README Instrucciones claras, descripción de arquitectura, 10%


decisiones documentadas
IFCD0182 — Desarrollo Web con Java

8. Resumen y cierre del Módulo 3

Concepto Lo esencial

SLF4J / Logback API de logging estándar en Java. Logger por clase. Niveles: TRACE, DEBUG,
INFO, WARN, ERROR.

Niveles de log INFO para producción, DEBUG para desarrollo, ERROR siempre con la
excepción como argumento.

Spring Boot Actuator Endpoints de observabilidad: /health, /metrics, /loggers. Proteger con Spring
Security en producción.

Micrometer Librería de métricas para Java. Counter para contar eventos, Timer para
medir duraciones.

Prometheus + Grafana Stack de monitorización estándar: Prometheus recopila, Grafana visualiza.

Green IT El software tiene impacto energético. Caché, paginación, compresión y


dimensionado correcto reducen el consumo.

Sostenibilidad código Evita operaciones innecesarias en bucles, usa lazy loading en JPA, cierra
recursos siempre.

Sostenibilidad CI Minimiza @SpringBootTest, usa caché de dependencias Maven, evita sleeps


en tests.

Sostenibilidad cloud Escala a cero, elige regiones con renovables, compresión HTTP, caché de
estáticos.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
1. ¿Por qué [Link]() no es una alternativa válida al logging en producción?
2. ¿Cuándo usas WARN y cuándo usas ERROR? Pon un ejemplo de cada uno.
3. ¿Qué endpoint de Actuator usarías para saber si la aplicación está levantada y la BD
conectada?
4. ¿Qué diferencia hay entre un Counter y un Timer de Micrometer?
5. Nombra tres prácticas de código que reducen el consumo energético de una aplicación
Java.

🤖 CON AYUDA DE LA IA
Revisión completa del Módulo 3 con IA:

"Actúa como entrevistador técnico senior para un puesto de desarrollador Java con Spring Boot
en una empresa que valora la calidad del software y la sostenibilidad. Hazme 10 preguntas de
dificultad creciente sobre el Módulo 3: Spring Security, JWT, JUnit con Mockito, MockMvc, CI/CD
con GitHub Actions, logging con SLF4J, Spring Boot Actuator y prácticas de Green IT. Después de
cada respuesta corrígeme."
IFCD0182 — Desarrollo Web con Java

Cierre del Módulo 3 — Lo que llevas contigo al Módulo 4


Has completado el Módulo 3. Esto es lo que has construido:

• Proteges tus APIs con Spring Security: autenticación, autorización por roles, BCrypt.
• Implementas autenticación JWT completa: login, filtro, refresh token, logout.
• Escribes tests automáticos profesionales: unitarios, de integración y de controlador.
• Tienes un pipeline CI/CD que ejecuta los tests automáticamente en cada push.
• Observas tu aplicación en producción con logging y métricas.
• Programas con criterio de sostenibilidad energética.

El Módulo 4 integra todo con el Frontend: React, Angular o Vue consumiendo las APIs REST seguras
que has construido. Es el punto donde el backend y el frontend se encuentran.
IFCD0182 — Desarrollo Web con Java

MÓDULO 4 — Integración con Frontend


UNIDAD 1

HTML, CSS, JavaScript y UX/UI


Los fundamentos del frontend que todo desarrollador backend necesita conocer para
comunicarse con su equipo y entender lo que consume su API

Duración estimada 5 horas — unidad compacta con enfoque en fundamentos

Módulo 4 — Integración con Frontend

Unidad anterior Módulo 3 completado — Seguridad, Testing y DevOps

Nivel de entrada Heterogéneo — sin asumir conocimientos previos de frontend

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Los fundamentos de HTML, CSS y JavaScript que necesitas como desarrollador
backend para entender el frontend y trabajar en equipo con desarrolladores
frontend

🔗 CONEXIÓN CON EL BACKEND


Por qué un desarrollador backend necesita saber frontend:
Todo el backend que has construido en los módulos anteriores —APIs REST, Spring Security, JWT—
está pensado para ser consumido por alguien: un navegador, una app React, una app móvil. Para
diseñar buenas APIs necesitas entender cómo el frontend las va a usar.

No necesitas ser experto en frontend. Necesitas entender el lenguaje del navegador para colaborar
con el equipo frontend, depurar problemas de integración y leer el código que consume tu API.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Entender la estructura de una página web con HTML semántico.
• Aplicar estilos básicos con CSS: colores, tipografía, modelo de caja y Flexbox.
• Escribir JavaScript básico para manipular el DOM y gestionar eventos.
• Hacer peticiones a tu API REST desde JavaScript con fetch().
• Aplicar los principios básicos de accesibilidad web (WCAG).
• Entender qué es UX/UI y cómo afecta a las decisiones de diseño de APIs.
IFCD0182 — Desarrollo Web con Java

1. Cómo funciona el navegador — El cliente de tu API

1.1 Las tres tecnologías del frontend


Cuando abres una página web en el navegador, el servidor envía tres tipos de ficheros. Cada uno tiene una
responsabilidad específica y no intercambiable:

HTML CSS JavaScript


Estructura y contenido Presentación y estilos Comportamiento e interacción
El esqueleto. Define qué hay en la La apariencia. Colores, tamaños, La lógica. Responde a acciones del
página: títulos, párrafos, tipografía, posición. Cómo se ve, usuario, llama a APIs, modifica la
imágenes, formularios, botones. no qué es. página dinámicamente.

1.2 El ciclo de vida de una petición web


Cuando un usuario escribe una URL o hace clic en un enlace, ocurre lo siguiente:

1. El navegador envía una petición HTTP GET al servidor.


2. El servidor (tu Spring Boot) devuelve el HTML de la página.
3. El navegador analiza el HTML y encuentra referencias a CSS y JavaScript.
4. Hace peticiones adicionales para descargar esos ficheros.
5. Renderiza la página: aplica los estilos CSS al HTML y ejecuta el JavaScript.
6. Si el JavaScript hace fetch() a tu API, el navegador hace otra petición HTTP a tu backend.
7. Tu API devuelve JSON. El JavaScript lo procesa y actualiza la página sin recargarla.

🔗 CONEXIÓN CON EL BACKEND


Ese paso 6 y 7 es exactamente donde conectan el frontend y tu API REST. El JavaScript hace la
petición, tu @RestController la recibe, tu @Service la procesa, y tu controlador devuelve un JSON
que el JavaScript convierte en HTML visible para el usuario.
IFCD0182 — Desarrollo Web con Java

2. HTML — La estructura de la página

2.1 HTML semántico — Usar las etiquetas correctas


HTML (HyperText Markup Language) describe la estructura y el contenido de una página mediante etiquetas.
El HTML semántico usa las etiquetas que mejor describen el significado del contenido, no solo su apariencia.
Esto mejora la accesibilidad, el SEO y la legibilidad del código.

Etiqueta semántica Para qué sirve No usar en su lugar

<header> Cabecera de la página o sección <div class='header'>

<nav> Menú de navegación <div class='menu'>

<main> Contenido principal — solo uno por página <div class='main'>

<section> Sección temática con su propio título <div>

<article> Contenido independiente (post, noticia, <div>


producto)

<aside> Contenido secundario (barra lateral, <div class='sidebar'>


anuncios)

<footer> Pie de página o sección <div class='footer'>

<h1>-<h6> Títulos jerárquicos — h1 solo uno por <p> o <div> en negrita


página

<button> Botón interactivo <div onclick=...>

<label> Etiqueta asociada a un campo de <span>


formulario

2.2 Estructura HTML completa de una página


<!DOCTYPE html>
<html lang="es">
<head>
<!-- Metadatos: no son visibles en la página --
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Gestión de productos de la tienda">
<title>Gestión de Productos</title>
<link rel="stylesheet" href="[Link]">
</head>
<body>

<header>
<h1>Mi Tienda</h1>
<nav>
<a href="/productos">Productos</a>
<a href="/carrito">Carrito</a>
<a href="/perfil">Mi cuenta</a>
</nav>
</header>
IFCD0182 — Desarrollo Web con Java
<main>
<section id="lista-productos">
<h2>Catálogo de productos</h2>
<!-- Los productos se cargarán con JavaScript desde la API -->
<div id="productos-container">Cargando...</div>
</section>
</main>

<footer>
<p>&copy; 2025 Mi Tienda. Todos los derechos reservados.</p>
</footer>

<script src="[Link]"></script>
</body>
</html>

2.3 Formularios HTML — El puente entre usuario y API


Los formularios son la principal forma en que el usuario envía datos al backend. Cada campo tiene atributos
que el navegador usa para validar antes de enviar:

<form id="formulario-login">
<label for="email">Correo electrónico:</label>
<input type="email"
id="email"
name="email"
required
placeholder="tu@[Link]"
autocomplete="email">

<label for="password">Contraseña:</label>
<input type="password"
id="password"
name="password"
required
minlength="8">

<!-- type='submit' envía el formulario -->


<button type="submit">Iniciar sesión</button>
</form>

👁 FÍJATE
El atributo for del <label> debe coincidir exactamente con el id del <input>. Esto los vincula: al hacer
clic en el label, el foco va al input. Es fundamental para accesibilidad.
IFCD0182 — Desarrollo Web con Java

3. CSS — La apariencia de la página

3.1 Selectores y propiedades básicas


CSS (Cascading Style Sheets) define cómo se ve el HTML. Funciona con reglas: selector { propiedad: valor }. El
selector indica a qué elementos se aplica el estilo.

/* Selector de etiqueta: aplica a todos los <p> */


p {
color: #333333; /* Color del texto */
font-size: 16px; /* Tamaño de fuente */
font-family: 'Calibri', sans-serif;
line-height: 1.6; /* Altura de línea — mejora la legibilidad */
margin-bottom: 12px; /* Espacio por debajo del párrafo */
}

/* Selector de clase: aplica a elementos con class='btn-primario' */


.btn-primario {
background-color: #E64A19;
color: #ffffff;
padding: 10px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
}

/* Selector de ID: aplica al elemento con id='productos-container' */


#productos-container {
max-width: 1200px;
margin: 0 auto; /* Centrar horizontalmente */
padding: 20px;
}

/* Pseudo-clase: aplica cuando el cursor está sobre el botón */


.btn-primario:hover {
background-color: #BF360C;
transition: background-color 0.2s ease;
}
IFCD0182 — Desarrollo Web con Java

3.2 El modelo de caja — Box Model


Todos los elementos HTML son cajas rectangulares. El modelo de caja define cómo se calcula el tamaño total
de un elemento:

Capa Qué es Propiedad CSS

Content El contenido real (texto, imagen) width, height

Padding Espacio interior entre el contenido y el borde padding, padding-


top/right/bottom/left

Border El borde visible del elemento border, border-width, border-


color

Margin Espacio exterior entre este elemento y los demás margin, margin-
top/right/bottom/left

/* box-sizing: border-box hace que width incluya padding y border */


/* Es la forma más intuitiva de trabajar con el modelo de caja */
*, *::before, *::after {
box-sizing: border-box;
}

3.3 Flexbox — Layout moderno


Flexbox es el sistema de maquetación más usado para distribuir elementos en una fila o columna de forma
flexible y responsiva:

.contenedor-productos {
display: flex; /* Activa Flexbox */
flex-wrap: wrap; /* Los items se envuelven a la siguiente línea si no
caben */
gap: 20px; /* Espacio entre items */
justify-content: flex-start;/* Alineación horizontal: start/center/space-between...
*/
}

.tarjeta-producto {
flex: 1 1 300px; /* Crece, se encoge, tamaño base 300px */
max-width: 400px;
padding: 16px;
border: 1px solid #cccccc;
border-radius: 8px;
}

3.4 Diseño responsivo con Media Queries


El diseño responsivo adapta la página a diferentes tamaños de pantalla. Con media queries defines estilos
que solo se aplican en ciertos rangos de pantalla:

/* Estilos por defecto: móvil primero (Mobile First) */


.contenedor-productos {
display: flex;
flex-direction: column; /* En móvil: en columna */
gap: 16px;
IFCD0182 — Desarrollo Web con Java
}

/* En pantallas de 768px o más (tablet/desktop) */


@media (min-width: 768px) {
.contenedor-productos {
flex-direction: row; /* En tablet/desktop: en fila */
flex-wrap: wrap;
}
}

🤖 CON AYUDA DE LA IA
CSS con IA — depurar y aprender layouts
CSS puede ser frustrante al principio. La IA es muy útil para debugear layouts:

"Tengo este div con class='contenedor-productos' y dentro tengo 6 tarjetas de producto. Quiero
que en móvil aparezcan en una columna, en tablet de 2 en 2 y en desktop de 3 en 3. Escríbeme el
CSS con Flexbox y media queries para conseguirlo, y explícame qué hace cada propiedad."
IFCD0182 — Desarrollo Web con Java

4. JavaScript — El comportamiento de la página

4.1 JavaScript en el contexto del backend


JavaScript es el único lenguaje que entiende el navegador de forma nativa. Como desarrollador backend Java
conoces la lógica de programación, los tipos de datos y las funciones. JavaScript comparte esos conceptos
aunque con sintaxis diferente.

Concepto Java (lo que sabes) JavaScript (lo nuevo)

Variables String nombre = "Juan"; let nombre = 'Juan'; (tipado dinámico)

Funciones public void saludar() {} function saludar() {} o () => {}

Arrays List<String> lista = new ArrayList<>(); const lista = []; (siempre dinámico)

Objetos Clase con atributos y métodos const obj = {nombre: 'Juan', edad: 30}

Condicionales if (x > 0) {} if (x > 0) {} — idéntico

Bucles for (int i=0; i<10; i++) {} for (let i=0; i<10; i++) {} — idéntico

Asincronía CompletableFuture / @Async Promises, async/await — esencial en JS

Clases class Producto { ... } class Producto { ... } — desde ES6

4.2 Manipulación del DOM


El DOM (Document Object Model) es la representación en memoria de la página HTML. JavaScript puede
leer y modificar cualquier elemento de la página a través del DOM:

// Seleccionar elementos
const titulo = [Link]('titulo');
const botones = [Link]('.btn-primario');
const formulario = [Link]('#formulario-login');

// Leer y modificar contenido


[Link] = 'Nuevo título'; // Solo texto
[Link] = '<strong>Texto</strong>'; // HTML dentro del elemento

// Modificar estilos
[Link] = '#E64A19';

// Añadir y eliminar clases


[Link]('activo');
[Link]('oculto');
[Link]('destacado');

// Crear y añadir elementos al DOM


const nuevoParrafo = [Link]('p');
[Link] = 'Nuevo párrafo añadido';
[Link]('main').appendChild(nuevoParrafo);
IFCD0182 — Desarrollo Web con Java

4.3 Eventos — Responder a las acciones del usuario


// addEventListener: escucha un evento en un elemento
[Link]('btn-login').addEventListener('click', function() {
[Link]('El usuario ha hecho clic en Iniciar sesión');
});

// Con arrow function (forma moderna)


[Link]('btn-login').addEventListener('click', () => {
[Link]('Clic en login');
});

// Capturar el envío de un formulario


[Link]('formulario-login').addEventListener('submit', (evento) => {
// Previene que el formulario recargue la página (comportamiento por defecto)
[Link]();

// Leemos los valores de los campos


const email = [Link]('email').value;
const password = [Link]('password').value;

[Link]('Email:', email, 'Password:', password);


// Aquí llamaríamos a la API con fetch()
});

4.4 fetch() — Llamar a tu API REST desde JavaScript


fetch() es la función nativa del navegador para hacer peticiones HTTP. Es la forma en que el frontend
consume tu API REST. Devuelve una Promise (una promesa de un valor futuro):

// GET — obtener productos de la API


async function cargarProductos() {
try {
// fetch() hace una petición GET a tu API Spring Boot
const respuesta = await fetch('[Link]

// Verificamos que la respuesta fue exitosa


if (![Link]) {
throw new Error(`Error ${[Link]}: ${[Link]}`);
}

// Convertimos el JSON de la respuesta a un objeto JavaScript


const productos = await [Link]();

// Actualizamos el DOM con los productos recibidos


mostrarProductos(productos);
} catch (error) {
[Link]('Error al cargar productos:', error);
[Link]('productos-container').innerHTML =
'<p>Error al cargar los productos. Inténtalo de nuevo.</p>';
}
}

// POST — enviar datos a la API con autenticación JWT


IFCD0182 — Desarrollo Web con Java
async function crearProducto(datos) {
const token = [Link]('jwt_token');

const respuesta = await fetch('[Link] {


method: 'POST',
headers: {
'Content-Type': 'application/json',
// Enviamos el JWT en la cabecera Authorization
'Authorization': `Bearer ${token}`
},
// Convertimos el objeto JavaScript a JSON string
body: [Link](datos)
});

if ([Link]) {
const productoCreado = await [Link]();
[Link]('Producto creado:', productoCreado);
}
}

// Mostrar los productos en el DOM


function mostrarProductos(productos) {
const contenedor = [Link]('productos-container');
// map() genera HTML para cada producto
[Link] = [Link](p => `
<div class='tarjeta-producto'>
<h3>${[Link]}</h3>
<p>Precio: ${[Link]} €</p>
<p>Stock: ${[Link]} unidades</p>
</div>
`).join('');
}

// Llamamos a la función al cargar la página


[Link]('DOMContentLoaded', cargarProductos);

🔗 CONEXIÓN CON EL BACKEND


El círculo completo:
Este fetch('/api/productos') llega a tu @RestController Spring Boot. Tu @Service consulta el
repositorio. El @RestController devuelve un JSON. JavaScript lo recibe, lo convierte en objetos y los
muestra en el DOM. Es el ciclo completo del desarrollo web full-stack.
IFCD0182 — Desarrollo Web con Java

5. Accesibilidad web — WCAG y diseño inclusivo

5.1 ¿Qué es la accesibilidad web?


La accesibilidad web (a11y) es el diseño de páginas web que pueden ser usadas por todas las personas,
incluidas aquellas con discapacidades visuales, auditivas, motoras o cognitivas. Millones de personas usan
lectores de pantalla, teclados en lugar de ratón, o tienen dificultades para distinguir ciertos colores.

WCAG (Web Content Accessibility Guidelines) es el estándar internacional de accesibilidad web, publicado
por el W3C. En muchos países es un requisito legal para webs de administración pública.

📊 NOTA DE MERCADO LABORAL


Accesibilidad en el mercado:
En España el Real Decreto 1112/2018 exige que las webs del sector público cumplan WCAG 2.1 nivel
AA. Cada vez más empresas privadas incluyen requisitos de accesibilidad en sus proyectos. Es una
habilidad diferenciadora y en muchos sectores, una obligación legal.

5.2 Los cuatro principios WCAG — POUR


Principio Qué significa Ejemplos prácticos

Perceptible La información debe presentarse de Texto alternativo en imágenes, subtítulos en


forma que el usuario pueda percibirla vídeos, contraste de color suficiente

Operable Los componentes deben ser Navegación con teclado, tiempo suficiente
utilizables con diferentes métodos de para leer, sin contenido que cause
entrada convulsiones

Comprensible El contenido y la interfaz deben ser Lenguaje claro, mensajes de error


fáciles de entender descriptivos, comportamiento predecible

Robusto El contenido debe ser interpretable HTML semántico válido, ARIA attributes,
por tecnologías de asistencia compatibilidad con lectores de pantalla

5.3 Prácticas de accesibilidad en HTML


<!-- 1. Texto alternativo en imágenes -->
<img src="[Link]" alt="Teclado mecánico RGB con switches Cherry MX">
<!-- Si la imagen es decorativa, alt vacío: el lector de pantalla la ignora -->
<img src="[Link]" alt="">

<!-- 2. Labels vinculadas a inputs -->


<label for="nombre">Nombre del producto:</label>
<input type="text" id="nombre" name="nombre" required>

<!-- 3. ARIA roles para componentes personalizados -->


<div role="alert" aria-live="polite" id="mensaje-error">
El email introducido no es válido.
</div>
IFCD0182 — Desarrollo Web con Java
<!-- 4. Focus visible para navegación con teclado -->
/* En CSS: nunca elimines el outline de focus sin reemplazarlo */
:focus {
outline: 2px solid #E64A19;
outline-offset: 2px;
}

<!-- 5. Contraste de color suficiente -->


/* Mínimo WCAG AA: ratio 4.5:1 para texto normal, 3:1 para texto grande */
/* Usa herramientas como [Link] */

5.4 Atributos ARIA más usados


Atributo ARIA Para qué sirve Ejemplo

aria-label Etiqueta descriptiva para el lector de <button aria-label="Cerrar


pantalla modal">X</button>

aria-hidden Oculta el elemento para lectores de <span aria-hidden="true">→</span>


pantalla

aria-live Anuncia cambios dinámicos en <div aria-live="polite">Cargando...</div>


tiempo real

aria-expanded Indica si un elemento colapsable está <button aria-


abierto expanded="false">Menú</button>

aria-describedby Vincula un elemento con su <input aria-describedby="ayuda-email">


descripción

role Define el rol semántico de un <div role="navigation">


elemento no nativo

🤖 CON AYUDA DE LA IA
Auditoría de accesibilidad con IA
La IA puede ayudarte a identificar problemas de accesibilidad en tu HTML:

"Revisa este fragmento HTML [pega tu código] desde el punto de vista de accesibilidad web
(WCAG 2.1 AA). Identifica problemas concretos: imágenes sin alt, inputs sin label, contraste
insuficiente, elementos no navegables por teclado, falta de ARIA. Para cada problema, dime la
línea exacta y cómo corregirlo."
IFCD0182 — Desarrollo Web con Java

6. UX/UI — Diseñar para el usuario

6.1 UX vs UI — La diferencia
UX y UI son dos disciplinas relacionadas pero distintas. Como desarrollador backend, entender la diferencia
te ayuda a colaborar mejor con el equipo de diseño y a tomar mejores decisiones en el diseño de tus APIs:

UX — User Experience UI — User Interface


Experiencia de Usuario Interfaz de Usuario
Cómo se siente el usuario al usar el producto. Cómo se ve el producto. Colores, tipografía,
¿Es fácil? ¿Es frustrante? ¿Consigue lo que botones, espaciado, iconos, layouts.
quiere?
Diseña componentes visuales y sistemas de
Investiga, diseña flujos, hace tests con usuarios diseño.
reales.

6.2 Principios de UX que afectan al diseño de APIs


El diseño de una API tiene impacto directo en la experiencia de usuario. Una API bien diseñada permite al
frontend hacer lo que el usuario necesita sin contorsiones:

Principio UX Impacto en el diseño de la API

Feedback inmediato — el usuario Los endpoints POST y PUT devuelven el recurso creado/actualizado, no
necesita saber que su acción tuvo solo 200 OK. El frontend puede mostrarlo inmediatamente sin hacer otro
efecto GET.

Mensajes de error claros — el El GlobalExceptionHandler que diseñaste devuelve mensajes de error


usuario no sabe de HTTP comprensibles, no stack traces técnicos.

Velocidad — la espera destruye la Paginación en listas grandes, caché para recursos estáticos, respuestas
experiencia compactas sin campos innecesarios.

Consistencia — el comportamiento Las convenciones REST que aprendiste (mismos códigos HTTP para las
debe ser predecible mismas situaciones) hacen la API predecible para el frontend.

Prevención de errores — mejor Validación en el backend (@Valid, @NotBlank) con mensajes claros por
evitar el error que gestionarlo campo. El frontend puede mostrarlos junto al input correcto.
IFCD0182 — Desarrollo Web con Java

6.3 Principios básicos de UI que debes conocer


✔ BUENAS PRÁCTICAS
Los fundamentos de UI que un backend developer necesita:
• Jerarquía visual: Lo más importante debe ser lo más visible. Usa tamaño, peso (negrita) y
color para guiar la atención del usuario.
• Contraste: El texto debe tener suficiente contraste con el fondo para ser legible. Mínimo
ratio 4.5:1 (WCAG AA). Usa un checker online.
• Espacio en blanco: El espacio vacío no es espacio perdido. Mejora la legibilidad y la
sensación de orden.
• Consistencia: Los botones del mismo tipo deben verse igual en toda la aplicación. Define
un sistema de colores y respétalo.
• Mobile first: Diseña para móvil primero y adapta a pantallas más grandes. El 60% del
tráfico web es móvil.
• Feedback visual: Los botones cambian al hacer hover. Los inputs muestran foco. Los
formularios confirman el envío. El usuario siempre sabe qué está pasando.
IFCD0182 — Desarrollo Web con Java

7. Errores frecuentes en HTML, CSS y JavaScript

Error Por qué es un problema Solución correcta

Imágenes sin atributo alt Los lectores de pantalla no Siempre alt descriptivo. Para imágenes
pueden describir la imagen. Error decorativas: alt vacío (alt='').
de accesibilidad y SEO.

Usar <div> para todo en lugar de El código es más difícil de leer y Usa <header>, <main>, <nav>,
etiquetas semánticas los lectores de pantalla no <article>, <footer>, <section> según el
entienden la estructura. contenido.

Eliminar el outline de focus en Los usuarios que navegan con Nunca elimines el outline sin
CSS (outline: none) teclado no ven dónde está el foco. reemplazarlo por otro indicador visual
Grave problema de accesibilidad. de foco.

fetch() sin gestión de errores Si la API falla, el usuario ve una Siempre usa try/catch con fetch y
página rota o en blanco sin ningún muestra al usuario un mensaje de error
mensaje. comprensible.

Contraseñas o tokens en el El código JS es visible para Las claves secretas solo van en el
código JavaScript cualquiera que abra las DevTools backend. En el frontend solo van tokens
del navegador. temporales de sesión.

No usar preventDefault() en el El formulario recarga la página al [Link]() es obligatorio


submit del formulario enviarse, perdiendo el estado de al interceptar el submit de un
la aplicación. formulario con JS.

8. Actividad práctica guiada

Título Página HTML que consume tu API REST con fetch() y JWT

Duración 50 minutos

Objetivo Crear una página HTML+CSS+JS que liste productos de tu API, permita añadir uno y
gestione la autenticación JWT en el navegador

Nivel Guiado — el formador explica cada bloque antes de implementarlo

1 Crea la estructura HTML semántica


Fichero [Link] con <header> (título + nav), <main> con dos secciones: #login y #productos
(inicialmente oculta). Usa etiquetas semánticas correctas y atributos de accesibilidad en los
formularios.

2 Añade los estilos CSS básicos


Fichero [Link] con: reset de box-sizing, tipografía base, estilos del header, layout de
tarjetas de producto con Flexbox, estilos del formulario de login. Media query para móvil.
IFCD0182 — Desarrollo Web con Java

3 Implementa el login con fetch()


En [Link]: escucha el submit del formulario de login. Llama a POST /api/auth/login con las
credenciales. Si la respuesta es 200, guarda el token en sessionStorage y muestra la sección de
productos ocultando el login. Si es 401, muestra el error junto al formulario.

4 Implementa la carga de productos


Función cargarProductos() que llama a GET /api/productos con el JWT en la cabecera
Authorization. Procesa la respuesta JSON y genera las tarjetas en el DOM. Gestiona el error
401 (token expirado) redirigiendo al login.

5 Verifica la accesibilidad
Navega por la página solo con el teclado (Tab, Enter, Esc). Verifica que el foco es visible en
todo momento. Comprueba que las imágenes tienen alt. Abre las DevTools → Lighthouse →
Accessibility y verifica la puntuación.

🤖 CON AYUDA DE LA IA
Usa IA para mejorar el CSS y la accesibilidad

"Tengo esta página HTML que consume una API REST Spring Boot con JWT [pega el código HTML
y CSS]. Revísala desde el punto de vista de: 1) Accesibilidad WCAG (etiquetas, ARIA, contraste), 2)
CSS — ¿el layout con Flexbox está bien implementado para móvil y desktop?, 3) JavaScript — ¿el
fetch() gestiona todos los errores correctamente? Dame correcciones concretas."
IFCD0182 — Desarrollo Web con Java

9. Actividad práctica autónoma

Título Panel de gestión completo que consume la API REST del Módulo 3

Duración 60 minutos

Objetivo Construir autónomamente un panel HTML/CSS/JS completo con login JWT, listado,
creación y eliminación de recursos, con accesibilidad básica verificada

Nivel Autónomo — MDN Web Docs, apuntes e IA disponibles

Entrega Tres ficheros: [Link], [Link], [Link] + captura de Lighthouse Accessibility

Enunciado
Crea un panel de gestión de empleados (o el recurso de tu API del Módulo 3) con las siguientes
funcionalidades:

• Página de login: Formulario accesible con email y contraseña. Llama al endpoint JWT. Guarda el
token. Muestra errores junto al campo correspondiente.
• Panel principal (visible solo autenticado): Tabla/lista de todos los recursos cargada con fetch().
Botón de logout que borra el token y vuelve al login.
• Formulario de creación: Un formulario inline o modal para crear un nuevo recurso. Llama al POST de
tu API con el JWT. Actualiza la lista tras el éxito.
• Eliminación: Botón 'Eliminar' en cada elemento de la lista. Pide confirmación antes de llamar al
DELETE. Actualiza la lista tras el éxito.
• Accesibilidad mínima: HTML semántico, labels vinculados a inputs, focus visible, alt en imágenes,
mensajes de error en aria-live.

Criterios de evaluación
Criterio Descripción Peso

Login JWT funcional Login llama a la API, guarda token, muestra/oculta 25%
secciones correctamente

Listado con fetch() GET funcional, datos en el DOM, gestión de error 401 20%

Creación y eliminación POST y DELETE funcionan con JWT, la lista se 25%


actualiza tras cada operación

Accesibilidad Lighthouse Accessibility ≥ 80, HTML semántico, focus 20%


visible

CSS responsivo La página es usable en móvil y en desktop 10%


IFCD0182 — Desarrollo Web con Java

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir ayuda con una propiedad CSS concreta, preguntar cómo funciona un
atributo ARIA específico, depurar por qué el fetch() no envía el token.
• No permitido: generar el [Link] completo, el [Link] completo o los estilos
completos.
• Obligatorio: la captura de Lighthouse debe ser real — ejecutada por el alumno en su
navegador, no generada por IA.
IFCD0182 — Desarrollo Web con Java

10. Resumen de la unidad

Concepto Lo esencial

HTML Estructura y contenido de la página. Usa etiquetas semánticas: header, main, nav,
article, section, footer.

CSS Apariencia de la página. Box model, selectores, Flexbox para layouts, media
queries para responsividad.

JavaScript Comportamiento e interacción. Manipulación del DOM, eventos, async/await con


fetch().

fetch() Función nativa del navegador para llamar a APIs REST. Devuelve una Promise.
Siempre con try/catch.

JWT en el frontend El token se guarda en sessionStorage o memoria. Se envía en la cabecera


Authorization: Bearer token.

DOM Document Object Model. La representación en memoria del HTML. JavaScript lo


lee y modifica.

Accesibilidad WCAG: Perceptible, Operable, Comprensible, Robusto. Alt en imágenes, labels en


inputs, focus visible.

ARIA Atributos que describen la semántica a los lectores de pantalla: aria-label, aria-
live, aria-expanded.

UX Experiencia de usuario — cómo se siente al usar el producto. Afecta al diseño de la


API.

UI Interfaz de usuario — cómo se ve. Contraste, jerarquía visual, consistencia, mobile


first.

Mobile First Diseña para móvil primero. Usa min-width en media queries para adaptar a
pantallas más grandes.

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
8. ¿Cuál es la diferencia entre HTML, CSS y JavaScript? ¿Puede cada uno hacer el trabajo de
los otros?
9. ¿Qué es el DOM y cómo lo modifica JavaScript?
10. ¿Por qué es importante usar etiquetas HTML semánticas en lugar de solo <div>?
11. ¿Qué hace [Link]() cuando se usa en el submit de un formulario?
12. ¿Cómo se envía un JWT desde JavaScript al hacer una petición a una API REST protegida?
13. ¿Qué es la accesibilidad web y por qué eliminar el outline de focus es un problema
grave?
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Consolidación antes de la Unidad 2:

"Tengo el HTML y JavaScript de una página que consume mi API REST Spring Boot. El
fetch('/api/empleados') funciona pero cuando intento hacer el POST para crear un nuevo
empleado con el JWT, obtengo 401 Unauthorized aunque el token es válido. ¿Qué puede estar
fallando? Analiza estas posibles causas: cabecera Authorization mal formada, token expirado,
CORS, Content-Type incorrecto."

📊 NOTA DE MERCADO LABORAL


Lo que viene en la Unidad 2:
En la Unidad 2 explorarás los frameworks de frontend más demandados: React, Angular y Vue.
Entenderás qué problema resuelve cada uno, en qué se diferencian y cómo consumen APIs REST.
Todo lo que has aprendido en esta unidad —HTML semántico, CSS, fetch(), JWT en el frontend— es
la base sobre la que se construyen esos frameworks.
IFCD0182 — Desarrollo Web con Java

MÓDULO 4 — Integración con Frontend


UNIDAD 2

Frameworks Frontend: React, Angular y Vue


Los tres grandes frameworks que consumen tu API REST: conceptos comunes, diferencias
clave y cómo elegir el adecuado

Duración estimada 5 horas — comparativa y conceptos comunes

Módulo 4 — Integración con Frontend

Unidad anterior Unidad 1 — HTML, CSS, JavaScript y UX/UI

Nivel de entrada fetch() y JWT en el navegador funcionando. JavaScript básico manejado.

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Qué son los frameworks frontend, qué problema resuelven, los conceptos
comunes a todos ellos y las diferencias entre React, Angular y Vue para poder
colaborar con cualquier equipo frontend

🔗 CONEXIÓN CON EL BACKEND


Por qué esta unidad es importante para ti como backend developer:
Cuando trabajas en una empresa con equipo frontend, el frontend usará uno de estos tres
frameworks para consumir tu API. Entender cómo funcionan te permite diseñar mejores APIs,
depurar problemas de integración y comunicarte con el equipo frontend sin barreras.

No necesitas dominarlos. Necesitas entender sus conceptos para ser un backend developer
completo.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Explicar qué problema resuelven los frameworks frontend y por qué nacieron.
• Describir el concepto de componente y su ciclo de vida.
• Entender el Virtual DOM y la reactividad declarativa.
• Reconocer la sintaxis básica de React, Angular y Vue.
• Comparar los tres frameworks en criterios clave: curva de aprendizaje, ecosistema, uso
empresarial.
• Saber cuándo recomendar cada framework según el tipo de proyecto.
• Entender cómo cada framework consume tu API REST con JWT.
IFCD0182 — Desarrollo Web con Java

1. ¿Por qué existen los frameworks frontend?

1.1 El problema del JavaScript «vanilla»


En la Unidad 1 usaste JavaScript para manipular el DOM con getElementById y innerHTML. Eso funciona para
páginas simples. Pero en aplicaciones complejas con decenas de vistas, cientos de componentes y datos que
cambian constantemente, el código JavaScript «vanilla» se convierte en un caos difícil de mantener.

✘ ERROR FRECUENTE
Problemas del JavaScript manual en aplicaciones grandes:
• Sincronizar el estado (datos) con la vista (HTML) manualmente es propenso a errores.
• El código se vuelve espagueti: difícil de leer, modificar y reutilizar.
• Sin estructura, dos desarrolladores trabajando en el mismo código crean conflictos
constantes.
• Reutilizar componentes de UI (botones, formularios, tablas) requiere copiar y pegar
código.
• Las aplicaciones de una sola página (SPA) son muy difíciles de implementar sin
herramientas.

1.2 La solución: componentes y reactividad declarativa


Los frameworks frontend modernos (React, Angular, Vue) resuelven estos problemas con dos conceptos
fundamentales:

Concepto Qué significa El beneficio

Componentes La UI se divide en piezas reutilizables e Reutilización, organización, equipos


independientes. Cada componente tiene su separados trabajando en
propio HTML, CSS y lógica. componentes distintos.

Reactividad Defines cómo debe verse la UI según el Ya no tienes que manipular el DOM
declarativa estado (datos). El framework actualiza el manualmente. El framework lo
DOM automáticamente cuando el estado sincroniza solo.
cambia.

👁 FÍJATE
Analogía con Spring MVC que ya conoces:
En Spring MVC defines la lógica en el @Controller y la presentación en la vista Thymeleaf. El
framework gestiona el ciclo petición-respuesta. Con un framework frontend defines la lógica en el
componente y la UI en la plantilla. El framework gestiona la actualización del DOM cuando cambian
los datos.
IFCD0182 — Desarrollo Web con Java

2. Conceptos comunes a React, Angular y Vue

Los tres frameworks comparten los mismos conceptos fundamentales aunque con sintaxis diferente.
Entenderlos una vez te permite trabajar con cualquiera de los tres.

2.1 Componentes — La unidad básica de construcción


Un componente es una pieza independiente y reutilizable de la interfaz de usuario. Tiene su propio estado,
su propia lógica y su propia apariencia. Puedes pensar en ellos como los @Service y @Controller de tu
backend: cada uno tiene una responsabilidad específica.

Característica del componente Descripción

Encapsulación El componente gestiona su propio HTML, estilos y lógica de forma


independiente

Reutilización El mismo componente TarjetaProducto se usa en el catálogo, en el carrito


y en el pedido

Comunicación Los componentes se comunican con Props (datos de padre a hijo) y Events
(eventos de hijo a padre)

Ciclo de vida Los componentes tienen fases: montaje, actualización y desmontaje —


equivalente al ciclo de vida de Spring Beans

Composición Los componentes se anidan: una Página contiene un Header, un Main y un


Footer, cada uno es un componente

2.2 Estado (State) — Los datos del componente


El estado es el conjunto de datos que un componente gestiona internamente. Cuando el estado cambia, el
framework actualiza automáticamente el DOM para reflejar el nuevo valor. Nunca modificas el DOM
directamente — modificas el estado y el framework hace el resto.

⚙ CÓMO FUNCIONA
El ciclo de la reactividad:
1. El componente tiene un estado inicial (ej: { productos: [], cargando: true }).
2. El componente llama a tu API con fetch() al montarse.
3. Cuando la API responde, actualiza el estado: { productos: [...], cargando: false }.
4. El framework detecta el cambio de estado y actualiza el DOM automáticamente.
5. El usuario ve la lista de productos sin que hayas escrito una sola línea de DOM
manipulation.
IFCD0182 — Desarrollo Web con Java

2.3 Props — Pasar datos entre componentes


Los props (propiedades) son datos que un componente padre pasa a un componente hijo. Son de solo
lectura: el hijo no puede modificar los props que recibe. Es como los parámetros de un método Java.
2.4 El Virtual DOM — Por qué los frameworks son rápidos
Manipular el DOM real del navegador es lento. Los frameworks modernos mantienen una copia en memoria
del DOM (el Virtual DOM) y comparan el DOM virtual actual con el nuevo cuando cambia el estado. Solo
actualizan en el DOM real los nodos que han cambiado. Esto hace la actualización de la UI extremadamente
eficiente.
2.5 Routing — Navegación sin recargar la página
Las SPA (Single Page Applications) no recargan la página al navegar. El routing del lado del cliente simula la
navegación interceptando los cambios de URL y mostrando el componente correspondiente. Para el usuario
se comporta como una app normal, pero todas las peticiones de navegación se gestionan en JavaScript.

🔗 CONEXIÓN CON EL BACKEND


El routing frontend y tu API backend:
Cuando el frontend tiene routing en el cliente (/productos, /carrito, /perfil), estas URLs no llegan a tu
backend. El backend solo recibe peticiones a /api/... El servidor Spring Boot debe devolver siempre el
[Link] para cualquier URL que no sea una petición a la API. El frontend gestiona el resto.

3. React — La librería de UI de Meta

3.1 ¿Qué es React?


React (creado por Facebook/Meta en 2013) es técnicamente una librería de UI, no un framework completo.
Se centra exclusivamente en la capa de vista. Para routing, gestión de estado global, llamadas a APIs, etc.,
necesitas librerías adicionales (React Router, Redux, Axios...).

Es el más popular de los tres en el mercado laboral actual. Su enfoque en componentes y su ecosistema
masivo lo convierten en la primera elección para muchos proyectos.

⚛ REACT
Características principales de React:
• JSX: Sintaxis que mezcla JavaScript y HTML en el mismo archivo. Parece HTML pero es
JavaScript transformado en tiempo de compilación.
• Hooks: Funciones especiales (useState, useEffect, useCallback...) que añaden estado y
ciclo de vida a los componentes funcionales.
IFCD0182 — Desarrollo Web con Java

• Componentes funcionales: La forma moderna de escribir React. Funciones JavaScript


que devuelven JSX.
• Unidireccional: El flujo de datos siempre va de padre a hijo (Props). Para comunicación
compleja se usan Context o Redux.
• Ecosistema enorme: Miles de librerías compatibles para cualquier necesidad.

3.2 Un componente React completo que consume tu API

// [Link] — Componente React funcional


import { useState, useEffect } from 'react';

// Un componente React es simplemente una función que devuelve JSX


function ListaProductos() {
// useState: declara el estado del componente
// productos: el valor actual | setProductos: función para actualizar
const [productos, setProductos] = useState([]);
const [cargando, setCargando] = useState(true);
const [error, setError] = useState(null);

// useEffect: se ejecuta después de que el componente se monta


// El array vacío [] significa que solo se ejecuta una vez (al montar)
useEffect(() => {
const token = [Link]('jwt_token');

fetch('[Link] {
headers: { 'Authorization': `Bearer ${token}` }
})
.then(res => {
if (![Link]) throw new Error(`Error ${[Link]}`);
return [Link]();
})
.then(data => {
setProductos(data);
setCargando(false);
})
.catch(err => {
setError([Link]);
setCargando(false);
});
}, []);

// El componente devuelve JSX — parece HTML pero es JavaScript


if (cargando) return <p>Cargando productos...</p>;
if (error) return <p>Error: {error}</p>;

return (
<div className="lista-productos">
<h2>Catálogo de productos</h2>
{/* .map() genera un componente por cada producto */}
IFCD0182 — Desarrollo Web con Java
{[Link](p => (
// key: identificador único — necesario en listas React
<div key={[Link]} className="tarjeta-producto">
<h3>{[Link]}</h3>
<p>Precio: {[Link]} €</p>
<p>Stock: {[Link]} unidades</p>
</div>
))}
</div>
);
}

export default ListaProductos;

3.3 Los hooks más usados en React

Hook Para qué sirve Equivalente conceptual en Spring

useState(valorInicial) Declara un estado en el componente. @Value o campo en el Bean


Devuelve [valor, setter].

useEffect(fn, deps) Ejecuta código cuando el componente se @PostConstruct o inicialización del


monta o cuando cambian las deps. Bean

useCallback(fn, deps) Memoriza una función para evitar re- Caché de método (conceptualmente)
renders innecesarios.

useMemo(fn, deps) Memoriza el resultado de un cálculo Caché de resultado


costoso.

useContext(contexto) Accede a datos globales sin pasar props Inyección de dependencias


por toda la jerarquía. (@Autowired)

useRef(valorInicial) Referencia a un elemento del DOM o Variable de instancia que no cambia


valor que persiste sin re-render.

4. Angular — El framework completo de Google

4.1 ¿Qué es Angular?


Angular (creado por Google en 2016, reimaginación del antiguo AngularJS) es un framework completo y
opinado. A diferencia de React, Angular incluye todo: routing, gestión de estado, HTTP client, validación de
formularios, testing... sin necesidad de librerías externas.

Usa TypeScript de forma obligatoria (JavaScript con tipado estático, muy similar a Java en su filosofía). Es
muy popular en proyectos enterprise y aplicaciones de gran escala.
IFCD0182 — Desarrollo Web con Java

🔺 ANGULAR
Características principales de Angular:
• TypeScript obligatorio: Tipado estático que detecta errores en tiempo de compilación.
Muy familiar para desarrolladores Java.
• Inyección de dependencias: El mismo concepto que Spring IoC. Los servicios se declaran
con @Injectable y se inyectan en los componentes.
• Framework completo: Routing, HTTP client, formularios reactivos, testing — todo
incluido y bien integrado.
• Decoradores: @Component, @Injectable, @NgModule... similar a las anotaciones Java
(@Controller, @Service...).
• Angular CLI: Herramienta de línea de comandos para generar componentes, servicios y
módulos automáticamente.

4.2 Un componente Angular que consume tu API

// [Link] — Componente Angular con TypeScript


import { Component, OnInit } from '@angular/core';
import { ProductoService } from './[Link]';

// @Component: decorador que define el componente (como @Controller en Spring)


@Component({
selector: 'app-productos', // Etiqueta HTML del componente
templateUrl: './[Link]', // La vista separada en otro fichero
styleUrls: ['./[Link]']
})
// implements OnInit: interfaz con el método ngOnInit (como @PostConstruct)
export class ProductosComponent implements OnInit {
productos: any[] = [];
cargando = true;

// Inyección de dependencias — igual que en Spring Boot


constructor(private productoService: ProductoService) {}

// ngOnInit: se ejecuta al montar el componente (como @PostConstruct)


ngOnInit(): void {
[Link]().subscribe({
next: (data) => {
[Link] = data;
[Link] = false;
},
error: (err) => [Link]('Error:', err)
});
}
}

// [Link] — Servicio Angular (como @Service en Spring)


IFCD0182 — Desarrollo Web con Java
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Injectable({ providedIn: 'root' })


export class ProductoService {
private apiUrl = '[Link]

// HttpClient: el equivalente a RestTemplate o WebClient de Spring


constructor(private http: HttpClient) {}

getAll() {
const token = [Link]('jwt_token');
const headers = new HttpHeaders({
'Authorization': `Bearer ${token}`
});
return [Link]<any[]>([Link], { headers });
}
}

👁 FÍJATE
¿Ves las similitudes con Spring Boot? @Component es como @Controller, @Injectable es como
@Service, la inyección en el constructor es exactamente el mismo patrón que Spring Security.
TypeScript con clases tipadas es muy similar a Java. Angular es el framework frontend que más se
parece al desarrollo backend Java.

5. Vue — El framework progresivo

5.1 ¿Qué es Vue?


Vue (creado por Evan You en 2014, ex-desarrollador de Google) es el más sencillo de los tres en curva de
aprendizaje. Es progresivo: puedes añadirlo a una página HTML existente con un script, o usarlo para
construir una SPA completa con Vue CLI.

Es muy popular en proyectos medianos, startups y en el mercado asiático. La sintaxis de los Single File
Components (.vue) que combina template, script y style en un solo fichero es muy intuitiva.

🟢 VUE
Características principales de Vue:
• Curva de aprendizaje suave: La más baja de los tres. HTML conocido con directivas (v-if,
v-for, v-bind, v-model).
• Single File Components (.vue): Template, script y style en un único archivo — fácil de
leer y mantener.
IFCD0182 — Desarrollo Web con Java

• Reactivo por defecto: Los datos del componente son reactivos automáticamente.
Cuando cambian, la UI se actualiza sola.
• Composition API (Vue 3): Forma moderna de organizar la lógica del componente, similar
a los hooks de React.
• Progresivo: Puedes añadirlo a cualquier proyecto HTML sin necesidad de compilación, o
usarlo en SPA completas.

5.2 Un componente Vue que consume tu API

<!-- [Link] — Single File Component -->


<!-- Los tres bloques en un solo fichero: template, script y style -->

<!-- TEMPLATE: la vista HTML con directivas Vue -->


<template>
<div class="lista-productos">
<h2>Catálogo de productos</h2>

<!-- v-if: equivale al th:if de Thymeleaf -->


<p v-if="cargando">Cargando productos...</p>

<!-- v-for: itera la lista — equivale al th:each de Thymeleaf -->


<div v-for="producto in productos" :key="[Link]"
class="tarjeta-producto">
<!-- {{ }}: interpolación de texto — como ${} en Thymeleaf -->
<h3>{{ [Link] }}</h3>
<p>Precio: {{ [Link] }} €</p>
<p>Stock: {{ [Link] }} unidades</p>
</div>
</div>
</template>

<!-- SCRIPT: la lógica del componente con Composition API (Vue 3) -->
<script setup>
import { ref, onMounted } from 'vue';

// ref(): crea un dato reactivo — equivale a useState en React


const productos = ref([]);
const cargando = ref(true);

// onMounted: se ejecuta al montar el componente — como useEffect(fn, [])


onMounted(async () => {
try {
const token = [Link]('jwt_token');
const res = await fetch('[Link] {
headers: { 'Authorization': `Bearer ${token}` }
});
[Link] = await [Link]();
} finally {
[Link] = false;
IFCD0182 — Desarrollo Web con Java
}
});
</script>

<!-- STYLE: estilos del componente (scoped: solo aplican a este componente) -->
<style scoped>
.lista-productos { padding: 20px; }
.tarjeta-producto { border: 1px solid #ccc; padding: 16px; margin: 8px; }
</style>

👁 FÍJATE
¿Ves las similitudes con Thymeleaf? v-if es como th:if. v-for es como th:each. {{ }} es como ${} de EL.
Si vienes de Thymeleaf, la plantilla de Vue te resultará muy familiar. La diferencia es que Vue se
ejecuta en el navegador, Thymeleaf se ejecuta en el servidor.

6. Comparativa completa — ¿Cuál usar?

Criterio React ⚛ Angular 🔺 Vue 🟢

Tipo Librería UI + ecosistema Framework completo Framework progresivo

Lenguaje JavaScript / TypeScript TypeScript obligatorio JavaScript / TypeScript

Curva aprendizaje Media Alta Baja — la más amigable

Creado por Meta (Facebook) Google Evan You (ex-Google)

Primera versión 2013 2016 (reescrito) 2014

Popularidad global #1 — más demandado #2 — muy demandado #3 — creciendo

Ecosistema Enorme — miles de Completo — todo Bueno — bien equilibrado


librerías incluido

Ideal para SPAs, startups, proyectos Enterprise, proyectos Proyectos medianos, curva
con equipos JS grandes, equipos Java suave, prototipos rápidos

Similitud con Java Media Alta — DI, decoradores, Media-baja


TypeScript

Offerta laboral ES Muy alta Alta (sobre todo banca y Media


enterprise)

📊 NOTA DE MERCADO LABORAL


¿Cuál aprender como backend developer?
Si tu objetivo es el backend Java y quieres saber lo mínimo de frontend para colaborar con el equipo:
empieza por Vue. La curva es la más suave y en pocas horas puedes hacer algo funcional.

Si quieres ampliar hacia fullstack y trabajar en España: aprende React. Es el más demandado y el que
más oportunidades laborales te da.
IFCD0182 — Desarrollo Web con Java

Si trabajas en banca, sector público o grandes corporaciones Java: Angular es frecuente porque su
filosofía y TypeScript son muy cercanos al mundo Java enterprise.

7. Cómo el frontend consume tu API Spring Boot

7.1 El patrón de comunicación frontend-backend


Independientemente del framework elegido, el patrón de comunicación con tu API Spring Boot es siempre el
mismo:

Paso Qué hace el frontend Qué hace tu backend Spring Boot

Login Envía POST /api/auth/login con credenciales AuthController devuelve 200 con el JWT
JSON

Guardar Guarda el JWT en localStorage o memory state No hace nada — es stateless


token

Petición Envía GET /api/productos con Authorization: JwtAuthFilter valida el token, @Controller
API Bearer token responde

Error Token expirado — llama a POST Valida el refresh token, devuelve nuevo access
401 /api/auth/refresh token

Logout Borra el JWT del storage, redirige al login POST /api/auth/logout invalida el refresh token
en BD

7.2 CORS — El problema de los orígenes diferentes


Cuando el frontend (en localhost:3000 o localhost:4200) llama a tu API (en localhost:8080), el navegador
bloquea la petición por la política CORS. Ya viste esto en el Módulo 2. Recuerda que la solución está en el
backend:

// En tu SecurityConfig o CorsConfig de Spring Boot:


public void addCorsMappings(CorsRegistry registry) {
[Link]("/api/**")
// Permite peticiones desde los puertos de dev de cada framework
.allowedOrigins(
"[Link] // React (Create React App)
"[Link] // React (Vite) / Vue (Vite)
"[Link] // Angular CLI
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
IFCD0182 — Desarrollo Web con Java

7.3 Herramientas de desarrollo y puertos por defecto

Framework Herramienta de creación Puerto por defecto Comando para arrancar

React Create React App (CRA) o Vite 3000 (CRA) / 5173 (Vite) npm start / npm run dev

Angular Angular CLI (@angular/cli) 4200 ng serve

Vue Vue CLI o Vite 8080 (CLI) / 5173 (Vite) npm run serve / npm run
dev

⚠ ATENCIÓN
Vue CLI usa por defecto el puerto 8080, el mismo que Spring Boot. Si arrancas ambos a la vez tendrás
un conflicto. Cambia el puerto de Vue en [Link] ([Link]: 8081) o cambia el de Spring
Boot en [Link] ([Link]: 8090).

8. Errores frecuentes al integrar frontend con Spring Boot

Error / Síntoma Causa habitual Solución

Error CORS en el navegador al El backend no tiene configurado Añade la URL del frontend en
llamar a la API CORS para el origen del frontend CorsConfig (addCorsMappings). Incluye
todos los puertos de desarrollo.

El JWT llega al backend pero el El frontend no incluye el prefijo Verifica que el header es exactamente:
filtro devuelve 401 'Bearer ' (con espacio) antes del Authorization: Bearer eyJ... (con
token espacio tras Bearer)

El componente hace el fetch Se modifica una variable local en En React: usa setProductos(data), no
pero los datos no aparecen en la lugar del estado reactivo del productos = data. En Vue: usa
UI framework [Link] = data, no productos =
data

Las rutas del frontend devuelven Spring Boot no sabe que Configura Spring Boot para devolver
404 al refrescar la página /productos es una ruta del [Link] para todas las rutas que no
frontend y busca un @Controller empiecen por /api. Es el patrón SPA
para esa URL fallback.

El token JWT expira durante la No hay interceptor que detecte el Implementa un interceptor HTTP que
sesión y el usuario recibe 401 401 y renueve el token detecte 401, llame al endpoint de
inesperado automáticamente refresh y reintente la petición original
con el nuevo token.
IFCD0182 — Desarrollo Web con Java

9. Actividad práctica guiada

Título Primer componente React/Vue que consume la API Spring Boot

Duración 50 minutos

Objetivo Crear el primer proyecto React o Vue (según elección del grupo) y hacer que muestre
datos de tu API REST con autenticación JWT

Nivel Guiado — el formador elige el framework y explica cada paso

1 Crear el proyecto con Vite (React o Vue)


En la terminal: npm create vite@latest mi-frontend -- --template react (o vue). cd mi-frontend
&& npm install && npm run dev. Verifica que el servidor arranca en localhost:5173.

2 Configurar CORS en Spring Boot para el puerto 5173


Añade [Link] a la lista de orígenes permitidos en CorsConfig. Reinicia el
backend. Verifica que no hay error CORS con la herramienta de red de las DevTools.

3 Crear el componente de Login


Formulario con email y contraseña. Al hacer submit, llama a POST /api/auth/login. Si la
respuesta es 200, guarda el token en localStorage y actualiza el estado para mostrar el panel
de productos.

4 Crear el componente de Lista de Productos


Al montarse, llama a GET /api/productos con el JWT en la cabecera. Muestra los productos en
tarjetas. Si llega un 401, borra el token y muestra el Login.

5 Verificar el flujo completo


Login → token → lista de productos → abre las DevTools → Network → verifica que las
peticiones llevan la cabecera Authorization con el Bearer token → verifica que el backend
responde 200.
IFCD0182 — Desarrollo Web con Java

🤖 CON AYUDA DE LA IA
Depura la integración con IA

"Mi componente React hace el fetch a /api/productos con el JWT pero recibo CORS error en el
navegador aunque tengo CorsConfig en Spring Boot. Aquí está mi CorsConfig [pega el código] y el
error exacto de la consola [pega el error]. ¿Qué puede estar fallando?"
Los errores CORS son de los más frustrantes en la integración frontend-backend. La IA puede
identificar si falta el puerto, si el método OPTIONS no está permitido o si hay algún interceptor de
Spring Security que está bloqueando antes de que llegue a CorsConfig.
IFCD0182 — Desarrollo Web con Java

10. Actividad práctica autónoma

Título Mini SPA con React o Vue que consume la API del Módulo 3 con autenticación JWT
completa

Duración 60 minutos

Objetivo Desarrollar autónomamente una SPA con login JWT, listado, creación y gestión de
sesión

Nivel Autónomo — documentación del framework elegido e IA disponibles

Entrega Proyecto frontend en .zip + captura de las DevTools mostrando el JWT en la cabecera
Authorization

Enunciado
Usando el framework de tu elección (React con Vite o Vue con Vite), construye una mini SPA que consuma la
API del Módulo 3 (Spring Security + JWT):

• Pantalla de Login: Formulario que llama a POST /api/auth/login. Guarda el token. Gestiona el error
401 mostrando 'Credenciales incorrectas' sin recargar la página.
• Panel principal: Solo visible si hay token. Lista todos los recursos de tu API. Muestra el nombre del
usuario autenticado en el header (decodifica el JWT para extraer el claim 'sub').
• Crear recurso: Formulario inline o modal. Llama al POST de tu API con el JWT. Actualiza la lista tras el
éxito sin recargar la página.
• Logout: Botón que borra el token del storage y muestra el Login. Llama al endpoint de logout del
backend si existe.
• Gestión de expiración: Si cualquier petición devuelve 401, muestra al usuario 'Tu sesión ha expirado'
y redirige al Login.

Criterios de evaluación
Criterio Descripción Peso

Login JWT funcional Llama a la API, guarda el token, muestra/oculta 25%


secciones, gestiona 401

Listado reactivo GET con JWT, datos en el estado del framework, UI 20%
actualizada sin recargar

Creación de recurso POST con JWT, lista se actualiza automáticamente 20%


tras el éxito

Logout y gestión de sesión Borra token, redirige al login, gestiona expiración 20%
del token

Captura DevTools Muestra la cabecera Authorization: Bearer en una 15%


petición real
IFCD0182 — Desarrollo Web con Java

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir ayuda con la sintaxis de un hook concreto, preguntar cómo decodificar
un JWT en JavaScript, depurar errores CORS.
• No permitido: generar el componente de login completo, el componente de lista
completo o el [Link]/[Link] principal.
• Obligatorio: la captura de DevTools debe mostrar una petición real de tu aplicación — no
generada por IA ni simulada.

11. Resumen de la unidad

Concepto Lo esencial

Frameworks frontend Resuelven el caos del JS manual con componentes reutilizables y reactividad
declarativa.

Componente Pieza reutilizable e independiente de UI con estado, lógica y plantilla propios.

Estado (State) Datos del componente. Cuando cambia, el framework actualiza el DOM
automáticamente.

Props Datos que el padre pasa al hijo. Solo lectura en el hijo.

Reactividad Defines cómo se ve la UI según el estado. El framework la mantiene


sincronizada.

Virtual DOM Copia en memoria del DOM. El framework compara y solo actualiza lo que
cambia.

React Librería UI de Meta. JSX, Hooks (useState, useEffect). El más popular en el


mercado.

Angular Framework completo de Google. TypeScript, DI, decoradores. Muy parecido a


Java Spring.

Vue Framework progresivo. Curva de aprendizaje suave. v-if, v-for similares a


Thymeleaf.

CORS Configurar allowedOrigins en Spring Boot para los puertos del framework
frontend.

Puerto 5173 Vite (React y Vue). 4200: Angular. Añadir a la configuración CORS de Spring
Boot.

SPA fallback Spring Boot devuelve [Link] para rutas del frontend que no son /api/*.
IFCD0182 — Desarrollo Web con Java

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
6. ¿Qué problema resuelven los frameworks frontend que el JavaScript vanilla no resuelve
bien?
7. ¿Qué es el Virtual DOM y qué ventaja tiene sobre manipular el DOM real directamente?
8. ¿En qué se parece Angular a Spring Boot? Menciona al menos tres similitudes concretas.
9. ¿Por qué Vue resulta familiar si vienes de Thymeleaf?
10. ¿Cuándo usarías React, Angular o Vue? ¿Hay una respuesta correcta universal?
11. ¿Qué debes configurar en Spring Boot para que un frontend en localhost:5173 pueda
llamar a tu API?

🤖 CON AYUDA DE LA IA
Consolidación antes de la Unidad 3:

"Soy desarrollador backend Java con Spring Boot y quiero entender mejor los frameworks
frontend. Explícame en qué se parecen y en qué se diferencian React, Angular y Vue desde el
punto de vista de alguien que conoce Spring Boot. Usa analogías con Spring: @Controller,
@Service, inyección de dependencias, Thymeleaf... para que los conceptos me resulten
familiares."

📊 NOTA DE MERCADO LABORAL


Lo que viene en la Unidad 3:
En la Unidad 3 profundizarás en la comunicación real entre frontend y backend: cómo estructurar el
consumo de APIs REST, cómo gestionar el estado global de la autenticación, cómo manejar errores
de red de forma consistente y los patrones más usados en proyectos reales.
IFCD0182 — Desarrollo Web con Java

MÓDULO 4 — Integración con Frontend


UNIDAD 3

Comunicación Frontend-Backend y SPAs


Patrones reales de consumo de APIs REST: interceptores, gestión de estado, errores de red
y autenticación persistente

Duración estimada 5 horas — enfoque en patrones reales de proyecto

Módulo 4 — Integración con Frontend

Unidad anterior Unidad 2 — Frameworks Frontend: React, Angular y Vue

Nivel de entrada Mini SPA funcional consumiendo la API REST con JWT.

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Los patrones que se usan en proyectos reales: cliente HTTP centralizado,
interceptores JWT, gestión de estado global, manejo de errores de red y
configuración de despliegue SPA

🔗 CONEXIÓN CON EL BACKEND


De la mini SPA de la Unidad 2 a un proyecto real:
En la Unidad 2 hiciste fetch() directamente en cada componente. En proyectos reales eso no escala:
tienes que repetir la lógica del token en cada petición, no hay un lugar centralizado para manejar
errores de red y si el token expira durante la sesión el usuario recibe un error críptico.

Esta unidad te enseña los patrones que usan los equipos profesionales para resolver estos
problemas de forma limpia y mantenible.

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Crear un cliente HTTP centralizado con Axios para todas las llamadas a la API.
• Implementar interceptores que añaden el JWT automáticamente a cada petición.
• Gestionar la expiración del token con refresh automático transparente al usuario.
• Centralizar el manejo de errores de red en un solo lugar.
• Entender qué es el estado global y cuándo necesitas una librería como Redux o Pinia.
• Configurar Spring Boot para servir una SPA correctamente en producción.
• Entender el despliegue fullstack: frontend y backend en el mismo servidor o separados.
IFCD0182 — Desarrollo Web con Java

1. Cliente HTTP centralizado con Axios

1.1 ¿Por qué Axios en lugar de fetch()?


fetch() es la función nativa del navegador y funciona perfectamente para casos sencillos. En proyectos reales
se usa Axios porque añade funcionalidades que en fetch() hay que implementar manualmente:

Aspecto fetch() nativo Axios

Errores HTTP (4xx, 5xx) fetch() NO lanza error en 4xx/5xx — hay Axios lanza automáticamente un error
que comprobar [Link] manualmente para cualquier respuesta no 2xx

Interceptores No existen nativamente — hay que Interceptores de petición y respuesta


envolverlo integrados

Timeout No existe nativo — hay que usar timeout: 5000 en la configuración


AbortController

Serialización JSON Hay que llamar a [Link](body) y Lo serializa/deserializa automáticamente


[Link]([Link]())

Cabeceras por defecto Hay que incluirlas en cada petición Se configuran una vez y se aplican a
manualmente todas

Soporte [Link] No disponible nativamente en Node Funciona en navegador y en [Link]


(solo en navegador)

1.2 Instalar Axios


# Instalar Axios en el proyecto frontend
npm install axios

1.3 Crear el cliente HTTP centralizado


En lugar de llamar a fetch() o axios directamente en cada componente, crea una instancia de Axios
configurada una vez y úsala en toda la aplicación:

// src/api/[Link] — El cliente HTTP centralizado de toda la aplicación


import axios from 'axios';

// Creamos una instancia configurada con la URL base de nuestra API


const apiCliente = [Link]({
baseURL: '[Link]
timeout: 10000, // 10 segundos máximo por petición
headers: {
'Content-Type': 'application/json',
}
});

// ── INTERCEPTOR DE PETICIÓN ─────────────────────────────────────


// Se ejecuta ANTES de enviar cada petición
[Link](
(config) => {
// Añadimos el JWT automáticamente a TODAS las peticiones
const token = [Link]('jwt_token');
IFCD0182 — Desarrollo Web con Java
if (token) {
[Link] = `Bearer ${token}`;
}
return config;
},
(error) => [Link](error)
);

// ── INTERCEPTOR DE RESPUESTA ─────────────────────────────────────


// Se ejecuta DESPUÉS de recibir cada respuesta
[Link](
// Si la respuesta es 2xx, la pasamos tal cual
(response) => response,

// Si hay un error, lo gestionamos centralizadamente


async (error) => {
const peticionOriginal = [Link];

// Si recibimos 401 y no hemos intentado renovar el token todavía


if ([Link]?.status === 401 && !peticionOriginal._reintentado) {
peticionOriginal._reintentado = true;

try {
// Intentamos renovar el token con el refresh token
const refreshToken = [Link]('refresh_token');
const res = await [Link]('[Link]
{ refreshToken });

// Guardamos el nuevo access token


const nuevoToken = [Link];
[Link]('jwt_token', nuevoToken);

// Reintentamos la petición original con el nuevo token


[Link] = `Bearer ${nuevoToken}`;
return apiCliente(peticionOriginal);
} catch (refreshError) {
// El refresh también falló — sesión expirada definitivamente
[Link]('jwt_token');
[Link]('refresh_token');
// Redirigimos al login
[Link] = '/login';
return [Link](refreshError);
}
}

// Para cualquier otro error, lo propagamos para que el componente lo gestione


return [Link](error);
}
);

export default apiCliente;


IFCD0182 — Desarrollo Web con Java

✔ BUENAS PRÁCTICAS
Lo que has conseguido con este cliente centralizado:
• El JWT se añade automáticamente a todas las peticiones. Los componentes no saben
nada del token.
• Si el token expira, el interceptor lo renueva de forma transparente y reintenta la petición
original. El usuario no nota nada.
• Si el refresh también falla, el usuario es redirigido al login automáticamente.
• Los componentes solo hacen [Link]('/productos') y todo lo demás ocurre
automáticamente.

1.4 Usar el cliente en los componentes

// Antes — cada componente gestionaba el token y los errores manualmente


const token = [Link]('jwt_token');
const res = await fetch('/api/productos', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (![Link]) throw new Error('Error ' + [Link]);
const datos = await [Link]();

// Ahora — el cliente centralizado gestiona todo esto automáticamente


const { data: datos } = await [Link]('/productos');
// El token ya está incluido (interceptor de petición)
// Los errores 401 ya se gestionan (interceptor de respuesta)
// Los datos ya vienen deserializados de JSON

🤖 CON AYUDA DE LA IA
Adapta el cliente a tu proyecto con IA
Puedes pedir a la IA que adapte el cliente HTTP a las particularidades de tu API:

"Tengo este cliente Axios centralizado [pega el código]. Mi API Spring Boot devuelve los errores
en el formato ApiError {status, mensaje, errores[]}. Modifica el interceptor de respuesta para que,
cuando haya un error, extraiga el campo 'mensaje' del cuerpo de la respuesta y lo incluya en el
error que recibe el componente, en lugar del mensaje genérico de Axios."
IFCD0182 — Desarrollo Web con Java

2. Servicios de API — Organizar las llamadas al backend

2.1 El patrón de servicio en el frontend


Igual que en Spring Boot tienes @Service para la lógica de negocio separada del @Controller, en el frontend
debes separar las llamadas a la API de los componentes. Los servicios de API son módulos JavaScript que
encapsulan todas las llamadas a un recurso de tu backend.

🔗 CONEXIÓN CON EL BACKEND


El patrón es el mismo que conoces del backend: el componente React/Vue/Angular (= @Controller)
llama al servicio de API (= @Service) que a su vez llama a la API REST (= @Repository). Separación de
responsabilidades del Módulo 1 aplicada al frontend.

2.2 Servicio de API por recurso


// src/api/[Link]
import apiCliente from './cliente';

// Todas las llamadas a /api/productos en un solo lugar


const productoService = {

// GET /api/productos
getAll: () =>
[Link]('/productos').then(res => [Link]),

// GET /api/productos/{id}
getById: (id) =>
[Link](`/productos/${id}`).then(res => [Link]),

// GET /api/productos/buscar?q=texto
buscar: (query) =>
[Link]('/productos/buscar', { params: { q: query } }).then(res =>
[Link]),

// POST /api/productos
create: (datos) =>
[Link]('/productos', datos).then(res => [Link]),

// PUT /api/productos/{id}
update: (id, datos) =>
[Link](`/productos/${id}`, datos).then(res => [Link]),

// DELETE /api/productos/{id}
remove: (id) =>
[Link](`/productos/${id}`),
};

export default productoService;


IFCD0182 — Desarrollo Web con Java
// Uso en un componente React (limpio, sin lógica HTTP):
import productoService from '../api/productoService';

const [productos, setProductos] = useState([]);

useEffect(() => {
[Link]()
.then(setProductos)
.catch(err => setError([Link]));
}, []);

3. Gestión de estado global

3.1 El problema del «prop drilling»


Imagina que el estado de autenticación (usuario logueado, token, roles) está en el componente App raíz.
Para que el componente Header muestre el nombre del usuario y el componente ProductoList sepa si el
usuario es ADMIN, tienes que pasar esos datos por props a través de todos los componentes intermedios
aunque no los usen. Eso se llama prop drilling y es uno de los problemas más comunes en SPAs complejas.
3.2 Cuándo necesitas estado global
No todos los proyectos necesitan una librería de estado global. La regla general:

Situación Solución recomendada

Datos usados en 1-2 componentes cercanos Estado local del componente (useState, ref)

Datos compartidos entre componentes Context API (React) / Provide-Inject (Vue) / Servicios (Angular)
distantes en el árbol

Estado complejo con muchas actualizaciones Redux (React) / Pinia (Vue) / NgRx (Angular)
y componentes

Autenticación — usuario, token, roles Context API o librería de estado según la complejidad
disponibles en toda la app

3.3 Context API de React — Estado global sin librería extra


Para la mayoría de proyectos medianos, el Context API de React es suficiente para gestionar el estado de
autenticación sin necesitar Redux:

// src/context/[Link] — Contexto de autenticación


import { createContext, useContext, useState } from 'react';

// 1. Creamos el contexto
const AuthContext = createContext(null);

// 2. El Provider envuelve la app y proporciona el estado


export function AuthProvider({ children }) {
const [usuario, setUsuario] = useState(null);
const [token, setToken] = useState([Link]('jwt_token'));
IFCD0182 — Desarrollo Web con Java

const login = async (credenciales) => {


const res = await [Link]('/auth/login', credenciales);
const { token: nuevoToken, username } = [Link];
[Link]('jwt_token', nuevoToken);
setToken(nuevoToken);
setUsuario(username);
};

const logout = () => {


[Link]('jwt_token');
setToken(null);
setUsuario(null);
};

return (
<[Link] value={{ usuario, token, login, logout }}>
{children}
</[Link]>
);
}

// 3. Hook personalizado para acceder al contexto


export const useAuth = () => useContext(AuthContext);

// 4. En [Link]: envolvemos toda la aplicación con el Provider


function App() {
return (
<AuthProvider>
<Router>
<Header />
<Routes> ... </Routes>
</Router>
</AuthProvider>
);
}

// 5. Cualquier componente puede acceder al estado sin prop drilling


function Header() {
const { usuario, logout } = useAuth();
return (
<header>
<span>Hola, {usuario}</span>
<button onClick={logout}>Cerrar sesión</button>
</header>
);
}
IFCD0182 — Desarrollo Web con Java

4. Manejo de errores de red — La experiencia del usuario


cuando algo falla

4.1 Los tipos de error que puede devolver tu API


Código HTTP Cuándo ocurre Qué debe ver el usuario

400 Bad Request El formulario tiene datos inválidos Mensajes de error junto a cada campo
inválido

401 Unauthorized Token expirado o inválido Renovar token automáticamente o redirigir al


login

403 Forbidden Autenticado pero sin permiso para "No tienes permiso para realizar esta acción"
esa acción

404 Not Found El recurso solicitado no existe "El elemento no fue encontrado"

409 Conflict Email duplicado, recurso ya existente Mensaje específico del conflicto junto al
campo

422 Unprocessable Datos con formato correcto pero Mensaje de validación específico
inválidos semánticamente

500 Internal Error Error del servidor — bug en tu Spring "Ha ocurrido un error. Por favor, inténtalo de
Boot nuevo"

Sin respuesta El servidor no responde (caído, "No hay conexión. Verifica tu red e inténtalo
timeout) de nuevo"

4.2 Componente de manejo de errores reutilizable (React)


// src/hooks/[Link] — Hook personalizado para llamadas a la API
import { useState, useCallback } from 'react';

// Hook que gestiona automáticamente loading, datos y errores


export function useApiCall(apiFn) {
const [datos, setDatos] = useState(null);
const [cargando, setCargando] = useState(false);
const [error, setError] = useState(null);

const ejecutar = useCallback(async (...args) => {


setCargando(true);
setError(null);
try {
const resultado = await apiFn(...args);
setDatos(resultado);
return resultado;
} catch (err) {
// Extraemos el mensaje de error del cuerpo de la respuesta de Spring Boot
const mensaje = [Link]?.data?.mensaje
|| [Link]
|| 'Ha ocurrido un error inesperado';
setError(mensaje);
throw err;
} finally {
IFCD0182 — Desarrollo Web con Java
setCargando(false);
}
}, [apiFn]);

return { datos, cargando, error, ejecutar };


}

// Uso en un componente — limpio y sin repetir la lógica de loading/error:


function ListaProductos() {
const { datos: productos, cargando, error, ejecutar } =
useApiCall([Link]);

useEffect(() => { ejecutar(); }, []);

if (cargando) return <p>Cargando...</p>;


if (error) return <p className='error'>{error}</p>;
if (!productos?.length) return <p>No hay productos.</p>;

return [Link](p => <TarjetaProducto key={[Link]} producto={p} />);


}

4.3 Errores de validación — Mapear errores de la API al formulario


Cuando tu Spring Boot devuelve errores de validación con el formato del GlobalExceptionHandler, el
frontend debe mostrarlos junto al campo correcto:

// El GlobalExceptionHandler de Spring Boot devuelve:


{
"status": 400,
"mensaje": "Datos de entrada inválidos",
"errores": ["El nombre es obligatorio", "El precio debe ser mayor que cero"]
}
// En el componente de formulario React:
const [erroresApi, setErroresApi] = useState([]);

const handleSubmit = async (e) => {


[Link]();
try {
await [Link](formulario);
onExito();
} catch (err) {
if ([Link]?.status === 400) {
// Extraemos el array de errores del cuerpo de la respuesta
setErroresApi([Link] || []);
}
}
};

// En el JSX del formulario:


{[Link] > 0 && (
<div className='errores-api'>
{[Link]((err, i) => <p key={i} className='error'>{err}</p>)}
</div>
)}
IFCD0182 — Desarrollo Web con Java

5. Routing en SPAs y rutas protegidas

5.1 React Router — El router más usado


npm install react-router-dom

import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';


import { useAuth } from './context/AuthContext';

// Componente que protege rutas — redirige al login si no hay token


function RutaProtegida({ children, rolRequerido }) {
const { token, usuario } = useAuth();

// Sin token: redirige al login


if (!token) return <Navigate to='/login' replace />;

// Si se requiere rol ADMIN y no lo tiene: página de sin permiso


if (rolRequerido === 'ADMIN' && !esAdmin(token)) {
return <Navigate to='/sin-permiso' replace />;
}

return children;
}
// [Link] — Configuración de rutas
function App() {
return (
<AuthProvider>
<Router>
<Routes>
<Route path='/login' element={<Login />} />
<Route path='/registro' element={<Registro />} />

{/* Rutas protegidas — requieren autenticación */}


<Route path='/' element={
<RutaProtegida><Dashboard /></RutaProtegida>
} />
<Route path='/productos' element={
<RutaProtegida><ListaProductos /></RutaProtegida>
} />

{/* Ruta solo para ADMIN */}


<Route path='/admin' element={
<RutaProtegida rolRequerido='ADMIN'><PanelAdmin /></RutaProtegida>
} />

{/* Ruta por defecto: redirige al dashboard */}


<Route path='*' element={<Navigate to='/' replace />} />
</Routes>
</Router>
</AuthProvider>
);
}
IFCD0182 — Desarrollo Web con Java

5.2 Decodificar el JWT para leer los roles en el frontend


Para que el componente RutaProtegida sepa si el usuario tiene rol ADMIN sin hacer una petición extra al
backend, decodifica el payload del JWT. Recuerda que el payload no está cifrado, solo codificado en
Base64URL:

// Función para decodificar el payload del JWT


function decodificarJwt(token) {
try {
// El payload es la segunda parte del JWT (separado por '.')
const payload = [Link]('.')[1];
// atob() decodifica Base64. replace() maneja Base64URL
return [Link](atob([Link](/-/g, '+').replace(/_/g, '/')));
} catch {
return null;
}
}

// Ejemplo de uso:
const payload = decodificarJwt(token);
// payload = { sub: 'alumno', roles: '[ROLE_USER]', iat: 1714300000, exp: 1714386400
}

const esAdmin = (token) => {


const payload = decodificarJwt(token);
return payload?.roles?.includes('ROLE_ADMIN') ?? false;
};

⚠ ATENCIÓN
Importante — esto NO es una comprobación de seguridad:
Decodificar el JWT en el frontend solo sirve para adaptar la UI (mostrar u ocultar botones, redirigir
rutas). La seguridad real está en el backend: tu @PreAuthorize y tu SecurityFilterChain verifican el
token en cada petición. Un atacante podría manipular el token decodificado en memoria, pero tu API
lo rechazará con 403.

6. Configurar Spring Boot para servir la SPA

6.1 El problema del SPA fallback


Cuando construyes la SPA para producción (npm run build), genera una carpeta dist con [Link] y los
ficheros estáticos. Si sirves el frontend desde Spring Boot y el usuario recarga la página estando en
/productos, Spring Boot busca un @Controller para esa URL y devuelve 404 porque esa ruta no existe en el
backend.

La solución es el SPA fallback: Spring Boot debe devolver siempre el [Link] para cualquier URL que no
sea una petición a la API (/api/**) ni un fichero estático.
IFCD0182 — Desarrollo Web con Java

6.2 Configurar el SPA fallback en Spring Boot

// src/main/java/com/tuapp/config/[Link]
import [Link];
import [Link];
import [Link].*;
import [Link].*;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Servir los ficheros estáticos del frontend desde /static
[Link]("/**")
.addResourceLocations("classpath:/static/")
// SPA fallback: si el fichero no existe, devuelve [Link]
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected [Link] getResource(
String resourcePath,
[Link] location) throws
Exception {
var recurso = [Link](resourcePath);
if ([Link]() && [Link]()) return recurso;
// Si el fichero no existe, devolvemos [Link]
return new ClassPathResource("/static/[Link]");
}
});
}
}

6.3 Copiar el build del frontend a Spring Boot


Cuando construyes el frontend (npm run build), copia la carpeta dist al directorio src/main/resources/static/
de Spring Boot. Al hacer mvn package, el .jar incluirá los ficheros estáticos del frontend.

# 1. Construir el frontend
cd mi-frontend
npm run build

# 2. Copiar el dist al directorio static de Spring Boot


cp -r dist/* ../mi-backend/src/main/resources/static/

# 3. Generar el .jar de Spring Boot (incluye el frontend)


cd ../mi-backend
mvn clean package

# 4. Ejecutar — sirve tanto la API como el frontend


java -jar target/[Link]
# Accede a [Link] — verás la SPA
# [Link] — devuelve JSON
IFCD0182 — Desarrollo Web con Java

6.4 Alternativa — Frontend y backend en servidores separados


En muchos proyectos el frontend se despliega en un servidor CDN (Vercel, Netlify, AWS S3) y el backend en
un servidor de aplicaciones. Esto separa las responsabilidades y permite escalar cada parte de forma
independiente:

Aspecto Mismo servidor (.jar) Servidores separados (CDN + API)

Complejidad Menor — un solo artefacto que desplegar Mayor — dos despliegues que coordinar

Escalado Frontend y API escalan juntos Cada uno escala de forma independiente

CORS No necesario — mismo origen Necesario — orígenes diferentes

Caché Spring Boot gestiona la caché de estáticos CDN gestiona la caché automáticamente
— más eficiente

Cuándo usar Proyectos pequeños/medianos, Proyectos grandes, alta disponibilidad,


prototipos tráfico alto

7. Errores frecuentes en la comunicación frontend-backend

Error / Síntoma Causa habitual Solución

El interceptor de Axios hace un El interceptor intenta renovar el Añade una bandera _reintentado al
bucle infinito de refresh token, falla, y vuelve a intentarlo config de la petición original y
infinitamente compruébala antes de intentar el
refresh

Los errores del servidor El servidor no está corriendo o Comprueba primero en Postman si la
aparecen como 'Network Error' CORS bloquea la petición antes de API responde. Si responde, el problema
en el frontend llegar al backend es CORS. Revisa la configuración en
Spring Boot.

Al recargar la página en No está configurado el SPA Añade el ResourceHandler con


/productos, Spring Boot fallback en WebConfig PathResourceResolver que devuelva
devuelve 404 [Link] para rutas sin fichero.

El estado de autenticación se El token está en el estado del Inicializa el estado de autenticación


pierde al recargar la página componente (useState) que se leyendo de localStorage:
reinicia con cada recarga useState([Link]('jwt_tok
en'))

Las rutas protegidas son La comprobación del token en Verifica que AuthProvider envuelve
accesibles aunque no hay token RutaProtegida es incorrecta o el todas las rutas. Añade [Link] para
Context no está disponible depurar el valor del token en el
contexto.
IFCD0182 — Desarrollo Web con Java

8. Buenas prácticas en la comunicación frontend-backend

✔ BUENAS PRÁCTICAS
Aplica estas prácticas en proyectos reales:
• Un cliente HTTP centralizado: Nunca importes axios o fetch directamente en los
componentes. Usa siempre el cliente centralizado con los interceptores.
• Servicios de API por recurso: [Link], [Link], [Link]. Un
fichero por recurso de la API.
• Variables de entorno para las URLs: Nunca escribas '[Link] en el código
fuente. Usa [Link].VITE_API_URL o [Link].REACT_APP_API_URL para
que funcione en producción.
• Gestiona siempre los estados de carga y error: El usuario nunca debe ver una pantalla
en blanco sin saber qué ocurre.
• Mensajes de error comprensibles: 'Error 422' no ayuda al usuario. 'El email ya está
registrado' sí.
• Confirma antes de eliminar: Siempre pide confirmación antes de llamar a un DELETE. Un
error de usuario no debe ser irreversible.
• No guardes datos sensibles en localStorage: El access token puede ir en sessionStorage
o memoria. El refresh token debe ir en una cookie HttpOnly del servidor.
IFCD0182 — Desarrollo Web con Java

9. Actividad práctica guiada

Título Refactorizar la mini SPA con cliente Axios centralizado y rutas protegidas

Duración 55 minutos

Objetivo Migrar la SPA de la Unidad 2 de fetch() directo a un cliente Axios con interceptores,
servicios de API y rutas protegidas con React Router

Nivel Guiado — el formador explica cada refactorización antes de implementarla

1 Instalar Axios y crear el cliente centralizado


npm install axios. Crea src/api/[Link] con la instancia Axios, el interceptor de petición que
añade el token y el interceptor de respuesta que gestiona el 401 con refresh.

2 Crear los servicios de API


Crea src/api/[Link] (o el recurso de tu API) con todos los métodos: getAll(),
getById(), create(), update(), remove(). Todos usan apiCliente, no fetch().

3 Crear el AuthContext
src/context/[Link] con el Provider que gestiona token y usuario. login() guarda el
token y llama al endpoint. logout() lo borra. useAuth() como hook de acceso.

4 Instalar React Router y configurar rutas protegidas


npm install react-router-dom. [Link] con BrowserRouter, Routes y el componente
RutaProtegida que comprueba el token del contexto y redirige a /login si no existe.

5 Eliminar el fetch() directo de todos los componentes


Sustituye todos los fetch() directos por llamadas al servicio correspondiente
([Link]()). Verifica que el token ya no se menciona en ningún componente —
el interceptor lo gestiona.

🤖 CON AYUDA DE LA IA
Audita la arquitectura con IA

"Revisa la arquitectura de mi SPA React [pega el árbol de ficheros y los principales componentes].
¿Hay algún componente que todavía accede directamente a localStorage o hace fetch() sin pasar
por el cliente centralizado? ¿El estado de autenticación está correctamente encapsulado en el
contexto? ¿Las rutas protegidas cubren todas las páginas que requieren autenticación?"
IFCD0182 — Desarrollo Web con Java

10. Actividad práctica autónoma

Título SPA completa con arquitectura profesional lista para integrar con el backend de
producción

Duración 60 minutos

Objetivo Construir autónomamente una SPA con cliente Axios, servicios de API, estado global de
autenticación, rutas protegidas por rol y manejo de errores centralizado

Nivel Autónomo — documentación de React/Vue e IA disponibles

Entrega Proyecto frontend en .zip con fichero [Link] describiendo la estructura


elegida

Enunciado
Desarrolla una SPA completa para el sistema del Módulo 3 (gestión de empleados, notas o el recurso que
hayas trabajado) con arquitectura profesional:

• Cliente HTTP Axios centralizado: Con interceptores de petición (JWT automático) y respuesta
(refresh transparente + redirección al login si falla).
• Servicios de API: Un fichero de servicio por recurso. Los componentes no hacen peticiones HTTP
directamente.
• Estado global de autenticación: Context API con login(), logout(), usuario y token. Persistencia del
token en localStorage.
• Rutas protegidas diferenciadas por rol: Las rutas de admin solo son accesibles con ROLE_ADMIN. El
resto requiere cualquier autenticación.
• Manejo de errores de red: Los errores 400 muestran los mensajes de validación del
GlobalExceptionHandler de Spring Boot. Los errores 500 muestran un mensaje genérico.
• Variables de entorno: La URL de la API en .env (VITE_API_URL). El código fuente no contiene
localhost.

Criterios de evaluación
Criterio Descripción Peso

Cliente Axios con interceptores JWT automático en petición, refresh + redirección 25%
en 401

Servicios de API Un servicio por recurso, ningún componente hace 20%


peticiones directas

Estado global Auth Context con login/logout/usuario, persistencia en 20%


localStorage

Rutas protegidas por rol Admin solo accesible con ROLE_ADMIN, verificado 20%
decodificando el JWT

[Link] Describe la estructura, justifica las decisiones y 15%


lista los patrones usados
IFCD0182 — Desarrollo Web con Java

⚠ ATENCIÓN
Normas de uso de IA:
• Permitido: pedir ayuda con la lógica del interceptor de refresh, preguntar cómo leer
variables de entorno con Vite, depurar por qué el contexto no está disponible.
• No permitido: generar el cliente Axios completo, el AuthContext completo o los servicios
de API completos.
• Obligatorio: el [Link] debe explicar con tus palabras por qué has
estructurado el proyecto así y qué patrón del módulo has aplicado en cada decisión.

11. Resumen de la unidad

Concepto Lo esencial

Axios Librería HTTP con interceptores, timeout y serialización automática. Preferible a


fetch() en proyectos reales.

Interceptor petición Se ejecuta antes de enviar. Añade el JWT automáticamente a todas las
peticiones.

Interceptor respuesta Se ejecuta tras recibir. Gestiona el 401 con refresh token y redirección al login.

Servicio de API Módulo JavaScript con todas las llamadas a un recurso. Los componentes no
hacen peticiones directas.

Prop drilling Pasar props por componentes que no los necesitan. Se evita con Context API o
librería de estado.

Context API Estado global de React sin librería extra. Ideal para autenticación en proyectos
medianos.

useAuth() Hook personalizado que encapsula el acceso al contexto de autenticación.

RutaProtegida Componente que comprueba el token y redirige al login o a sin-permiso.

decodificarJwt() Decodifica el payload del JWT en el frontend para adaptar la UI. No reemplaza
la seguridad del backend.

SPA fallback Spring Boot devuelve [Link] para rutas del frontend. Necesario al servir la
SPA desde Spring Boot.

Variables de entorno VITE_API_URL en .env. Nunca escribas URLs hardcodeadas en el código.


IFCD0182 — Desarrollo Web con Java

✔ BUENAS PRÁCTICAS
Preguntas de autoevaluación:
1. ¿Qué ventaja tiene usar Axios frente a fetch() nativo en proyectos reales?
2. ¿Qué hace el interceptor de petición de Axios? ¿Y el interceptor de respuesta para 401?
3. ¿Qué es el prop drilling y cómo lo resuelve el Context API?
4. ¿Por qué decodificar el JWT en el frontend no es una medida de seguridad real?
5. ¿Qué configuras en Spring Boot para que al recargar /productos no se reciba un 404?
6. ¿Cuándo usarías frontend y backend en el mismo .jar y cuándo en servidores separados?

🤖 CON AYUDA DE LA IA
Consolidación antes de la Unidad 4 — Cierre del curso:

"Actúa como entrevistador técnico para un puesto fullstack Java + React. Hazme 5 preguntas
sobre la comunicación frontend-backend en proyectos reales: interceptores HTTP, gestión del JWT
en el cliente, manejo de errores de red, estado global de autenticación y configuración de Spring
Boot para SPAs. Corrige mis respuestas."

📊 NOTA DE MERCADO LABORAL


Lo que viene en la Unidad 4 — Cierre del curso:
La Unidad 4 es el cierre del curso: integración completa Java Backend con Frontend, el proyecto final
que une todo lo aprendido en los cuatro módulos, y las consideraciones de eficiencia energética en
el ciclo de vida completo del software full-stack.
IFCD0182 — Desarrollo Web con Java

MÓDULO 4 — Integración con Frontend | CIERRE DEL CURSO


UNIDAD 4 — UNIDAD FINAL

Integración Java con Frontend y eficiencia


energética full-stack
El proyecto final que une todo el curso: backend seguro + frontend funcional + despliegue
sostenible

Duración estimada 5 horas — integración, proyecto final y cierre del curso

Módulo 4 — Integración con Frontend (unidad y módulo finales)

Unidad anterior Unidad 3 — Comunicación Frontend-Backend y SPAs

Nivel de entrada SPA completa con cliente Axios, estado global y rutas protegidas funcionando.

Modalidad Presencial con apoyo de herramientas de IA

Lo que aprenderás Integración completa backend-frontend en producción, optimización energética


del ciclo de vida full-stack y consolidación de todo el curso

✔ BUENAS PRÁCTICAS
Al terminar esta unidad serás capaz de:
• Integrar completamente un backend Spring Boot con un frontend React o Vue en
producción.
• Construir el proyecto final del curso: una aplicación full-stack funcional.
• Aplicar los principios de sostenibilidad energética al ciclo completo frontend + backend.
• Optimizar la carga del frontend: lazy loading, code splitting y compresión.
• Configurar el despliegue en cloud de forma básica y sostenible.
• Hacer una retrospectiva de todo el curso identificando conexiones entre módulos.
IFCD0182 — Desarrollo Web con Java

1. Integración completa backend-frontend en producción

1.1 La arquitectura completa de una aplicación full-stack


Una aplicación web moderna tiene estas capas, y en este curso has aprendido a construir cada una de ellas:

Capa Tecnología del curso Responsabilidad

Persistencia H2 / MySQL + Spring Data JPA Almacenar y recuperar datos. Repositorios,


entidades, consultas.

Negocio Spring Boot @Service Lógica de negocio, validaciones, reglas del


dominio.

API REST Spring Boot @RestController + JWT Exponer los datos de forma segura.
Autenticación y autorización.

Testing JUnit 5 + Mockito + MockMvc Garantizar que el código funciona. Pipeline


CI/CD.

DevOps GitHub Actions + Docker Automatizar compilación, tests y despliegue.

Observabilidad SLF4J + Actuator + Micrometer Monitorizar, medir y mejorar el sistema en


producción.

Frontend HTML + CSS + JS + React/Vue La interfaz que el usuario ve e interactúa.

Comunicación Axios + JWT + Context API Conectar frontend y backend de forma segura
y eficiente.

1.2 Lista de verificación de integración completa


Antes de desplegar una aplicación full-stack en producción, verifica estos puntos:

✔ BUENAS PRÁCTICAS
Backend — lista de verificación:
• CORS configurado con los orígenes de producción del frontend (no localhost).
• JWT con clave secreta en variable de entorno, no en [Link] del
repositorio.
• Contraseñas de BD en variables de entorno o sistema de secretos.
• Spring Security protege todos los endpoints sensibles con roles correctos.
• GlobalExceptionHandler devuelve errores en JSON consistente.
• Actuator expone solo /health y /metrics, protegido con Spring Security.
• Logs configurados en nivel INFO con rotación. Sin datos sensibles en los logs.
• Tests automáticos pasando en el pipeline CI/CD.
IFCD0182 — Desarrollo Web con Java

✔ BUENAS PRÁCTICAS
Frontend — lista de verificación:
• URL de la API en variable de entorno (.env), no hardcodeada.
• Cliente Axios con interceptores JWT y refresh token automático.
• Rutas protegidas por autenticación y por rol.
• Gestión de errores de red con mensajes comprensibles para el usuario.
• Build de producción generado con npm run build (no el servidor de desarrollo).
• Variables de entorno de producción configuradas (VITE_API_URL=[Link]
[Link]/api).
• Lighthouse score de accesibilidad ≥ 80.
• La aplicación funciona correctamente al recargar cualquier página (SPA fallback).

2. Optimización del frontend para producción

2.1 Por qué optimizar el frontend


Una aplicación frontend no optimizada puede pesar varios megabytes y tardar segundos en cargar. Eso tiene
impacto en la experiencia de usuario, en el SEO y en el consumo energético (más datos transmitidos = más
energía). Vite (la herramienta de build que usamos) ya aplica muchas optimizaciones automáticamente, pero
hay varias que puedes configurar.

Técnica Qué hace Cómo aplicarla con Vite

Code splitting Divide el JavaScript en trozos que se Automático con [Link]() y import()
cargan solo cuando se necesitan dinámico

Tree shaking Elimina código que no se usa del bundle Automático con Vite — usa imports
final específicos

Compresión gzip/brotli Comprime los ficheros antes de Configurable en el servidor (Nginx) o


enviarlos — reduce hasta 70% el tamaño con plugin de Vite

Lazy loading de imágenes Las imágenes solo se cargan cuando el Atributo loading='lazy' en las
usuario las ve etiquetas <img>

Caché del navegador El navegador guarda los ficheros Spring Boot o Nginx añaden cabeceras
estáticos — no los descarga en cada Cache-Control
visita

Minificación Elimina espacios, comentarios y acorta Automático en npm run build con Vite
nombres de variables
IFCD0182 — Desarrollo Web con Java

2.2 Lazy loading de componentes en React

// Sin lazy loading — TODOS los componentes se cargan al arrancar


import ListaProductos from './pages/ListaProductos';
import PanelAdmin from './pages/PanelAdmin';
import Perfil from './pages/Perfil';

// Con lazy loading — cada página se carga solo cuando el usuario navega a ella
import { lazy, Suspense } from 'react';

// import() dinámico: Vite divide el bundle en chunks separados


const ListaProductos = lazy(() => import('./pages/ListaProductos'));
const PanelAdmin = lazy(() => import('./pages/PanelAdmin'));
const Perfil = lazy(() => import('./pages/Perfil'));

// Suspense: muestra un fallback mientras el chunk se descarga


function App() {
return (
<Suspense fallback={<div>Cargando...</div>}>
<Routes>
<Route path='/productos' element={<ListaProductos />} />
<Route path='/admin' element={<PanelAdmin />} />
<Route path='/perfil' element={<Perfil />} />
</Routes>
</Suspense>
);
}

2.3 Variables de entorno en Vite para producción

# .[Link] — solo para desarrollo local


VITE_API_URL=[Link]

# .[Link] — para el build de producción


VITE_API_URL=[Link]

// En el cliente Axios — usa la variable de entorno


const apiCliente = [Link]({
baseURL: [Link].VITE_API_URL,
timeout: 10000,
});

ℹ NOTA
Añade .[Link] al .gitignore. Nunca subas al repositorio los valores de producción. En el
sistema de CI/CD (GitHub Actions), configura las variables de entorno como secrets y pásalas al
proceso de build con --env.
IFCD0182 — Desarrollo Web con Java

3. Sostenibilidad energética en el ciclo de vida full-stack

3.1 El impacto energético de una aplicación web completa


Una aplicación web consume energía en cada capa del ciclo de vida. Como desarrolladores full-stack
tenemos visibilidad y control sobre todo el ciclo:

Fase Dónde se consume energía Lo que puedes hacer

Desarrollo Compilaciones, tests, entornos de Tests selectivos, build incremental (Gradle),


desarrollo activos apagar entornos cuando no se usan

CI/CD Pipeline ejecutándose en cada commit Tests paralelos, caché de dependencias


— servidores en la nube Maven, pipeline selectivo por módulo
cambiado

Frontend — red Transmitir JS, CSS e imágenes al Code splitting, lazy loading, compresión
navegador del usuario brotli, caché agresiva de estáticos

Frontend — render El navegador del usuario procesa y Evitar re-renders innecesarios,


renderiza la UI virtualización de listas largas, CSS eficiente

Backend — API CPU y memoria del servidor procesando Caché de respuestas frecuentes (Redis),
peticiones paginación, consultas JPA optimizadas

Backend — BD Consultas SQL, índices, conexiones del Índices en columnas de búsqueda


pool frecuente, caché de segundo nivel JPA, pool
ajustado

Infraestructura Servidores corriendo 24/7 Escala a cero cuando no hay tráfico,


rightsizing, región cloud cercana

3.2 Patrones de código full-stack que reducen el consumo energético

🌿 SOSTENIBILIDAD
Decisiones de código con impacto energético en toda la pila:
• Paginación de extremo a extremo: El backend devuelve 20 resultados (no 10.000). El
frontend solo muestra esos 20. Menos datos en red, menos memoria en el cliente,
menos procesamiento en el servidor.
• Caché HTTP con ETags: Spring Boot puede devolver 304 Not Modified si el recurso no
cambió. El navegador usa su copia local. La petición viaja pero la respuesta es mínima.
• Debouncing en búsquedas: Si el usuario escribe en un buscador, espera 300ms antes de
llamar a la API. Sin debounce, cada tecla genera una petición. Con debounce, solo una
petición al final.
IFCD0182 — Desarrollo Web con Java

• WebSockets para actualizaciones en tiempo real: En lugar de que el frontend haga


polling cada 5 segundos, una conexión WebSocket notifica al frontend solo cuando hay
cambios.
• Compresión de respuestas API: Spring Boot puede comprimir las respuestas JSON con
gzip. Una respuesta de 1MB puede bajar a 100KB con compresión.

3.3 Configurar compresión en Spring Boot


# [Link] — activar compresión de respuestas
[Link]=true
[Link]-types=application/json,application/xml,text/html
# Solo comprimir respuestas mayores de 1KB
[Link]-response-size=1024

3.4 Debouncing en el frontend


// Sin debounce — una petición por cada tecla que pulsa el usuario
[Link]('input', (e) => {
[Link](`/productos/buscar?q=${[Link]}`); // Se llama 20 veces
});

// Con debounce — solo llama a la API cuando el usuario deja de escribir


let temporizador;
[Link]('input', (e) => {
clearTimeout(temporizador);
temporizador = setTimeout(() => {
[Link](`/productos/buscar?q=${[Link]}`); // Se llama 1 vez
}, 300); // Espera 300ms tras la última tecla
});

3.5 Medir el impacto — Lighthouse y DevTools


Antes de optimizar, mide el estado actual. El navegador incluye herramientas gratuitas para medir el
rendimiento y el impacto energético de tu frontend:

Herramienta Dónde está Qué mide

Lighthouse DevTools → pestaña Lighthouse Rendimiento, accesibilidad, buenas


prácticas, SEO. Puntuación de 0 a 100.

Network DevTools → pestaña Network Tamaño de cada recurso, tiempo de carga,


número de peticiones.

Performance DevTools → pestaña Tiempo de renderizado, re-renders, uso de


Performance CPU del navegador.

Coverage DevTools → Coverage Qué porcentaje del JS y CSS descargado se


(Ctrl+Shift+P) realmente ejecuta.

Carbon Calculator [Link] Estimación de CO₂ generado por visita a


una URL.
IFCD0182 — Desarrollo Web con Java

4. Proyecto final del curso — Aplicación full-stack completa

ℹ NOTA
Este es el proyecto que cierra el curso IFCD0182. Une todo lo aprendido en los cuatro módulos:
backend seguro con Spring Boot + JWT, tests automáticos, pipeline CI/CD, frontend React o Vue,
integración completa y consideraciones de sostenibilidad.

Título Sistema de gestión full-stack — aplicación completa lista para producción

Duración 120 minutos (proyecto final del curso)

Objetivo Construir y desplegar una aplicación full-stack completa que integre Spring Boot + JWT
+ Tests + CI/CD + React/Vue + diseño accesible y sostenible

Nivel Autónomo — todo el material del curso, documentación e IA disponibles

Entrega Repositorio Git con backend y frontend + README completo + demostración funcional

Enunciado
Elige un dominio de negocio (gestor de tareas, biblioteca, tienda, agenda, sistema de incidencias...) y
construye una aplicación web completa con las siguientes capas obligatorias:

1 Backend Spring Boot completo (Módulos 1-3)


API REST con al menos 3 recursos. Spring Security con autenticación JWT. Roles diferenciados
(USER y ADMIN con permisos distintos). Bean Validation en todos los endpoints.
GlobalExceptionHandler con errores en JSON. Tests automáticos: mínimo 5 unitarios con
Mockito y 3 con MockMvc. Pipeline GitHub Actions que ejecuta los tests en cada push.

2 Frontend React o Vue completo (Módulo 4)


SPA con login JWT y rutas protegidas por rol. Cliente Axios centralizado con interceptores.
Servicios de API por recurso. CRUD completo del recurso principal. Manejo de errores con
mensajes comprensibles. Diseño responsivo y accesible (Lighthouse ≥ 80). Lazy loading de al
menos las rutas de admin.

3 Integración y despliegue
Frontend construido con npm run build y servido desde Spring Boot (SPA fallback configurado).
Variables de entorno en el frontend (no URLs hardcodeadas). Dockerfile que construye la
imagen completa (backend + frontend estático). README con instrucciones de instalación,
variables de entorno necesarias y capturas de la aplicación funcionando.
IFCD0182 — Desarrollo Web con Java

4 Sostenibilidad documentada
Identifica y documenta en el README: 3 decisiones técnicas que reducen el consumo
energético de tu aplicación (paginación, caché, compresión, debouncing, lazy loading, etc.).
Incluye el resultado de [Link] para la página principal.

Criterios de evaluación del proyecto final


Módulo Criterio Peso

M1-M2 Backend API REST funcional, roles correctos, validación, errores JSON, 20%
estructura MVC

M3 Seguridad Spring Security + JWT completo con refresh token, endpoints 15%
protegidos por rol

M3 Testing Tests automáticos pasando en GitHub Actions pipeline 10%

M3 Observabilidad Logging profesional, Actuator configurado, al menos una 5%


métrica personalizada

M4 Frontend SPA funcional, cliente Axios con interceptores, rutas 20%


protegidas, CRUD completo

M4 Accesibilidad Lighthouse Accessibility ≥ 80, HTML semántico, focus visible 10%

M4 Integración SPA fallback, variables de entorno, Dockerfile, frontend 10%


servido desde Spring Boot

Sostenibilidad 3 decisiones documentadas, resultado Carbon Calculator 5%

README Instrucciones claras, capturas, decisiones técnicas justificadas 5%

⚠ ATENCIÓN
Sobre el uso de IA en el proyecto final:
• Permitido: pedir a la IA que revise la arquitectura, que depure errores concretos, que
mejore el CSS de un componente específico.
• No permitido: generar la mayoría del proyecto con IA. El proyecto debe demostrar lo que
has aprendido, no la capacidad generativa de la IA.
• Recomendado: usa la IA como revisor técnico al final — pídele que evalúe cada capa del
proyecto según los criterios de evaluación y que identifique puntos de mejora.
IFCD0182 — Desarrollo Web con Java

5. Retrospectiva — El mapa completo del curso

Antes de cerrar el curso, conecta visualmente todo lo que has construido. Cada módulo fue la base del
siguiente:

Módulo Lo que aprendiste Cómo conecta con lo siguiente

M1 — Java Web HTTP, Servlet, JSP, JavaBeans, Seguridad básica, El MVC de Servlet+JSP es el mismo
clásico Patrón MVC patrón que Spring MVC del Módulo 2,
con diferente sintaxis

M2 — Spring IoC, DI, Spring Boot, Spring MVC, Thymeleaf, Las APIs REST del M2 son las que el
Framework APIs REST, Microservicios frontend del M4 consume. Los
microservicios se protegen con Spring
Security del M3

M3 — Seguridad y Spring Security, JWT, JUnit+Mockito, CI/CD, El JWT del M3 protege la API que el
DevOps Logging, Actuator, Sostenibilidad frontend del M4 consume. Los tests
del M3 son el corazón del pipeline
CI/CD

M4 — Frontend e HTML/CSS/JS, React/Vue, Axios, Estado global, Cierre del ciclo: el frontend consume
integración SPA, Despliegue full-stack, Sostenibilidad end- la API REST del M2, protegida con JWT
to-end del M3, desplegada con CI/CD del M3

🔗 CONEXIÓN CON LO ANTERIOR


Las conexiones más importantes del curso:
• El patrón MVC del Módulo 1 (Servlet + JSP) es el mismo que Spring MVC del Módulo 2
(@Controller + Thymeleaf). Solo cambia la tecnología.
• El JavaBean del Módulo 1 es el mismo concepto que el Bean de Spring del Módulo 2. Solo
cambia cómo se gestiona.
• La seguridad básica del Módulo 1 (XSS, SQL Injection, CSRF) es la base conceptual de
Spring Security del Módulo 3.
• El fetch() del Módulo 4 llama exactamente a los @RestController del Módulo 2,
protegidos por el JWT del Módulo 3.
• Los tests del Módulo 3 son los que el pipeline CI/CD del Módulo 3 ejecuta
automáticamente en cada push.
IFCD0182 — Desarrollo Web con Java

6. Tu perfil profesional al terminar el curso

📊 NOTA DE MERCADO LABORAL


Lo que eres capaz de hacer al terminar IFCD0182:
• Construir APIs REST completas con Spring Boot: autenticación JWT, validación, manejo
de errores, Swagger.
• Proteger aplicaciones con Spring Security: roles, permisos, BCrypt, sesiones seguras.
• Escribir tests automáticos que verifiquen el código: unitarios, integración y de
controlador.
• Automatizar la compilación y los tests con GitHub Actions y Docker.
• Instrumentar la aplicación con logging profesional, métricas y health checks.
• Construir una SPA funcional con React o Vue que consuma tu API REST de forma segura.
• Integrar frontend y backend en un despliegue completo y sostenible.

Rol en el mercado Qué incluye Salario medio en España


(2025)

Desarrollador Java Junior Spring Boot, REST APIs, tests básicos 22.000 — 28.000 €/año

Desarrollador Java Backend Spring Boot, Spring Security, JWT, CI/CD, 28.000 — 40.000 €/año
testing avanzado

Desarrollador Full-Stack Java Todo lo anterior + frontend React/Vue, 32.000 — 48.000 €/año
integración completa

DevSecOps Java Seguridad, Docker, GitHub Actions, 38.000 — 55.000 €/año


observabilidad, sostenibilidad

💼 EN EL TRABAJO REAL
Próximos pasos para el mercado laboral:
• Portfolio en GitHub: Sube el proyecto final bien documentado. Es tu carta de
presentación técnica.
• LinkedIn actualizado: Añade las tecnologías del curso: Spring Boot, JWT, React/Vue,
Docker, GitHub Actions, JUnit.
• Profundiza en lo que más te gusta: Si te apasiona la seguridad: Spring Security + OAuth2
+ OWASP. Si te gusta el frontend: React avanzado + TypeScript. Si te interesa la
arquitectura: microservicios + Kubernetes.
• Practica entrevistas técnicas: Los prompts de IA de este curso simulan entrevistas reales.
Úsalos antes de las entrevistas.
IFCD0182 — Desarrollo Web con Java

7. Resumen de la unidad y cierre del curso

Concepto de esta unidad Lo esencial

Integración full-stack Frontend + Backend en producción. Lista de verificación de 16 puntos antes de


desplegar.

Lazy loading Los componentes se cargan solo cuando se necesitan. [Link]() + import()
dinámico.

Variables de entorno .[Link] y .[Link] con Vite. Nunca URLs hardcodeadas en el


código.

Compresión en Spring Boot [Link]=true en [Link]. Hasta 70% menos


tamaño.

Debouncing Espera 300ms antes de llamar a la API en búsquedas. Reduce peticiones hasta
20x.

Sostenibilidad full-stack Paginación E2E, caché HTTP, compresión, lazy loading, escala a cero — cada
capa importa.

Carbon Calculator [Link] mide el CO₂ por visita a tu aplicación. Mide antes de
optimizar.

🤖 CON AYUDA DE LA IA
Revisión completa del curso con IA:

"Actúa como entrevistador técnico senior para un puesto de desarrollador Java full-stack. Hazme
12 preguntas de dificultad creciente que cubran todo el curso IFCD0182: arquitectura MVC,
Spring Boot (IoC, DI, REST APIs), Spring Security + JWT, testing (JUnit, Mockito, MockMvc), CI/CD
(GitHub Actions, Docker), observabilidad y frontend (fetch/Axios, React/Vue, integración full-
stack). Corrige cada respuesta e indica qué añadiría un candidato con 2 años de experiencia."
IFCD0182 — Desarrollo Web con Java

🏁 CIERRE DEL CURSO


Cierre del curso IFCD0182 — Desarrollo Web con Java
“ Felicidades “
Has completado el curso. A lo largo de 120 horas has construido:

• Aplicaciones web con Java EE clásico: Servlet, JSP, JavaBeans y el patrón MVC.
• APIs REST completas con Spring Boot: autenticación, autorización, validación y manejo
de errores.
• Arquitecturas de microservicios con Spring Cloud: Eureka, Feign, API Gateway.
• Sistemas de seguridad profesionales: Spring Security con BCrypt, JWT con refresh tokens.
• Suites de tests automáticos: unitarios, integración y de controlador con JUnit y Mockito.
• Pipelines CI/CD con GitHub Actions y contenedores Docker.
• Sistemas observables: logging profesional, métricas con Micrometer y health checks.
• Interfaces de usuario con HTML semántico, CSS responsivo y JavaScript.
• SPAs con React o Vue que consumen APIs REST de forma segura y eficiente.
• Aplicaciones full-stack integradas, desplegadas y sostenibles.

Lo que has aprendido no caduca. Los conceptos de seguridad, arquitectura, testing y sostenibilidad
son válidos más allá de Spring Boot y React. Las tecnologías cambian; los principios permanecen.

También podría gustarte