Manual Desarrollo Web JavaTemporal
Manual Desarrollo Web JavaTemporal
MÓDULO 1
Introducción al Desarrollo Web con Java
UNIDAD 1
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
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
Cliente El programa que usa el Envía peticiones y muestra Chrome, Firefox, Edge,
usuario final las respuestas aplicación móvil
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.
Cada petición HTTP indica qué tipo de acción quiere realizar. Los métodos más importantes son:
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
PATCH "Modifica solo esto" Actualizar solo parte de un Cambiar solo tu contraseña
dato
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
404 Not Found No existe lo que pides URL incorrecta o recurso eliminado
🤖 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
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...
ℹ 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
Una aplicación web hecha con Java está formada por varios componentes que trabajan juntos. Conviene
conocerlos antes de instalar nada:
JDK (Java Development El kit completo para Compilar y ejecutar Siempre, en todo el curso
Kit) desarrollar en Java código Java
JSP (JavaServer Pages) Páginas HTML con Generar HTML dinámico Módulo 1
código Java incrustado
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
Petición HTTP Método Java que se ejecuta Qué debes implementar ahí
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.
java -version
⚠ 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.
# 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.
✔ 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
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.
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];
✔ 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.
• 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
Los siguientes errores son los más habituales cuando se empieza con Java Web. Aprende a reconocerlos para
no perder tiempo:
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
⚠ 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
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
Duración 60 minutos
Objetivo Ampliar el primer Servlet para que muestre información dinámica generada en el
servidor
Enunciado
Modifica el Servlet HolaMundo para que muestre en el navegador:
🤖 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
Duración 90 minutos
Nivel Autónomo — resuelves los problemas por tu cuenta (con acceso a apuntes y a la IA)
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:
Criterios de evaluación
⚠ 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.
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:
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.
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.
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.
Duración estimada 4–5 horas (dentro del bloque de 10h con la Unidad 1)
Nivel de entrada Grupo heterogéneo — se explica desde cero con ritmo progresivo
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
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
ℹ 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
@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>");
}
}
✔ 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.
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.
⚙ 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.
✘ 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.
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
✔ 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.
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.
session HttpSession Guardar y leer datos del usuario entre La sesión del
peticiones usuario
<%-- 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).
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.
<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>
<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>
⚠ 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
✔ BUENAS PRÁCTICAS
Comparación: scriptlet vs JSTL para el mismo resultado
Scriptlet (evitar en producción):
Ya sabes usar JSP por separado. Pero en una aplicación real, JSP nunca trabaja solo. La combinación correcta
es:
JSP Mostrar los datos que le preparó el Servlet Contener lógica de negocio ni acceder a
la base de datos
@WebServlet("/catalogo")
public class CatalogoServlet extends HttpServlet {
⚙ 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.
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
🤖 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
✔ 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
Duración 60 minutos
Objetivo Crear un flujo Servlet → JSP completo con lista de objetos y JSTL
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.
@WebServlet("/tienda/catalogo")
public class CatalogoServlet extends HttpServlet {
[Link]("catalogo", catalogo);
[Link]("totalProductos", [Link]());
[Link]("/WEB-INF/vistas/[Link]")
.forward(req, res);
}
}
🤖 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
Duración 90 minutos
Objetivo Desarrollar de forma autónoma una aplicación Servlet + JSP + JSTL completa
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
⚠ 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
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
Nivel de entrada Instalación completada. El Servlet básico puede funcionar con algún problema
pendiente
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
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:
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.
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
package [Link];
// Esta clase representa un producto. Es un JavaBean porque cumple las tres reglas.
public class Producto {
⚙ 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.
ℹ 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
✔ 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
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.
<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)
<!-- 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.
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)
💡 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
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:
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
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] {
<!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
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(...)
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
✔ 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.
• 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
Duración 60 minutos
Objetivo Implementar el flujo completo Servlet → Bean → JSP con un caso de negocio distinto al
explicado
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.
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.
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?
🤖 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
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
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.
• 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
⚠ 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
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.
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í.
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.
Nivel de entrada Servlet y JSP funcionando. Bean conectado con JSP mediante Servlet
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.
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
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.
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
⚠ 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.
<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
<!-- SEGURO: escapa < > & " ' antes de incluirlos en el HTML -->
<p>Hola, <c:out value="${[Link]}"/></p>
// Uso en el doGet:
String nombreSeguro = escapeHtml([Link]("nombre"));
[Link]("<p>Hola, " + nombreSeguro + "</p>");
IFCD0182 — Desarrollo Web con Java
🤖 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
⚠ 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.
✘ 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
// 4. Ejecutamos
ResultSet rs = [Link]();
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
👁 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.
<!-- 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
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.
⚙ 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.
if (autenticacionCorrecta(usuario, password)) {
[Link]("/dashboard");
} else {
[Link]("error", "Credenciales incorrectas");
[Link]("/[Link]").forward(request, response);
}
IFCD0182 — Desarrollo Web con Java
[Link]("/login");
}
}
<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
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
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 < 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.
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
✘ 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
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
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
✔ 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.
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
Duración 60 minutos
Nivel Guiado — el formador analiza el primer bloque con el grupo antes de que cada uno
trabaje el resto
@WebServlet("/buscar")
public class BuscadorServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
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]());
}
}
}
4 Corrige la JSP
Sustituye ${param.q} por la forma segura usando <c:out>.
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
Duración 90 minutos
Objetivo Implementar desde cero un sistema de autenticación con todas las medidas de
seguridad aprendidas
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:
Criterios de evaluación
Criterio Descripción Peso
⚠ 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
Concepto Lo esencial
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.
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
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
Enfoque Conceptual y práctico equilibrado. Unidad compacta: JSF está en declive pero sus
conceptos son clave para entender Spring MVC
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
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.
Mostrar errores al usuario Código Java que genera HTML de Etiqueta <h:message> automática
error
👁 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.
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.
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
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.
@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
package [Link];
import [Link];
import [Link];
<h:commandButton> <button type="submit"> Botón que dispara un método de acción del Bean
<h:dataTable> <table> Tabla que itera sobre una colección del Bean
<!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>
</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
// En el Managed Bean:
public String registrar() {
// Si todo va bien, navegamos a la página de confirmación
return "confirmacion"; // → navega a /[Link]
}
💡 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
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:
Managed Bean (@Named + @Controller o @Service Clase Java que contiene la lógica de
@RequestScoped) la petición
Expression Language #{[Link]} Thymeleaf th:text="${atributo}" Vincular datos del backend con el
HTML
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
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
Duración 50 minutos
Objetivo Crear una aplicación JSF funcional con Managed Bean, formulario validado y resultado
en una segunda vista
Enunciado
Construye una aplicación JSF que calcule el Índice de Masa Corporal (IMC) del usuario:
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).
🤖 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."
Duración 70 minutos
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
Vista de confirmación Muestra los datos correctamente con #{} EL, sin 15%
código Java
⚠ 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
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
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.
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
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
✘ 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.
¿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
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:
💡 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
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:
package [Link];
public Producto() {}
public Producto(Long id, String nombre, double precio, int stock) {
[Link] = id; [Link] = nombre;
[Link] = precio; [Link] = stock;
}
// Getters y setters...
}
package [Link];
import [Link];
import [Link];
package [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
import [Link];
@WebServlet("/productos")
public class ProductoController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws [Link], IOException {
<%-- 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>
✔ 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
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:
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).
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
✘ 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 {
[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
}
}
✔ 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);
}
🤖 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
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
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.
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
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
Duración 60 minutos
Objetivo Tomar una aplicación funcional pero mal estructurada y reorganizarla en capas MVC
siguiendo el patrón correctamente
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.
🤖 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
ℹ 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.
Objetivo Construir desde cero una aplicación Java Web completa aplicando el patrón MVC y las
medidas de seguridad del Módulo 1
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:
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.
⚠ 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
Modelo Datos y lógica de negocio. No sabe que existe la Vista. No genera HTML.
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
Unidad anterior Módulo 1 completado — patrón MVC, Servlets, JSP, JavaBeans, seguridad
Lo que aprenderás Qué es Spring, cómo funciona por dentro (IoC y DI) y cómo Spring Boot simplifica
todo el ecosistema
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
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.
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.
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.
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.
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
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.
⚙ 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.
✘ ERROR FRECUENTE
Sin DI — creación manual de dependencias (forma antigua):
✔ 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
}
💡 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:
(@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
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.
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.
@Controller Clase que gestiona peticiones web Igual que @Component + habilita
(MVC) funcionalidades de Spring MVC.
ℹ 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
import [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
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:
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 Test Utilidades para probar aplicaciones Spring con Módulo 3 — Testing
JUnit y Mockito
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
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
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
package [Link];
import [Link];
import [Link];
# Nombre de la aplicación
[Link]=tienda-app
ℹ 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
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.
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.
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)
package [Link];
import [Link];
import [Link];
🤖 CON AYUDA DE LA IA
Experimenta y aprende con IA
Una vez que el endpoint /hola funciona, usa este prompt:
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
✔ 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
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
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.
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
Duración 90 minutos
Objetivo Desarrollar autónomamente una aplicación Spring Boot con IoC, DI y capas
correctamente separadas
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]).
Criterios de evaluación
Criterio Descripción Peso
⚠ 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
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.
Inyección por constructor La forma recomendada. Permite campos final y facilita el testing. Con
Lombok se genera automáticamente.
[Link] Archivo de configuración de Spring Boot. Ajusta los valores por defecto de la
autoconfiguración.
✔ 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."
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
Nivel de entrada Proyecto Spring Boot funcionando, tres capas con @Service y @Repository
creadas
Lo que aprenderás Crear aplicaciones web completas con Spring MVC: controladores, vistas
Thymeleaf, formularios y validación
✔ 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
⚙ 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.
@Controller
@RequestMapping("/productos")
public class ProductoController {
@GetMapping("/{id}")
public String detalle(@PathVariable Long id, Model model) {
IFCD0182 — Desarrollo Web con Java
[Link]("producto", [Link](id));
return "productos/detalle";
}
}
@ModelAttribute Objeto Java relleno con los campos @ModelAttribute Producto producto
del formulario
🤖 CON AYUDA DE LA IA
Practica con distintos parámetros de controlador
Para entender cuándo usar cada tipo de parámetro:
¿Funciona sin servidor? No. Solo en el contenedor. Sí. Es HTML válido y se abre en el
navegador.
Integración con Spring Buena pero limitada Nativa. @Valid, errores de validación,
mensajes.
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>
✔ 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
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.
public ProductoForm() {}
// Getters y setters...
}
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>
<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
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.
@NotBlank El texto no es nulo ni está vacío (ni solo message="El nombre es obligatorio"
espacios)
@Size(min=, max=) La longitud del texto está dentro del message="Entre 3 y 100 caracteres"
rango
@Positive El número es positivo (mayor que cero) message="Debe ser un número positivo"
<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="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
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)
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
✔ 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
Duración 75 minutos
Objetivo Construir un CRUD (Crear, Listar, Editar, Eliminar) completo con Spring MVC, Thymeleaf
y validació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:
🤖 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
Duración 90 minutos
Objetivo Desarrollar autónomamente una aplicación web completa con CRUD, validación y
layout compartido
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
Layout Thymeleaf Cabecera y pie compartidos con th:insert en todas las 15%
vistas
⚠ 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
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.
Thymeleaf Motor de plantillas HTML. Usa atributos th:* procesados en el servidor. Los
archivos son HTML válido.
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.
✔ 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."
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
Lo que aprenderás Construir APIs REST completas con Spring Boot: @RestController, ResponseEntity,
manejo de errores y documentación con Swagger
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
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).
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.
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
👁 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
¿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
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.
ResponseEntity te da control total sobre la respuesta: puedes especificar el cuerpo, el código HTTP y las
cabeceras:
import [Link];
import [Link];
403 Forbidden Sin permiso Autenticado pero sin permiso para esa operación
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é?"
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.
package [Link];
import [Link].*;
import [Link];
import [Link];
import [Link].*;
import [Link].*;
import [Link];
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
import [Link].*;
import [Link].*;
import [Link];
✔ 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
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.
{
"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.
🤖 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
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>
import [Link].*;
import [Link].*;
💡 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
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
@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
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.
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...)
✔ 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
Duración 60 minutos
Objetivo Construir una API REST CRUD completa con DTOs, @ControllerAdvice y documentación
Swagger
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.
4 Crea el GlobalExceptionHandler
Captura: MethodArgumentNotValidException (400), IsbnDuplicadoException (409) y Exception
genérica (500). Todos devuelven un ApiError en JSON.
🤖 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?"
Duración 90 minutos
Objetivo Desarrollar autónomamente una API REST completa con DTOs, errores centralizados,
Swagger y endpoints adicionales de búsqueda
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]().
Nivel de entrada API REST CRUD completa funcionando con ResponseEntity y manejo de errores
Lo que aprenderás Qué son los microservicios, cuándo usarlos y cómo construir una arquitectura
básica con Spring Cloud
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
⚙ 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.
Tecnología Un único lenguaje y framework para Cada servicio puede usar la tecnología más
todo. adecuada.
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.
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.
Tolerancia a fallos Si el Servicio B falla, ¿cómo evito que el Resilience4j (Circuit Breaker)
Servicio A también caiga?
👁 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
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).
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);
}
}
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
✔ 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
Dependencia
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@GetMapping("/api/v1/usuarios")
List<UsuarioDTO> listarUsuarios();
}
IFCD0182 — Desarrollo Web con Java
🤖 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
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.
Cliente conoce múltiples URLs Cliente solo conoce la URL del 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
Dependencia
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
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
CLOSED (Cerrado) Las peticiones pasan normalmente al servicio Estado normal — el servicio
funciona bien
HALF-OPEN (Medio) Deja pasar algunas peticiones de prueba Tras un tiempo de espera en
OPEN — comprueba si el servicio
se recuperó
@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
}
}
ℹ 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
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.
⚙ 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
✔ 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
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.
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
✔ 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
Duración 60 minutos
Objetivo Crear Eureka Server, dos microservicios registrados y comunicación entre ellos via Feign
Client
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
ℹ 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.
Entrega 3 proyectos Spring Boot en un repositorio Git. README con diagrama de arquitectura y
orden de arranque
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
APIs REST correctas DTOs, códigos HTTP correctos, errores manejados 20%
en ambos servicios
⚠ 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
Concepto Lo esencial
Feign Client Interfaz Java que describe los endpoints de otro servicio. Spring genera el
cliente HTTP automáticamente.
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.
✔ 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."
• 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
⚠ 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
Concepto Lo esencial
REST Estilo arquitectónico para APIs web. Recursos identificados por URLs, acciones por
métodos HTTP.
@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.
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."
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
Lo que aprenderás Añadir autenticación y autorización a tus aplicaciones Spring Boot usando Spring
Security 6
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
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.
⚙ 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.
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ú.
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
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.
@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);
}
@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();
La entidad Usuario
@Entity
@Table(name = "usuarios")
public class Usuario {
@Id @GeneratedValue(strategy = [Link])
private Long id;
@Column(nullable = false)
// En la BD se guarda el hash BCrypt, nunca la contraseña original
private String password;
// Getters y setters...
}
La implementación de UserDetailsService
import [Link].*;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
✔ 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;
// Solo ADMIN
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers([Link], "/api/**").hasRole("ADMIN")
⚠ 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
// 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) { ... }
💡 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."
El controlador — LoginController
@Controller
public class LoginController {
<label>Contraseña:</label>
<input type="password" name="password" required/>
✔ 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
if (![Link]().equals([Link]())) {
[Link]("confirmPassword", "error", "Las contraseñas no
coinciden");
return "auth/registro";
}
[Link]([Link](), [Link]());
return "redirect:/login?registrado";
}
}
// Getters y setters...
IFCD0182 — Desarrollo Web con Java
}
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.
@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]();
}
🤖 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
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}'/>
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
✔ 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
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
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
Título Portal de noticias con tres roles y registro real con BCrypt
Duración 90 minutos
Registrarse ✔ Público ✗ ✗
Crear noticia ✗ ✗ ✔
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
⚠ 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
Concepto Lo esencial
SecurityFilterChain Bean que configura toda la seguridad: qué URLs proteger, cómo autenticar,
login y logout.
UserDetailsService Interfaz que Spring Security usa para cargar usuarios. Implementa
loadUserByUsername().
@PreAuthorize Controla el acceso a nivel de método. Más granular que la configuración por
URL.
✔ 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."
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
Lo que aprenderás Implementar autenticación JWT completa: login que devuelve token, filtro que
valida token, endpoints protegidos por token
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
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.
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.
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):
• 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.
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
Antes de escribir código, entiende el flujo completo. Es el mismo en cualquier aplicación que use JWT,
independientemente del lenguaje o framework:
{
"token": "[Link]...",
"tipo": "Bearer",
"expiracion": "2025-05-29T10:00:00"
}
<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>
⚠ 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.
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;
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();
}
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 {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws [Link], [Link] {
// 9. Lo guardamos en el SecurityContext
// A partir de aquí, Spring Security sabe quién es el usuario
[Link]().setAuthentication(authToken);
}
}
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@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
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:
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]()
)
);
// 3. Generamos el JWT
String token = [Link](usuario);
} 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
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.
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
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) {
if ([Link]() ||
[Link]().isBefore([Link]())) {
return [Link]([Link]).build();
}
ℹ 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
Probar JWT con Postman requiere seguir un flujo de dos pasos: primero obtener el token y luego usarlo en
las siguientes peticiones.
🤖 CON AYUDA DE LA IA
IFCD0182 — Desarrollo Web con Java
"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
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.
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
✔ 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
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
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.
🤖 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
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
Entrega Proyecto en .zip con colección Postman que incluya todos los flujos de autenticación
probados
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
Refresh token Refresh genera nuevo access token, logout invalida el 20%
refresh
Manejo de errores JWT Token expirado y token inválido devuelven 401 con 10%
mensajes distintos
⚠ 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
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).
JJWT La librería Java más usada para crear y validar JWTs. Tres dependencias: api,
impl, jackson.
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.
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
Con Spring Security + JWT + Testing tendrás las tres habilidades de seguridad más demandadas en el
mercado Java.
IFCD0182 — Desarrollo Web con 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.
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
✔ 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
✘ 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.
✔ 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
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.
<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
@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)
@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
@Nested Agrupa tests relacionados en clases anidadas Para organizar tests de una clase
compleja
ACT Actuar Ejecuta el método que estás probando. Una sola llamada
— si necesitas más, probablemente es más de una prueba.
@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;
@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));
}
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.
@InjectMocks La instancia real de la clase bajo prueba, con los Para la clase que estás probando —
@Mock inyectados siempre una por test
// 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();
}
// ACT + ASSERT
RuntimeException ex = assertThrows(
[Link],
() -> [Link](99L)
);
assertTrue([Link]().contains("99"));
}
assertNotNull(resultado);
assertEquals("Ratón", [Link]());
assertEquals(29.99, [Link](), 0.001);
verify(repo, times(1)).save(any([Link]));
IFCD0182 — Desarrollo Web con Java
}
}
🤖 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
Qué arranca Solo la clase bajo prueba con mocks El contexto completo de Spring (todos los
beans)
Cuándo usar Para lógica de negocio pura sin Para probar que las capas se integran
dependencias reales correctamente
import [Link].*;
import [Link];
@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));
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
import [Link].*;
import [Link].*;
import [Link];
import [Link];
import [Link];
import static [Link].*;
import static [Link].*;
import static [Link].*;
@Autowired
// MockMvc: el cliente HTTP simulado para las pruebas
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper; // Para serializar/deserializar JSON
[Link](get("/api/productos/99"))
.andExpect(status().isNotFound());
}
[Link](post("/api/productos")
.contentType(MediaType.APPLICATION_JSON)
.content(dtoJson))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(3L))
.andExpect(jsonPath("$.nombre").value("Ratón"));
}
[Link](post("/api/productos")
.contentType(MediaType.APPLICATION_JSON)
.content(dtoJson))
.andExpect(status().isBadRequest());
IFCD0182 — Desarrollo Web con Java
}
}
⚠ 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.
<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>
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
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()
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
✔ 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
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
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.
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.
Test 4: POST /api/v1/libros con título vacío (validación) → 400 Bad Request.
🤖 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
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
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
Todos los tests pasan mvn test completo en verde sin fallos 15%
⚠ 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
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.
@InjectMocks Crea la instancia real de la clase bajo prueba con los @Mock inyectados.
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.
✔ 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."
Nivel de entrada Tests automatizados funcionando. Proyecto Spring Boot completo con Maven.
Lo que aprenderás Automatizar la compilación, los tests y el despliegue de tu aplicación con pipelines
CI/CD profesionales
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.
✔ 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
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.
👁 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
3 Compilar el proyecto
mvn compile o gradle build. Si el código no compila, el pipeline falla inmediatamente y notifica
al desarrollador.
6 Empaquetar la aplicación
mvn package. Genera el .jar ejecutable que se puede desplegar en cualquier servidor.
validate mvn validate Verifica que el [Link] es correcto y el proyecto está bien
estructurado
test mvn test Ejecuta los tests de src/test/java. Compila el código de test
primero.
verify mvn verify Ejecuta tests de integración y verifica que el paquete es correcto
💡 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.
# Generar el .jar saltando los tests (cuando los tests ya pasaron antes)
mvn package -DskipTests
<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
Configuración XML ([Link]) — verboso pero Groovy o Kotlin DSL — conciso y flexible
predecible
Curva aprendizaje Más sencillo para empezar — estructura Más flexible pero más complejo al
fija principio
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
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
⚠ 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
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.
Artifact Archivo generado que se puede descargar del El .jar o el informe de tests
pipeline
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
✔ 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ó.
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: Tests
run: mvn test
IFCD0182 — Desarrollo Web con Java
🤖 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
Dónde corre En los servidores de GitHub (cloud) En tu propio servidor (on-premise o cloud
propio)
Coste runners Gratuito para repos públicos, minutos Coste del servidor propio, sin límite de
limitados privados minutos
pipeline {
// Dónde se ejecuta el pipeline
agent any
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'
}
}
}
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
# Ejecutar el contenedor
docker run -p 8080:8080 mi-app:1.0.0
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'
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
✔ 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
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# Información de la aplicación
[Link]=Mi Aplicacion
[Link]=1.0.0
Endpoints disponibles
Endpoint URL Qué devuelve
⚠ 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
import [Link];
import [Link];
@Service
public class ProductoService {
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;
}
}
}
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
✔ 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
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
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.
🤖 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
Duración 90 minutos
Objetivo Implementar un pipeline completo que incluya tests, cobertura JaCoCo, construcción
Docker y despliegue en Railway o Render
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
Cobertura mínima Pipeline falla si cobertura < 60% (config JaCoCo en 15%
[Link])
⚠ 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
Concepto Lo esencial
GitHub Actions Plataforma CI/CD integrada en GitHub. Workflows en YAML, runners en la nube.
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."
Unidad anterior Unidad 4 — CI/CD con Maven, Gradle, Jenkins y GitHub Actions
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
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.
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.
import [Link];
import [Link];
import [Link];
@Service
public class ProductoService {
✔ BUENAS PRÁCTICAS
Buenas prácticas de logging:
IFCD0182 — Desarrollo Web con Java
🤖 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
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
💡 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.
import [Link].*;
import [Link];
@Component
public class ServicioExternoHealthIndicator implements HealthIndicator {
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
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.
<dependency>
<groupId>[Link]</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
import [Link].*;
import [Link];
@Service
public class ProductoService {
👁 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
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.
🌿 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
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
🤖 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
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
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
Duración 60 minutos
Enunciado
Toma la API REST de cualquiera de las unidades anteriores (libros, empleados, productos) y añade
observabilidad completa.
ℹ 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.
Duración 90 minutos
Objetivo Construir o completar una API Spring Boot que integre todos los aspectos del Módulo 3
Entrega Repositorio GitHub público con README completo que incluya: descripción,
instrucciones de uso, badge del pipeline CI y decisiones de sostenibilidad aplicadas
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
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.
Sostenibilidad código Evita operaciones innecesarias en bucles, usa lazy loading en JPA, cierra
recursos siempre.
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
• 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
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
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
<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>© 2025 Mi Tienda. Todos los derechos reservados.</p>
</footer>
<script src="[Link]"></script>
</body>
</html>
<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">
👁 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
Margin Espacio exterior entre este elemento y los demás margin, margin-
top/right/bottom/left
.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;
}
🤖 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
Arrays List<String> lista = new ArrayList<>(); const lista = []; (siempre dinámico)
Objetos Clase con atributos y métodos const obj = {nombre: 'Juan', edad: 30}
Bucles for (int i=0; i<10; i++) {} for (let i=0; i<10; i++) {} — idéntico
// Seleccionar elementos
const titulo = [Link]('titulo');
const botones = [Link]('.btn-primario');
const formulario = [Link]('#formulario-login');
// Modificar estilos
[Link] = '#E64A19';
if ([Link]) {
const productoCreado = await [Link]();
[Link]('Producto creado:', productoCreado);
}
}
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.
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
Robusto El contenido debe ser interpretable HTML semántico válido, ARIA attributes,
por tecnologías de asistencia compatibilidad con lectores de pantalla
🤖 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.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:
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.
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
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.
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
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
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
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%
⚠ 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
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.
fetch() Función nativa del navegador para llamar a APIs REST. Devuelve una Promise.
Siempre con try/catch.
ARIA Atributos que describen la semántica a los lectores de pantalla: aria-label, aria-
live, aria-expanded.
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."
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
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
✘ 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.
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
Los tres frameworks comparten los mismos conceptos fundamentales aunque con sintaxis diferente.
Entenderlos una vez te permite trabajar con cualquiera de los tres.
Comunicación Los componentes se comunican con Props (datos de padre a hijo) y Events
(eventos de hijo a padre)
⚙ 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
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
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);
});
}, []);
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>
);
}
useCallback(fn, deps) Memoriza una función para evitar re- Caché de método (conceptualmente)
renders innecesarios.
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.
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.
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.
<!-- SCRIPT: la lógica del componente con Composition API (Vue 3) -->
<script setup>
import { ref, onMounted } from 'vue';
<!-- 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.
Ideal para SPAs, startups, proyectos Enterprise, proyectos Proyectos medianos, curva
con equipos JS grandes, equipos Java suave, prototipos rápidos
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.
Login Envía POST /api/auth/login con credenciales AuthController devuelve 200 con el JWT
JSON
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
React Create React App (CRA) o Vite 3000 (CRA) / 5173 (Vite) npm start / npm run dev
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).
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
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
🤖 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
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
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
Listado reactivo GET con JWT, datos en el estado del framework, UI 20%
actualizada sin recargar
Logout y gestión de sesión Borra token, redirige al login, gestiona expiración 20%
del token
⚠ 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.
Concepto Lo esencial
Frameworks frontend Resuelven el caos del JS manual con componentes reutilizables y reactividad
declarativa.
Estado (State) Datos del componente. Cuando cambia, el framework actualiza el DOM
automáticamente.
Virtual DOM Copia en memoria del DOM. El framework compara y solo actualiza lo que
cambia.
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."
Nivel de entrada Mini SPA funcional consumiendo la API REST con JWT.
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
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
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
Cabeceras por defecto Hay que incluirlas en cada petición Se configuran una vez y se aplican a
manualmente todas
try {
// Intentamos renovar el token con el refresh token
const refreshToken = [Link]('refresh_token');
const res = await [Link]('[Link]
{ refreshToken });
✔ 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.
🤖 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
// 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}`),
};
useEffect(() => {
[Link]()
.then(setProductos)
.catch(err => setError([Link]));
}, []);
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
// 1. Creamos el contexto
const AuthContext = createContext(null);
return (
<[Link] value={{ usuario, token, login, logout }}>
{children}
</[Link]>
);
}
400 Bad Request El formulario tiene datos inválidos Mensajes de error junto a cada campo
inválido
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"
return children;
}
// [Link] — Configuración de rutas
function App() {
return (
<AuthProvider>
<Router>
<Routes>
<Route path='/login' element={<Login />} />
<Route path='/registro' element={<Registro />} />
// Ejemplo de uso:
const payload = decodificarJwt(token);
// payload = { sub: 'alumno', roles: '[ROLE_USER]', iat: 1714300000, exp: 1714386400
}
⚠ 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.
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
// 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]");
}
});
}
}
# 1. Construir el frontend
cd mi-frontend
npm run build
Complejidad Menor — un solo artefacto que desplegar Mayor — dos despliegues que coordinar
Escalado Frontend y API escalan juntos Cada uno escala de forma independiente
Caché Spring Boot gestiona la caché de estáticos CDN gestiona la caché automáticamente
— más eficiente
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.
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
✔ 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
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
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.
🤖 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
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
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
Rutas protegidas por rol Admin solo accesible con ROLE_ADMIN, verificado 20%
decodificando el JWT
⚠ 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.
Concepto Lo esencial
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.
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.
✔ 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."
Nivel de entrada SPA completa con cliente Axios, estado global y rutas protegidas funcionando.
✔ 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
API REST Spring Boot @RestController + JWT Exponer los datos de forma segura.
Autenticación y autorización.
Comunicación Axios + JWT + Context API Conectar frontend y backend de forma segura
y eficiente.
✔ 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).
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
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
// Con lazy loading — cada página se carga solo cuando el usuario navega a ella
import { lazy, Suspense } from 'react';
ℹ 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
Frontend — red Transmitir JS, CSS e imágenes al Code splitting, lazy loading, compresión
navegador del usuario brotli, caché agresiva de estáticos
Backend — API CPU y memoria del servidor procesando Caché de respuestas frecuentes (Redis),
peticiones paginación, consultas JPA optimizadas
🌿 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
ℹ 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.
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
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:
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.
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
⚠ 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
Antes de cerrar el curso, conecta visualmente todo lo que has construido. Cada módulo fue la base del
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
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
💼 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
Lazy loading Los componentes se cargan solo cuando se necesitan. [Link]() + import()
dinámico.
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
• 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.