← Peticiones, respuestas y CRUD en memoria

Sesión 5 · Semana 3

De la petición al objeto Java

Proyecto compartido. En el taller de Intermodular que abre esta semana has trabajado el primer workflow. En Servidor continúas la implementación del mismo producto.

Se explica

25 minutos · explicación y demostración

El CRUD funciona, pero una petición puede fallar antes de entrar en tu método. Hoy seguirás cómo Spring selecciona la ruta y transforma las entradas. Deserializar significa convertir el JSON recibido en un objeto Java; el registro de ejecución permitirá identificar dónde se detiene ese proceso.

Cómo llega la petición al controlador

En las primeras sesiones asociaste rutas con métodos mediante anotaciones y enviaste objetos JSON. Ahora vas a distinguir tres tareas que realiza Spring antes de invocar tu código: elegir el método, obtener sus parámetros y convertir el cuerpo. Esta distinción permite buscar cada fallo en el lugar adecuado.

El recorrido completo

Cuando llega una petición, no hay un if gigante buscando tu ruta. Hay una cadena de piezas, cada una con un trabajo, y cada una puede rechazar la petición por un motivo distinto.

De los bytes a tu método, y de vuelta
  1. Tomcat acepta la conexión y convierte los bytes en un objeto petición
  2. DispatcherServlet recibe absolutamente todas las peticiones y dirige el tráfico
  3. Handler mapping busca qué método tuyo corresponde a esa ruta y ese método HTTP
  4. Resolutores de argumentos construyen uno a uno los parámetros de tu método
  5. Tu método se ejecuta y devuelve un valor
  6. Conversor de mensaje convierte ese valor en el cuerpo de la respuesta
  7. Tomcat escribe la respuesta en la red

DispatcherServlet

La puerta única. Todas las peticiones de tu aplicación pasan por él, sea cual sea la ruta. Se llama front controller: en lugar de que cada ruta tenga su propio punto de entrada, hay uno solo que reparte. Por eso puedes añadir un endpoint nuevo sin registrarlo en ningún sitio.

Tú nunca escribes esta clase, nunca la instancias y nunca la llamas. La monta @SpringBootApplication al arrancar, y es la razón de que la línea de la consola dijera with context path '/': le está diciendo desde qué prefijo escucha.

Qué puede ser un parámetro de tu método

El paso 4 del recorrido es el que más magia parece. Spring mira uno a uno los parámetros que has declarado y, según cómo estén anotados, sabe de dónde sacar el valor.

Lo que declaras De dónde sale Visto en
@PathVariable Un trozo de la ruta UD1 · sesión 2
@RequestParam La query string UD1 · sesión 2
@RequestBody El cuerpo de la petición UD1 · sesión 3
@RequestHeader Una cabecera Hoy
HttpServletRequest La petición cruda entera Hoy

No hay un orden obligatorio ni un número máximo. Puedes combinarlos todos en un mismo método.

@RequestHeader · leer una cabecera

En la sesión 1 viste que cada petición viaja con una lista de cabeceras. Aquí es donde se recogen:

@GetMapping("/diagnostico")
public String diagnostico(
        @RequestHeader(name = "User-Agent") String cliente,
        @RequestHeader(name = "Accept") String acepta) {

    return "Me llama: " + cliente + "\nQuiere recibir: " + acepta;
}

Pruébalo desde dos clientes distintos: desde Postman y desde el navegador. La ruta es la misma, tu código es el mismo, y la respuesta es distinta, porque quien pregunta no es el mismo.

Es un buen momento para entender algo: el servidor sabe bastante más de quien le llama de lo que parece, y todo eso lo ha enviado el cliente voluntariamente en cada petición.

Cabeceras que no siempre vienen

Si pides una cabecera que no llega, obtienes un 400, igual que con un @RequestParam obligatorio. La corrección es la misma:

@RequestHeader(name = "X-Origen", required = false) String origen

Las cabeceras que empiezan por X- son, por convención, las que se inventa cada aplicación para sus propias necesidades.

La pregunta que quedó sin responder

En la UD1 enviaste un JSON con una errata y la API respondió 200 con el campo a null. Lo anotamos como una curiosidad inquietante y seguimos.

Hoy toca entenderlo, porque es el origen de una clase entera de fallos: los que no fallan. Un error que devuelve 500 te despierta a las tres de la mañana; un error que devuelve 200 guardando datos incompletos no te despierta nunca, y aparece tres meses después cuando alguien pregunta por qué faltan doscientos títulos.

Quién convierte el cuerpo

Conversor de mensaje

La pieza del recorrido HTTP que traduce entre el cuerpo HTTP —bytes y texto— y los objetos Java. Funciona en las dos direcciones: al entrar, con @RequestBody; al salir, con lo que devuelve tu método.

Para JSON, ese conversor utiliza Jackson, la biblioteca de conversión introducida en la sesión 3. En el modelo con constructor vacío y setters que estamos usando, el proceso es el siguiente; otros modelos, como los records, se construyen de otra forma.

Qué hace Jackson con cada clave del JSON
  1. Crea el objeto vacío con el constructor sin argumentos
  2. Coge la primera clave del JSON, por ejemplo titulo
  3. Busca un setter que le corresponda: setTitulo
  4. Si lo encuentra, convierte el valor al tipo que pida ese setter y lo llama
  5. Si no hay setter, puede utilizar otros accesos configurados, como un campo inferido. Si la propiedad es desconocida, la ignora en modo tolerante o rechaza el cuerpo en modo estricto
  6. Repite hasta terminar el JSON

Jackson procesa las propiedades presentes en el JSON. Las omitidas conservan el valor inicial del objeto: el asignado por su constructor o inicializadores, o el valor por defecto de Java si no hay ninguno (null, 0 o false, según el tipo). Una propiedad desconocida y una propiedad omitida son casos diferentes.

Los tres estados de un cuerpo

Un cuerpo que llega puede estar en tres situaciones muy distintas, y Spring las trata de forma radicalmente diferente:

Estado Ejemplo Qué hace Spring Código
Válido {"titulo":"Revisar"} Construye el objeto 200
Inválido {"titulo":"Revisar",} No puede leerlo, rechaza 400
Incompleto {} Lo construye igual, con valores por defecto 200

La distinción que hay que interiorizar hoy

Un cuerpo puede fallar por sintaxis JSON incorrecta o por incompatibilidad con el tipo Java. Ambos pueden producir 400, pero sus causas y correcciones son distintas.

Incompleto es un problema de significado: es JSON perfecto y le faltan datos que tu aplicación necesita. Jackson no tiene ninguna opinión al respecto, porque nadie le ha dicho qué es una tarea válida.

El primero te lo resuelve el framework. El segundo es responsabilidad tuya, y hasta la UD3 no tendrás la herramienta para resolverlo bien.

Se trabaja

140 minutos · implementación guiada sobre el proyecto propio

Paso 1 · Retomar el proyecto y preparar la comprobación

  1. Abre el controlador del CRUD, el modelo y src/main/resources/application.properties. Reproduce un GET y un POST válidos de la sesión 4.
  2. Localiza la consola donde aparecen los mensajes del servidor. Allí leerás los registros, o logs, después de activar el nivel DEBUG en el siguiente paso.
  3. Prepara cuatro variaciones de una petición válida: ruta inexistente, método no admitido, parámetro de tipo incorrecto y cuerpo JSON mal formado. Conserva la petición original para compararlas.

Paso 2 · Activar los logs de Spring y localizar el método que atiende la petición

Añade la propiedad de DEBUG al archivo existente, sin borrar sus otras líneas, y reinicia. Crea una tarea mediante el POST y utiliza su id en el GET: el número 1 del ejemplo solo es válido si ese registro existe. Deja visible la terminal del servidor, envía una petición y localiza únicamente las líneas correspondientes a su ruta; los logs de arranque pertenecen a otro momento.

En src/main/resources/application.properties:

logging.level.org.springframework.web=DEBUG

Desde Postman, un GET http://localhost:8080/tareas/1. Ahora mira la consola: donde antes no salía nada, aparecen varias líneas.

DispatcherServlet        : GET "/tareas/1", parameters={}
RequestMappingHandlerMapping : Mapped to TareaController#detalle(int)
RequestResponseBodyMethodProcessor : Using 'application/json'
DispatcherServlet        : Completed 200 OK
Línea 1 · llegó esto
DispatcherServlet registra método y ruta. Si no ves la línea, comprueba primero el nivel de log, la consola seleccionada y que miras la ejecución actual; después revisa dirección, puerto y arranque. La ausencia de un mensaje por sí sola no demuestra que no haya llegado una petición.
Línea 2 · va a este método
La decisión más importante de todo el recorrido. Te dice, con nombre y apellidos, qué método tuyo va a ejecutarse. Cuando una petición «hace algo raro», esta línea te dice si está entrando por donde crees.
Línea 3 · lo devuelvo así
Con qué formato se va a escribir la respuesta.
Línea 4 · terminó así
El código de estado final.

Esta es la herramienta de diagnóstico de todo el curso

Relaciona las líneas con las fases del recorrido. Encontrar un método no implica que se haya ejecutado: todavía deben resolverse sus argumentos. También puede fallar la escritura de la respuesta después de ejecutarlo. Comprueba la excepción y, cuando haga falta, añade temporalmente un mensaje al principio del método para observar si entra.

Déjalo encendido mientras desarrollas y apágalo cuando te moleste. Es una línea en un archivo.

Paso 3 · Distinguir errores de ruta, método, parámetros y cuerpo

La anotación @PostMapping(consumes=..., produces=...) sustituye a la anotación del POST existente. Conserva dentro del método la asignación de id y el guardado de la sesión 4: el bloque ilustra el acuerdo de formatos, no reinicia el CRUD. consumes restringe el cuerpo que aceptas y produces el formato que puedes devolver; cambia una sola cabecera de la petición en cada prueba.

Dónde muere una petición que no encaja
  1. ¿Hay alguna ruta que coincida? Si no, 404
  2. ¿Esa ruta acepta este método HTTP? Si no, 405
  3. ¿Sabe leer el formato que envío? Si no, 415
  4. ¿Puede devolver el formato que pido? Si no, 406
Código Nombre Qué falló Qué revisas
404 Not Found Ninguna ruta coincide La URL, y el paquete del controlador
405 Method Not Allowed La ruta existe con otro método El verbo de la petición
415 Unsupported Media Type No sabe leer tu Content-Type La cabecera de envío
406 Not Acceptable No puede darte lo que pides en Accept La cabecera de aceptación

Los cuatro son 4xx, y eso ya te lo dice todo

Estos códigos describen el resultado HTTP, pero no bastan para deducir si se ejecutó el controlador. Con consumes o produces explícitos, Spring puede rechazar durante la selección del método. Si intenta convertir una respuesta ya devuelta, un 406 puede producirse después de ejecutarlo. Usa los logs y un mensaje temporal de entrada para comprobar la fase real.

consumes y produces

Puedes declarar en el propio mapeo qué formatos acepta y qué formatos devuelve un método:

@PostMapping(consumes = "application/json", produces = "application/json")
public Tarea crear(@RequestBody Tarea tarea) {
    tareas.add(tarea);
    return tarea;
}

consumes es lo que provoca el 415 cuando el Content-Type no coincide. produces es lo que provoca el 406 cuando lo que pide el cliente en Accept no está entre lo que sabes dar.

Casi nunca hará falta escribirlos: Spring ya deduce lo razonable. Se escriben cuando un mismo recurso puede devolverse en varios formatos, o cuando quieres que el rechazo sea explícito y no una consecuencia.

Paso 4 · Enviar una petición por cada tipo de error y comparar la respuesta

Con el registro encendido y la consola a la vista. Para cada caso, predice el código antes de enviar y después mira hasta qué línea del registro llegó.

# Qué envías Predice
1 GET /tareaas · ruta inexistente
2 DELETE /tareas · sobre la colección, no sobre un elemento
3 POST /tareas con cuerpo y el desplegable de Postman en Text
4 GET /tareas con la cabecera Accept: application/xml

Para el caso 4 tendrás que añadir la cabecera a mano en la pestaña Headers de Postman. Es la primera vez que escribes una cabecera tú.

Al terminar, rellena esta tabla, que es el objetivo real de la sesión:

Caso Código ¿Apareció la línea Mapped to? ¿Se ejecutó tu método?
1
2
3
4

No presupongas cuántas filas mostrarán Mapped to: depende de dónde declaraste las restricciones y de los conversores instalados. En el caso del GET con Accept: application/xml, compara lo observado con y sin produces="application/json" en ese GET. Añade un mensaje temporal al inicio del método para distinguir selección, ejecución y conversión de salida; retíralo después.

Paso 5 · Seguir una petición completa en los logs

Dentro de ProyectoController, añade un método de diagnóstico con @GetMapping("/{id}/incidencias") si la clase ya tiene el prefijo /proyectos. Sus argumentos son @PathVariable(name="id") int id, @RequestParam(name="estado", required=false) String estado y @RequestHeader(name="User-Agent") String cliente. Importa las anotaciones, devuelve una frase con los tres valores y reinicia. Si esa ruta ya existe, utiliza temporalmente /{id}/diagnostico para no sustituir una operación del proyecto. Compara un envío con estado y otro sin él.

  1. Escribe un endpoint GET /proyectos/{id}/incidencias que reciba además un @RequestParam opcional estado y la cabecera User-Agent, y devuelva un texto con los tres valores.
  2. Llámalo desde Postman con todo puesto.
  3. Copia de la consola las cuatro líneas del registro y anota junto a cada una qué pieza del recorrido la ha escrito.
  4. Vuelve a llamarlo quitando el parámetro estado. ¿Cambia alguna línea del registro? ¿Cuál?

Cuerpo JSON y deserialización

Paso 6 · Crear un endpoint de diagnóstico que devuelva el objeto recibido

Para estudiar esto necesitamos ver qué objeto ha construido Jackson. Añade a tu controlador:

@PostMapping("/espejo")
public Tarea espejo(@RequestBody Tarea tarea) {
    System.out.println("He recibido: " + tarea.getTitulo()
            + " / " + tarea.getPrioridad()
            + " / completada=" + tarea.isCompletada());
    return tarea;
}

Devuelve lo que ha construido y además lo imprime, para que veas el objeto Java y el JSON de vuelta a la vez. No guarda nada: es un banco de pruebas.

Paso 7 · Enviar variantes de JSON al endpoint de diagnóstico

Envía estos nueve cuerpos, uno a uno, a POST /tareas/espejo. Predice antes de enviar el código de estado y los valores del objeto.

# Cuerpo enviado Predice el código
1 {"titulo":"Revisar","prioridad":"alta","completada":true}
2 {"titulo":"Revisar"}
3 {}
4 {"titulo":"Revisar",}
5 {"tituloo":"Revisar"}
6 {"titulo":"Revisar","color":"azul"}
7 {"titulo":"Revisar","completada":"quizás"}
8 {"titulo":"Revisar","completada":"true"}
9 cuerpo vacío, sin nada

Los resultados que sorprenden son estos cuatro, y conviene mirarlos despacio:

3 · el objeto vacío
200. Se crea una tarea con título null, prioridad null y completada=false. Tu API acaba de aceptar una tarea que no es nada.
5 y 6 · claves que no existen
200 las dos, y en silencio. Spring Boot configura Jackson para ignorar las claves desconocidas. Resulta indiferente que se trate de una errata propia o de un campo que el cliente se ha inventado: se descarta sin avisar.
7 · un tipo que no convierte
400: el JSON es sintácticamente válido, pero el texto «quizás» no puede convertirse al booleano esperado. Distingue un error de conversión de un JSON mal formado.
8 · un tipo que sí convierte
200, y completada vale true. El texto "true" entre comillas no es un booleano JSON, y aun así Jackson lo acepta y lo convierte. Es tolerante por defecto, y esa tolerancia es una decisión que se puede cambiar.

Paso 8 · Localizar la causa de deserialización en el mensaje de error

Cuando salga un 400, el cuerpo de la respuesta es escueto y casi inútil. El mensaje bueno está, como siempre, en la consola:

HttpMessageNotReadableException: JSON parse error:
Unexpected character ('}' (code 125)): was expecting double-quote to start field name
 at [Source: (line 1, column 26)]

Te dice la excepción, el motivo y la posición exacta. Acostúmbrate a leerla: en la UD3 vamos a convertir estos mensajes en respuestas útiles para el cliente, y no se puede transformar lo que no se sabe leer.

Paso 9 · Configurar el rechazo de campos JSON desconocidos

Que las claves desconocidas se ignoren responde a una configuración que Spring Boot aplica por defecto, no a un comportamiento inherente al protocolo. Puedes darle la vuelta:

spring.jackson.deserialization.fail-on-unknown-properties=true

Reinicia y vuelve a enviar el cuerpo número 6, el del color. Ahora responde 400.

Tolerante · lo que trae Spring

Un cliente antiguo que envía un campo ya retirado sigue funcionando. A cambio, una errata pasa desapercibida y se guarda un dato incompleto.

Estricto · fail-on-unknown-properties

Una errata se detecta al instante. A cambio, cualquier campo de más rompe la petición, y quien te consume tiene que ir exactamente a la par que tú.

Cuál elegir

La tolerancia o el rechazo forman parte del contrato de la API. Ignorar campos puede facilitar compatibilidad; rechazarlos permite detectar errores de nombre. No existe una elección obligatoria por el hecho de que la API sea pública.

En una API interna, o durante el desarrollo, ser estricto ahorra horas de depuración.

Para este curso, déjalo estricto mientras desarrollas la UD2 y la UD3 y decide tú al llegar al proyecto. Lo que no vale es no haberlo decidido.

Paso 10 · Recibir objetos anidados y listas en el JSON

Cada ampliación pertenece a Tarea.java: añade un campo cada vez, sus métodos de acceso y el import de su tipo. Para List<String> importa java.util.List; para LocalDate, java.time.LocalDate. Crea Responsable.java en model con nombre y email, constructor vacío y getters/setters antes de declarar Responsable responsable en Tarea. Después de cada ampliación, reinicia y envía su JSON al endpoint espejo. Esos objetos aún no son relaciones de base de datos.

Una lista dentro del objeto

Añade a Tarea un campo List<String> etiquetas con su getter y su setter, y envía:

{
  "titulo": "Revisar el login",
  "etiquetas": ["urgente", "movil", "regresion"]
}

Jackson construye la lista sola. No hay que hacer nada.

Un objeto dentro del objeto

Crea una clase Responsable con nombre y email, añádela como campo de Tarea, y envía:

{
  "titulo": "Revisar el login",
  "responsable": { "nombre": "Marc", "email": "marc@ejemplo.com" }
}

Jackson entra dentro y repite el mismo proceso con la clase interior. Es recursivo, y por eso funciona con estructuras de cualquier profundidad.

Una fecha

private LocalDate vencimiento;
{ "titulo": "Revisar el login", "vencimiento": "2026-09-15" }

Funciona con el formato ISO, que es año-mes-día con guiones. Prueba a enviar "15/09/2026" y observa el 400: no es que la fecha sea imposible, es que no está en el formato que se espera.

Una fecha siempre se escribe igual

En este contrato, las fechas se transmiten en formato ISO 8601, como 2026-09-15. Un texto como 03/04/2026 puede interpretarse como 3 de abril o 4 de marzo según la convención. Acordar año-mes-día evita esa ambigüedad; otros formatos requieren una configuración explícita.

Dar formato a la fecha para que se lea bonita es trabajo del cliente, no tuyo.

Paso 11 · Repetir el diagnóstico con otra entidad de tu dominio

Sobre tu proyecto:

  1. Amplía la clase Proyecto con una lista de String y una fecha.
  2. Crea un endpoint espejo para Proyecto.
  3. Construye tu propia tabla de seis cuerpos: dos válidos, dos inválidos y dos incompletos. Envíalos y anota código y valores resultantes.
  4. Para comparar ambas configuraciones, prueba primero con spring.jackson.deserialization.fail-on-unknown-properties=false, reinicia y ejecuta los seis casos. Cambia después a true, reinicia y repítelos. Conserva al terminar el modo estricto acordado para esta unidad.
  5. Escribe en dos frases qué configuración dejarías puesta en tu proyecto y por qué.

Paso 12 · Comprobar y registrar el resultado del proyecto

  1. Envía cada variación por separado y relaciona estado, mensaje de la consola y fase de procesamiento. Comprueba si llegó a ejecutarse el método del controlador.
  2. Compara un JSON válido completo, uno con una clave equivocada y otro mal formado. Documenta cuándo hay rechazo y cuándo se construye un objeto con valores por defecto.

Ampliación si has completado el trabajo

Primero termina y verifica los pasos anteriores. Estos retos profundizan en el mismo contenido; no sustituyen la entrega ni obligan a iniciar otro proyecto.

Reto · Predecir qué ruta selecciona Spring

En un controlador de diagnóstico separado, con prefijo /diagnostico-rutas, declara un GET /{id} que reciba un int y devuelva su valor, y otro GET /nueva que devuelva «Ruta literal». Utiliza un prefijo separado para no duplicar rutas de tu proyecto.

  1. Predice la respuesta de /diagnostico-rutas/nueva, /diagnostico-rutas/7 y /diagnostico-rutas/abc.
  2. Ejecuta y compara los logs: la ruta literal /nueva tiene prioridad frente a /{id}; debe responder 200 con «Ruta literal». El número entra en detalle y abc no se convierte a int, por lo que responde 400.
  3. Quita temporalmente solo la anotación del método /nueva y repite esa petición: ahora coincide con /{id} y falla la conversión a int.
  4. Recupera la anotación, repite y retira el controlador de diagnóstico al terminar. Explica qué diferencia hay entre seleccionar una ruta y construir los argumentos.
Comprobar la explicación

El método literal sí se ejecuta: no hay una avería en su definición. Al retirar su mapeo, la misma URL pasa a coincidir con la variable. No importa qué método se escribió antes en el archivo, sino la especificidad de la ruta.

Reto · El campo que desaparece

Sin ejecutarlo todavía, predice qué devuelve este endpoint espejo al recibir el cuerpo de abajo. Escribe el JSON de respuesta entero, clave por clave:

public class Incidencia {

    private int id;
    private String titulo;
    private String estado;
    private boolean urgente;

    public Incidencia() {
    }

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }

    public String getTitulo() { return titulo; }
    public void setTitulo(String titulo) { this.titulo = titulo; }

    public String getEstado() { return estado; }
    // sin setter de estado

    public void setUrgente(boolean urgente) { this.urgente = urgente; }
    // sin getter de urgente
}
{
  "id": 7,
  "titulo": "Caída del servidor",
  "estado": "abierta",
  "urgente": true,
  "prioridad": 3
}

Preguntas:

  1. ¿Qué código de estado devuelve?
  2. ¿Qué valor tiene estado dentro del objeto Java? ¿Y en el JSON de respuesta?
  3. ¿Qué valor tiene urgente dentro del objeto Java? ¿Y en el JSON de respuesta?
  4. ¿Qué ha pasado con prioridad?
  5. Comprueba si Jackson admite estado a pesar de que no haya setter. Con la configuración habitual puede inferir el campo privado a partir de su getter: no deduzcas el contrato mirando solo los setters. Localiza urgente con el depurador, porque no tiene getter para observarlo en el JSON.

Para este reto, prueba primero con campos desconocidos permitidos y después en modo estricto. El campo prioridad, que no existe, debe provocar rechazo en modo estricto. Restaura esa configuración al finalizar. Anota las diferencias observadas en lugar de asumir que quitar un setter bloquea la entrada.

Objetivo mínimoEl endpoint espejo funcionando y los nueve cuerpos enviados con su resultado anotado.
Si lo tienesEl espejo de proyectos con lista y fecha, y la comparación con y sin tolerancia.
RetoEl comportamiento de las propiedades de Incidencia comprobado con ambas configuraciones y sus diferencias explicadas.
Ver respuestas

1 · Las claves del JSON. La consecuencia es que lo que no venga en el cuerpo no se toca y conserva el valor por defecto de Java, sin que nadie avise.

2 · Porque el primero es JSON perfectamente válido que simplemente no trae datos, y el segundo no es JSON: falla al leerlo, antes de intentar construir nada.

3 · En ISO 8601, 2026-09-15. Porque los formatos nacionales son ambiguos entre sí y el servidor no puede adivinar cuál usa quien llama. Formatearla es trabajo del cliente.

4 · Entra, porque para deserializar Jackson usa los setters. No sale, porque para serializar usa los getters y no hay ninguno.

Cierre

15 minutos · resultado comprobable y explicación individual

Al terminar la sesión:

Puedes relacionar método, ruta, parámetros, cabeceras y cuerpo con su origen; distinguir JSON mal formado, conversión incompatible y datos incompletos; y reproducir el contraste entre configuración tolerante y estricta. Las entidades admiten la lista y la fecha previstas. Conserva las peticiones que demuestran los resultados y publica los cambios mediante una PR.

Cada integrante explica una decisión del código apoyándose en una de las comprobaciones realizadas.