Proyecto: tienda de merchandising
Introducción
En este proyecto desarrollarás la aplicación Web de una tienda en línea de merchandising. La tienda estará especializada en un sector comercial concreto de tu elección, como por ejemplo camisetas de equipos de fútbol, figuras de acción de personajes de películas, ropa y otros productos promocionales de grupos musicales, etc.
En la aplicación habrá dos tipos de usuarios: clientes y administradores. Los administradores podrán gestionar el catálogo de productos de la tienda (añadir nuevos productos, publicarlos para hacerlos visibles a los clientes o retirarlos para que dejen de ser visibles). Los clientes podrán navegar el catálogo de productos, añadir productos a su carro de la compra, examinar su carro de la compra y vaciarlo.
Los administradores deben poder añadir al catálogo productos fijos y productos no fijos. Los productos fijos son aquellos que el cliente no puede personalizar o configurar de ninguna forma (por ejemplo, una taza con diseño y tamaño fijos). Los productos no fijos son aquellos en que a los clientes se les ofrecen diversas opciones para elegir, o se les ofrece establecer un mensaje personalizado. Son ejemplos de productos no fijos los siguientes:
- Una sudadera en que el cliente puede elegir aspectos como el color, la talla, el diseño, etc.
- Una camiseta de fútbol en que el cliente puede elegir, además de la talla, equipo o futbolista.
- Una camiseta de fútbol personalizada en que el cliente puede, además de elegir talla y color, introducir un nombre y un número que aparecerán impresos.
- Un libro en que el cliente puede pedir una dedicatoria o una firma de su autor o autora. Por una parte, el cliente escogería entre las opciones de comprar un ejemplar firmado o sin firmar. Por otra, en caso de desear que la firma incluyese una dedicatoria personalizada, se le ofrecería introducir su nombre y un pequeño mensaje para el autor o autora.
El tipo de personalización escogido podría tener efectos en el precio del producto. Por ejemplo:
- En el caso de la sudadera, algunos colores o diseños podrían ser más caros que otros.
- En el caso de la camiseta de fútbol, algunas combinaciones de equipo y futbolista podrían ser más caras que otras.
- En el caso de la camiseta de fútbol personalizada, sin embargo, resultaría natural que el precio no variase en función del texto o número introducidos.
- En el caso del libro con dedicatoria o firma, el libro estándar tendría un precio, la firma sería más cara y la firma con dedicatoria personalizada sería aun más cara.
Además de escoger un sector comercial concreto para tu tienda, puedes decidir entre hacerla más general (por ejemplo, camisetas o merchandising de distintos equipos de fútbol) o más específica (por ejemplo, merchandising variado, como camisetas, chándales, balones, mochilas, etc,) de un único equipo de fútbol. En todo caso, debes evitar acotar en exceso el ámbito de tu tienda. Por ejemplo, no se puede desarrollar una tienda que solo venda camisetas de un único equipo de fútbol.
El proyecto se dividirá en funcionalidad obligatoria, que toda entrega debe implementar, funcionalidad adicional y funcionalidad avanzada. Tienes libertad para decidir qué funcionalidad adicional y avanzada deseas implementar en tu proyecto.
Condiciones generales
Las siguientes condiciones aplican al proyecto:
- El proyecto debe ser desarrollado en parejas. Otras configuraciones de grupo solo serán permitidas bajo circunstancias excepcionales, y siempre con la aprobación previa de los profesores. La composición de cada pareja debe ser comunicada a los profesores con antelación, en los plazos y medios que se establezcan.
- El proyecto debe ser desarrollado con el lenguaje Java y el framework Spring MVC, usando Spring Data JPA para acceder a la base de datos y Thymeleaf para generar las vistas HTML.
- Las entregas deben funcionar en los ordenadores de los laboratorios del Depto. de Ingeniería Telemática (físicos o virtuales) con las bases de datos proporcionadas. Puedes trabajar en tus propios ordenadores si lo prefieres, pero debes comprobar regularmente que tu código funcione correctamente en los ordenadores del laboratorio.
- La base de datos que configures en el código de tu entrega debe contener suficientes datos en el momento en que realices la entrega para que los profesores puedan probar tu aplicación adecuadamente.
- Compartir código entre diferentes grupos está estrictamente prohibido. Será considerado y denunciado como plagio conforme a la normativa en vigor.
- Reutilizar código de las prácticas está permitido. Por ejemplo, puedes reutilizar el código de autenticación de usuarios y creación de cuentas.
- Todos los miembros de un grupo deben participar activa y equitativamente en el desarrollo del proyecto. Los grupos en que esto no suceda pueden ser divididos por los profesores antes de la fecha de entrega, incluso con poca antelación a la misma. Las calificaciones de algunos miembros de un grupo podrían diferir de las de los demás si los profesores tienen evidencias de que su participación no ha sido suficiente. Junto con la entrega, cada equipo debe adjuntar un documento detallando las contribuciones de cada uno de sus integrantes a dicha entrega.
- El uso de herramientas de inteligencia artificial generativa está permitido, siempre que no incluyas grandes fragmentos de código generados por una inteligencia artificial en tu entrega. Cada equipo debe adjuntar a su entrega una declaración acerca de su uso de inteligencia artificial generativa, según las instrucciones en la sección Más detalles sobre el uso de herramientas de inteligencia artificial generativa.
Funcionalidad obligatoria
Cualquier funcionalidad que no se mencione expresamente en esta sección no es obligatoria. La funcionalidad no obligatoria que implementes será evaluada como funcionalidad adicional o funcionalidad avanzada.
Todas las entregas deben implementar la siguiente funcionalidad:
- Cuentas de usuario: los usuarios pueden iniciar y cerrar sesión. Existirán dos tipos de usuarios: clientes y administradores. Los usuarios pueden crear nuevas cuentas de tipo cliente rellenando un formulario con sus datos. Para crear cuentas de tipo administrador, se puede usar la aplicación para crear primero una cuenta de tipo cliente, y se modificar a continuación el tipo de la cuenta directamente en la base de datos mediante SQL.
- Crear productos: los administradores pueden crear nuevos productos (fijos y no fijos). Para los productos no fijos, los administradores podrán definir las opciones disponibles (véase Productos no fijos para más detalles). Cada producto tendrá un precio base, que será el precio final para los productos fijos, y el precio mínimo para los productos no fijos, cuyo precio final dependerá de las opciones de personalización elegidas por los clientes. Los administradores podrán subir una imagen para cada producto, que se guardará en la base de datos y se mostrará a los clientes, como mínimo, en la vista del producto (véase Gestión de las imágenes de productos para más detalles). Los productos creados por los administradores se guardarán en la base de datos, pero no serán visibles para los clientes hasta que el administrador los publique explícitamente.
- Publicar y retirar productos: los administradores podrán publicar los productos creados, haciendo que sean visibles para los clientes. También podrán retirar productos publicados, haciendo que dejen de ser visibles para los clientes sin eliminarlos de la base de datos. Solo los productos publicados pueden ser añadidos por lo clientes al carro o ser mostrados en la portada o en listados del catálogo. Si un cliente dispone ya de un producto en su carro, y el administrador retira dicho producto, el cliente podrá seguir viendo el producto en su carro.
- Navegar el catálogo de productos y ver detalles de un producto: los clientes podrán navegar el catálogo de productos. Para cada producto, podrán ver su precio, imagen y demás información detallada. Para los productos no fijos, los clientes podrán seleccionar entre las opciones disponibles o introducir datos de personalización según corresponda (véase Productos no fijos para más detalles). Se actualizará el precio a medida que el cliente vaya seleccionando opciones. No se exige que el cliente esté autenticado para acceder a esta funcionalidad.
- Añadir productos al carro de la compra: los clientes autenticados podrán añadir una o más unidades de uno o más productos a su carro de la compra. El carro de la compra se guardará en la base de datos, de tal forma que sea accesible al usuario en sesiones futuras y en diferentes dispositivos. De los productos no fijos, se guardará también en el carro toda la información relativa a las opciones elegidas.
- Ver el carro de la compra: los usuarios autenticados podrán ver su carro de la compra, el importe producto a producto y el importe total del carro de la compra. Además, podrán eliminar productos individuales del carro o vaciarlo por completo en una sola acción. De los productos no fijos, se mostrará toda la información relativa a las opciones elegidas. Cada producto del carro enlazará con su vista de producto.
Productos no fijos
Cuando creen un producto no fijo, los administradores podrán definir las opciones disponibles. Para cada uno de estos productos, el administrador podrá crear campos de personalización de distintos tipos. Los tipos de campo de personalización a implementar dependerán del tipo concreto de productos que ofrezca tu tienda. Debes incluir, como mínimo, la posibilidad de añadir campos de los dos tipos siguientes:
- Campo de selección entre varias opciones: el cliente debe elegir una opción entre varias disponibles. Por ejemplo, en el caso de una sudadera, se podrían definir tres campos distintos para seleccionar un color, una talla y un diseño. Cuando el administrador defina un campo de este tipo, deberá introducir los posibles valores para el campo, de entre los cuales el cliente deberá seleccionar uno (por ejemplo, para el campo de talla, los posibles valores podrían ser S, M, L y XL). Otro ejemplo de este tipo de campo sería aquel que permita a un cliente elegir si desea un libro firmado o sin firmar.
- Campo de texto: el cliente puede introducir un valor de texto. Por ejemplo, en el caso de una camiseta de fútbol personalizada, donde el usuario pueda elegir un nombre y un número para imprimir en la camiseta, el administrador definiría un campo de texto para el nombre y otro campo de texto para el número. Los administradores podrán restringir el formato del texto a introducir en un campo de este tipo mediante una expresión regular de Java (véase Uso de expresiones regulares para validar datos). Los administradores podrán también marcar un campo de este tipo como opcional, de tal forma que los clientes no estén obligados a introducir un valor para el mismo, o como obligatorio, en cuyo caso sí lo estaría.
Cada campo de personalización tendrá una etiqueta asociada como, por ejemplo, "color", "talla", "diseño", "nombre a imprimir" o "número a imprimir".
Por otra parte, distintas opciones de personalización podrían tener distintos efectos en el precio del producto. Para dar soporte a esto, para cada producto se definirá un precio base. En los campos de personalización de selección entre varias opciones, los administradores asociarán a cada opción un precio adicional a sumar al precio base. El precio adicional puede depender de la opción concreta, y para algunas opciones este puede ser cero. En los campos de personalización de texto opcionales, los administradores podrán definir un precio adicional fijo a sumar al precio base si los clientes introducen un valor para el campo. Si el campo es obligatorio, lo razonable es que definan cero como precio adicional, dado que se entiende que el precio base ya incluye la personalización de dicho campo.
Cuando a un cliente se le presente la vista de un producto no fijo, se le presentará también un formulario para que seleccione un valor para cada uno de los campos de personalización definidos para dicho producto. Para cada campo se mostrará su etiqueta en el formulario. La aplicación debe actualizar el precio del producto dinámicamente a medida que el cliente vaya rellenando el formulario, usando JavaScript para ello.
Cuando a un usuario se le muestre su carro de la compra, se le mostrará, para cada campo de personalización, la etiqueta del campo, el valor elegido por el cliente y el precio adicional asociado a dicho valor.
Modelo de datos
Tu proyecto debe dar soporte al siguiente modelo de datos para la funcionalidad obligatoria. Puedes desviarte de este modelo si tienes razones justificadas para hacerlo. Por ejemplo, puedes enriquecer este modelo de datos debido a las necesidades de cualquier funcionalidad adicional que implementes (añadir nuevas entidades, añadir nuevas propiedades a algunas entidades, etc.), o si crees que otro modelo diferente es mejor en el contexto de tu diseño del proyecto.
Las principales entidades a almacenar en la base de datos son:
- Usuarios: se identifica a los usuarios por su dirección de correo electrónico, que será única en la aplicación. Cada usuario se autentica mediante dicha dirección de correo electrónico y una contraseña, que debe ser almacenada en la base de datos de forma segura, cifrada y con valor de salt (por ejemplo, usando bcrypt como en las prácticas). Se deben guardar también, al menos, el nombre y apellidos del usuario. Los usuarios pueden ser de tipo cliente o administrador.
- Productos: para cada producto se guardarán un nombre, una descripción y un precio base. Además, habrá otros campos con información específica, que dependerán del tipo de productos que ofrezca tu tienda. Por ejemplo, si se trata de una tienda de libros, se podrían guardar el autor, la editorial, el número de páginas, el ISBN, etc. Dispondrán también de un campo para indicar si el producto está o no publicado, esto es, si es visible para los clientes o no.
- Imágenes de productos: para cada producto se guardará una imagen. Desde el punto de vista de la funcionalidad obligatoria, existe una relación uno a uno entre productos e imágenes de productos (un producto tiene una imagen y una imagen corresponde a un solo producto).
- Campos de personalización: para cada campo de personalización se incluyen su etiqueta y el tipo de campo de personalización (por ejemplo, selección entre varias opciones o campo de texto). Se incluyen también atributos específicos de cada tipo de campo de personalización si procede. Por ejemplo, los campos de texto necesitan un atributo para indicar si son opcionales u obligatorios, otro para la expresión regular que restringe el formato del texto a introducir y otro para el precio adicional a sumar al precio base del producto si el cliente introduce un valor para el campo. Existe una relación uno a muchos entre productos y campos de personalización (un producto puede tener varios campos de personalización y un campo de personalización está ligado a exactamente un producto).
- Opciones de campos de personalización: para cada campo de personalización de selección entre varias opciones, se guardará cada opción con su etiqueta textual (la que se mostrará al cliente en la lista de opciones) y su precio adicional. Existe una relación uno a muchos entre campos de personalización y opciones de campos (un campo de personalización puede tener varias opciones, y una opción está ligada a exactamente un campo de personalización).
- Producto seleccionado: para cada producto que un cliente añada a su carro o a una compra, se guardará el producto seleccionado y el número de unidades añadidas o compradas. Para los productos no fijos, se guardará también el valor seleccionado para cada uno de sus campos de personalización (se trata de una relación uno a muchos con los campos de personalización completados, según lo explicado en el siguiente punto).
-
Campo de personalización completado:
para cada campo de personalización que un cliente complete,
se guardará el valor introducido por el cliente.
Existe una relación muchos a uno
entre campos de personalización completados y productos seleccionados
(un producto seleccionado puede tener varios campos de personalización completados,
y un campo de personalización completado está ligado a exactamente un producto seleccionado).
Se puede crear una única entidad de este tipo
con un atributo que represente una relación con la opción de campo de personalización elegida,
para rellenar en el caso de los campos de selección entre varias opciones
(relación muchos a uno con las opciones de campos de personalización),
y otro atributo distinto que represente el texto introducido por el cliente,
para rellenar en el caso de los campos de texto.
Cuando se complete un campo de personalización de selección entre varias opciones,
se guardará (como relación) la opción de campo de personalización elegida por el cliente,
y se pondrá a
NULLel otro atributo. Por el contrario, si se trata de un campo de texto, se establecería el valorNULLen la relación anterior. El atributo del texto introducido tomaría también valorNULLpara campos opcionales no seleccionados, y un texto para campos obligatorios o para campos opcionales que el cliente haya decidido seleccionar. - Carro de la compra: el carro de la compra de cada usuario estará compuesto por diversos productos seleccionados. Se trata de una relación uno a muchos (un carro de la compra puede contener varios productos seleccionados, pero un producto seleccionado solo puede estar en un carro de la compra).
Vistas
Tu aplicación debe proporcionar al menos las siguientes vistas:
- Vista principal (abierta también a usuarios no autenticados): la composición de esta vista es libre, con el único requisito de que debe dar acceso a las funcionalidades de la tienda. Por ejemplo, se pueden presentar productos destacados de la tienda, un formulario de búsqueda de productos, campañas comerciales actuales, etc.
- Vista de creación de producto (abierta a usuarios de tipo administrador autenticados): se presenta a los administradores un formulario para crear un nuevo producto fijo o no fijo. Si el producto es de tipo no fijo, el administrador puede crear uno o más campos de personalización.
- Vista de producto (abierta también a usuarios no autenticados): se presenta la información detallada de un producto, incluyendo su precio. Para los productos no fijos, se muestra también el formulario para seleccionar las opciones de personalización (se proporciona una pista acerca de cómo recoger estos parámetros a continuación, en el método del controlador, en Recogida de parámetros de la petición con nombre no conocido) y se informa de cómo estos afectan al precio del producto. Se debe actualizar dinámicamente mediante JavaScript el precio mostrado al cliente según este vaya haciendo en el formulario. Si el usuario está autenticado y es cliente, se mostrará un botón para añadir el producto al carro y un control para seleccionar el número de unidades a añadir. Si el usuario autenticado es administrador, se mostrará un botón para publicar o retirar el producto, según corresponda.
- Vista de carro de la compra (abierta solo a usuarios autenticados): se presenta el contenido actual del carro de la compra del usuario. Se permitirá al usuario eliminar un producto del carro de la compra o vaciarlo por completo con una sola acción. Estas acciones eliminarán también de la base de datos, en cascada, todos los objetos de la entidad de productos seleccionados que haya en la base de datos relacionados con el carro de la compra. También se eliminarán de la base de datos, a su vez, todos los objetos de la entidad de campo de personalización completado ligados a dichos productos seleccionados.
Puedes tomar la lista de vistas anterior como una sugerencia. Eres libre de diseñar tu aplicación con vistas diferentes siempre y cuando proporciones la misma funcionalidad. También puedes cambiar estas vistas para acomodar funcionalidad adicional.
Funcionalidad adicional
El resto de la funcionalidad a desarrollar podrá ser elegida libremente por cada grupo, para obtener hasta 1,5 puntos adicionales conforme a los criterios de evaluación.
Una o dos funciones adicionales, dependiendo de su complejidad, podrían aportar la nota máxima de este apartado. Puedes consultar con los profesores en cualquier momento para saber a qué nota aspiras según lo que pretendas implementar.
Funcionalidad avanzada
Para obtener el punto de funcionalidad avanzada previsto en los criterios de evaluación debes hacer uso de otras tecnologías que no se hayan visto en clase o se hayan visto con menor profundidad.
Se proponen, a modo de referencia, algunas posibilidades a continuación, pero puedes implementar cualquier otra que pactes previamente con los profesores:
- Usar funcionalidad del ecosistema de Spring que no se haya visto en clase.
- Mejorar la apariencia, facilidad de uso o fluidez de la aplicación, así como introducir nueva funcionalidad mediante un uso más intensivo de JavaScript. Se valorará especialmente que lo combines con el envío de peticiones asíncronas al servidor, intercambiando información con el mismo en formato JSON.
- Usar las nuevas APIs de JavaScript, disponibles dentro del marco de HTML5 o proporcionadas por terceros, para integrar funcionalidad útil en el contexto de tu aplicación web.
- Diseñar un aspecto visual y experiencia de uso con apariencia profesional y moderna. Se valorará como funcionalidad avanzada este aspecto de tu aplicación únicamente si, por su calidad, está a la altura de aplicaciones profesionales actuales.
- Usar funcionalidad de CSS que no se hayan visto en clase como, por ejemplo, Flexbox.
- Diseñar la aplicación para que se adapte bien tanto a ordenadores de escritorio como a dispositivos móviles con diversos tamaños de pantalla (teléfonos móviles, tabletas). Puedes, opcionalmente, utilizar para ello Bootstrap o algún otro entorno similar que facilite alcanzar este objetivo. No obstante, la implementación de este aspecto debe ir más allá de simplemente utilizar dichos entornos, siendo necesario que cuides con detalle cada una de las vistas y acciones a realizar para garantizar que realmente se adapten bien a dispositivos móviles. Las herramientas para desarrolladores del navegador te permitirán comprobar cómo se vería la aplicación en dispositivos de pantalla pequeña.
- Enviar confirmaciones por correo electrónico a los usuarios cuando realicen ciertas acciones en la aplicación, como crear una cuenta.
- Implementar otras medidas de seguridad adicionales, distintas de las que se consideren obligatorias en este enunciado o que Spring MVC o Spring Security implementen por defecto.
Cada aspecto avanzado se evaluará en función de la complejidad de su implementación, teniendo además en cuenta la dificultad técnica, documentación disponible, etc.
También puedes implementar otros aspectos avanzados propuestos por ti, siempre que supongan profundizar en alguna de las tecnologías vistas en clase o el uso de otras tecnologías nuevas. Debes consultar previamente con los profesores para saber si tu propuesta resulta adecuada y obtener una estimación de cómo se valoraría en tu calificación. No se valorará que desarrolles nueva funcionalidad que simplemente consista en utilizar las mismas técnicas que se usan en la funcionalidad obligatoria y adicional.
Uno de los aspectos que más se valorarán con respecto a esta funcionalidad avanzada es tu capacidad para utilizar la documentación disponible y resolver los problemas de forma autónoma. Por ello, contarás con un soporte más limitado por parte de los profesores que para la funcionalidad obligatoria y adicional.
Es muy aconsejable que, al principio del proyecto o durante el desarrollo del mismo, pidas a los profesores una estimación de la puntuación que obtendrías por la implementación de cada función adicional o avanzada que te plantees realizar. Esto te permitirá conocer qué debes desarrollar para alcanzar la puntuación a la que aspires.
Criterios de evaluación
Aviso: este proyecto es una prueba de evaluación. El plagio y otros tipos de fraude académico relativos al mismo serán denunciados a las autoridades académicas conforme a la normativa en vigor.
En la evaluación se tendrá en cuenta la correcta implementación de la funcionalidad obligatoria, funcionalidad adicional y funcionalidad avanzada, así como diversos aspectos de calidad de su implementación:
- Funcionalidad obligatoria (7,5 puntos): la máxima puntuación en este bloque se otorgará a las entregas con una implementación de toda la funcionalidad obligatoria que, además de funcionar correctamente, cumpla con todos los criterios de calidad que se detallan a continuación.
- Funcionalidad adicional (1,5 puntos): puedes integrar otras funciones en tu aplicación que decidas libremente. Consulta el apartado de funcionalidad adicional para más detalles. Para que se te otorguen puntos, la funcionalidad adicional debe estar implementada correctamente y cumplir con los requisitos de calidad.
- Funcionalidad avanzada (1 punto): la correcta implementación de funciones avanzadas te permitirá obtener 1 punto más. Si los profesores consideran especialmente meritoria tu implementación de una función avanzada, esta podría compensar parcialmente la puntuación perdida por errores menores en la implementación de la funcionalidad obligatoria, así como puntuar igualmente en el apartado de funcionalidad adicional. Por ejemplo, puedes optar al 10 en el proyecto sin obtener puntos de funcionalidad adicional, obteniendo 2,5 puntos de funcionalidad avanzada.
Para los equipos de 3 personas que hayan sido autorizados de forma excepcional, se fijan la puntuación máxima de la funcionalidad obligatoria en 6,5 puntos, de la funcionalidad adicional en 2,5 puntos y de la funcionalidad avanzada en 1 punto.
Los criterios de calidad que se espera que cumpla tu proyecto son:
- Que cada función implementada funcione correctamente, es decir, se comporte como se requiere.
- Que las vistas de la aplicación (su interfaz de usuario) sean claras y fáciles de usar. Que la apariencia visual sea razonablemente buena.
- Que el código fuente esté correctamente organizado y sea claro de leer y entender. Debe seguir los principios explicados en los laboratorios de Spring MVC en cuanto a organización del código. Si bien puedes dejar comentarios en partes especialmente complejas del código, no es necesario que haya comentarios exhaustivos de todo el código.
- Que el diseño del esquema de la base de datos sea adecuado, incluyendo la división de los datos en entidades (tablas), el tipo de datos de cada atributo (columna) y la declaración de relaciones uno a muchos, muchos a muchos, etc. entre clases (tablas).
- Que el uso de HTML y CSS sea correcto. Los documentos HTML deben incluir el contenido de la página, mientras que las propiedades de estilo deben ser declaradas mediante CSS. Salvo en casos justificados, debería haber una única hoja de estilos CSS compartida por todas las vistas de tu aplicación. Se deben utilizar los elementos de HTML apropiados para cada caso (por ejemplo, en un formulario cada control debe ser del tipo más adecuado para el dato a recibir).
- Que la aplicación maneje correctamente situaciones de error potenciales, mostrando a los usuarios mensajes de error informativos cuando sea apropiado. No se considera apropiado que la aplicación muestre trazas de excepción o mensajes de error del servidor para situaciones previsibles (por ejemplo, que el usuario no rellene un control obligatorio en un formulario).
- Que los controladores comprueben todos los datos que reciban de los usuarios, incluso si dichos datos están ya siendo controlados en el lado del cliente. No comprobar los datos en el lado del servidor abre la puerta a múltiples vulnerabilidades potenciales.
- Que haya controles de acceso adecuados. Por ejemplo, solo los administradores pueden crear productos, establecer la foto de los productos y publicarlos o retirarlos. Solo los clientes pueden añadir productos al carro de la compra. Cada cliente solo puede ver su propio carro de la compra.
- Que la aplicación no sea vulnerable a ataques de inyección de SQL, cross-site scripting o cross-site request forgery. Si programas la aplicación siguiendo las buenas prácticas vistas en las prácticas de Spring MVC, la aplicación no será vulnerable a estos ataques.
En las clases de seguridad en aplicaciones Web se explicarán detalle estas y otras vulnerabilidades frecuentes, y cómo evitarlas.
Sugerencias de implementación
En esta sección se incluyen algunas sugerencias que pueden ser útiles para la implementación de tu proyecto.
Gestión de las imágenes de productos
De acuerdo con el enunciado, cuando un administrador cree un producto, podrá subir una imagen para el mismo. En este apartado se incluyen pistas para implementar esta funcionalidad, utilizando la base de datos de la aplicación para almacenar las imágenes.
Desde el punto de vista de la funcionalidad obligatoria, hay una relación uno a uno entre productos e imágenes de productos (cada producto tiene una imagen, y cada imagen está asociada a un único producto). Por ello, se puede crear una entidad de imágenes de productos de la siguiente forma:
@Entity
public class Photo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(nullable = false)
private String imageType;
@Lob
@Column(nullable = false, length = 256 * 1024)
private byte[] imageData;
@OneToOne
@JoinColumn(name = "product_id")
private Product product;
// ...getters y setters...
}
image/jpeg o image/png).
@Lob indica que el atributo es un objeto grande.
Permite almacenar campos de gran tamaño en la base de datos,
ya sean de tipo textual o binario.
length de la anotación @Column
se utiliza para especificar el tamaño máximo permitido para el campo de la base de datos.
En este caso, se establece un límite de 256 KB para cada imagen.
El sistema de JPA elegirá el tipo de columna más adecuado conforme a este límite.
@OneToOne indica que existe una relación uno a uno
entre la entidad de imagen y la entidad de producto.
En este caso, cada imagen está asociada a un único producto,
y cada producto tiene una única imagen.
Además, debes añadir a la entidad del producto el atributo correspondiente al otro lado de la relación, con su correspondiente anotación de JPA. Puedes declararla como opcional en dicho lado, para permitir que, temporalmente, un producto no tenga imagen asociada (por ejemplo, justo después de haber sido creado). También debes declarar una interfaz de repositorio para esta nueva entidad.
Aunque no es obligatorio hacerlo así,
se sugiere permitir a los administradores subir la imagen de un producto
después de haberlo creado.
De esta forma,
también será posible que los administradores cambien la imagen del producto
en cualquier momento.
Se usará un formulario para subir la imagen de un producto,
que podría estar incluido, por ejemplo,
en la vista de un producto,
mostrándose solo en el caso de que el usuario autenticado sea un administrador.
Este debe incluir un control de tipo file,
subir los datos con una petición de tipo POST
y, además, codificar los datos con multipart/form-data.
El motivo es que los controles de tipo file
solo pueden subir el contenido del fichero
con dicha combinación de método y codificación:
<form th:action="@{/photos/upload}" method="post" enctype="multipart/form-data">
<input type="hidden" name="productId" th:value="${product.id}">
<input type="file" name="photo" accept="image/*" required>
<input type="submit" value="Upload photo">
</form>
multipart/form-data,
que es el formato adecuado para subir ficheros eficientemente.
Solo se puede usar este tipo de codificación con el método POST,
dado que los datos codificados de esta forma deben ser enviados necesariamente
en el cuerpo de la petición HTTP.
accept del control de tipo file
se utiliza para indicar al navegador
que solo debe permitir seleccionar ficheros de tipo imagen.
En el lado del servidor, necesitaremos un método de controlador que gestione las peticiones enviadas desde dicho formulario, y almacene la imagen en la base de datos. Además, necesitaremos otro método de controlador que devuelva estas imágenes desde la base de datos. Programaremos una nueva clase de tipo controlador dedicada exclusivamente a la gestión de las imágenes de productos (aunque nada impide que simplemente añadamos estos métodos a otro controlador ya existente) con dichos dos métodos:
@Controller
@RequestMapping(path = "/photos")
public class PhotoController {
@Autowired
private ProductRepository productRepository;
@Autowired
private PhotoRepository photoRepository;
@PostMapping(path = "/upload")
public String uploadPhoto(@RequestParam("photo") MultipartFile file,
@RequestParam("productId") Product product,
Principal principal) {
User user = userRepository.findByEmail(principal.getName());
// ...comprueba aquí que el usuario sea administrador...
if (file.isEmpty()) {
return "redirect:/product?id=" + product.getId() + "&error=empty";
}
if (file.getSize() > 256 * 1024) {
return "redirect:/product?id=" + product.getId() + "&error=size";
}
if (product.getPhoto() != null) {
photoRepository.delete(product.getPhoto());
}
Photo photo = new Photo();
photo.setProduct(product);
photo.setImageType(file.getContentType());
try {
photo.setImageData(file.getBytes());
} catch (IOException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
"Error processing the uploaded file", e);
}
photoRepository.save(photo);
return "redirect:/product/" + product.getId();
}
@GetMapping(path = "/product/{productId}")
public ResponseEntity<byte[]> getProductPhotos(
@PathVariable("productId") Product product) {
if (product.getPhoto() == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
"Photo not found for product id " + product.getId());
}
MediaType mediaType;
if (photo.getImageType() != null) {
mediaType = MediaType.parseMediaType(photo.getImageType());
} else {
mediaType = MediaType.APPLICATION_OCTET_STREAM;
}
return ResponseEntity.ok()
.contentType(mediaType)
.body(photo.getImageData());
}
}
/photos a todas sus rutas
de los métodos de este controlador.
Por ejemplo,
la ruta del método uploadPhoto será /photos/upload.
/photos/upload
(se concatena la ruta del controlador a la ruta del método)
con método POST.
MultipartFile
se utiliza para recibir el fichero subido por el usuario.
El nombre del control de tipo fichero en el formulario debe ser photo.
Product correspondiente a dicho identificador.
MultipartFile.getContentType().
ResponseEntity
otorga mayor control al método del controlador
sobre la respuesta HTTP a devolver.
Entre otros,
permite establecer el código de estado,
las cabeceras
y el cuerpo de la respuesta.
Product correspondiente a dicho identificador.
Content-Type.
200 OK.
Para mostrar la imagen de un producto en la vista de dicho producto,
se establecerá como ruta de dicha imagen la ruta del método getProductPhotos.
Por ejemplo:
<img th:src="@{/photos/product/{productId}(productId=${product.id})}" alt="Product photo">
Recogida de parámetros de la petición con nombre no conocido
Para utilizar la anotación @RequestParam de Spring MVC,
que recoge parámetros de la petición HTTP,
es necesario conocer el nombre de dichos parámetros
en el momento en que se programe el método del controlador.
Sin embargo,
en el caso de los campos de personalización de los productos no fijos,
el nombre y el número de los mismos es desconocido cuando este se programa,
porque depende del producto en concreto que se esté configurando.
Una opción para recoger estos parámetros
es utilizar el objeto HttpServletRequest que define el API de Servlet de Java
(recuerda que Spring MVC se construye sobre esta API).
Este objeto se puede recibir como parámetro de un método de controlador.
Por ejemplo:
@PostMapping(path = "/add-to-cart")
public String addToCart(@RequestParam("productId") Product product,
HttpServletRequest request,
Principal principal) {
// ...
for (Field field: product.getFields()) {
String fieldValue = request.getParameter("field-" + field.getId());
if (fieldValue != null) {
// ...guarda el valor del campo de personalización...
} else {
// ...el usuario no ha rellenado el campo de personalización...
}
// ...
}
// ...
}
HttpServletRequest representa la petición HTTP que el cliente ha enviado al servidor.
Permite acceder a toda la información de la petición, incluyendo los parámetros enviados por el cliente.
Spring proporciona este objeto automáticamente a los métodos de controlador que lo declaren como parámetro.
getParameter del objeto HttpServletRequest
se utiliza para recoger el valor de un parámetro de la petición HTTP
a partir de su nombre.
En este caso, asumimos que el nombre del parámetro se construye dinámicamente
a partir del identificador del campo de personalización.
Obviamente, es necesario que la plantilla Thymeleaf
que genera el formulario
utilice el mismo esquema de nombrado para los controles de los campos de personalización.
Uso de expresiones regulares para validar datos
El enunciado de la funcionalidad obligatoria establece que, para los campos de personalización de tipo texto, el administrador puede opcionalmente definir una expresión regular que restrinja los posibles valores que los clientes pueden introducir (por ejemplo, para que solo puedan introducir dígitos, solo letras y espacios, etc.). Java proporciona soporte para trabajar con expresiones regulares, por lo que incorporar esta funcionalidad en tu proyecto no debería ser complicado.
El administrador puede introducir cualquier expresión regular que sea válida en Java (véase la documentación acerca de expresiones regulares en Java). Algunos ejemplos de expresiones regulares válidas son los siguientes:
-
\d\d?: solo se pueden introducir uno o dos dígitos (números del 0 al 99), por ejemplo, para personalizar el número de una camiseta de fútbol. -
[a-zA-Z ]+: solo se pueden introducir letras y espacios, por ejemplo, para personalizar el nombre que aparecerá en una camiseta. -
[a-zA-Z0-9 ]{1,32}: solo se pueden introducir letras, dígitos y espacios, con mínimo un carácter y máximo 32.
La comprobación de si una determinada cadena de texto cumple o no una expresión regular
se realiza invocando el método matches de la clase String de Java,
que recibe la expresión regular como parámetro
(véase la
documentación de String.matches).
Cuando recibas desde el formulario una expresión regular introducida por el administrador,
puedes comprobar que sea válida
mediante el método compile de la clase Pattern de Java:
try {
Pattern.compile(regex);
} catch (PatternSyntaxException e) {
// ...la expresión regular no es válida...
}
compile es un método estático de la clase Pattern.
Devuelve un objeto Pattern, que en este caso no necesitamos
y, por ello, no guardamos.
compile lanzará una excepción PatternSyntaxException.
Más detalles sobre el uso de herramientas de inteligencia artificial generativa
El uso de inteligencia artificial generativa está permitido en el desarrollo del proyecto, siempre que no incluyas grandes fragmentos de código generados por una inteligencia artificial en tu entrega.
Todos los equipos deben adjuntar a su entrega una declaración acerca del uso de inteligencia artificial generativa que incluya, al menos, la siguiente información:
- Si se ha utilizado o no inteligencia artificial generativa en el desarrollo del proyecto.
- En caso afirmativo, el nombre de las herramientas de inteligencia artificial generativa utilizadas, cómo se han utilizado dichas herramientas, para qué propósitos y en qué partes del código.
Algunos ejemplos de usos recomendados de inteligencia artificial generativa son:
- Puedes preguntarle sobre fragmentos de código que no entiendas.
- Puedes pedirle que busque errores en fragmentos de código que hayas escrito.
- Puedes usarlo como asistente de auto-completado en tu entorno de desarrollo, para obtener rápidamente sugerencias de fragmentos de código a medida que escribes.
- Puedes pedirle que produzca unas pocas líneas de código para resolver un problema específico.
Se recomienda que siempre revises cualquier fragmento de código generado por una inteligencia artificial antes de incluirlo en tu proyecto, y que te asegures de entender cómo funciona cualquier fragmento de código que incluyas. Además, no confíes ciegamente en los fragmentos de código que la herramienta de inteligencia artificial generativa produzca. Pueden contener errores o hacer algo diferente de lo que necesitas.
Por último, recuerda que tendrás que programar en el examen final, en papel, modelos de datos, consultas a la base de datos, funciones de controlador, plantillas, etc. similares a los del proyecto, y que se exige una nota mínima en dicho examen. Independientemente de cómo uses la inteligencia artificial generativa, asegúrate de ser capaz de programarlos sin ayuda.