<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Josué Leiva - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Descubre miles de cursos de programación escritos por expertos. Aprende Desarrollo Web, Ciencia de Datos, DevOps, Seguridad y obtén asesoramiento profesional para desarrolladores. ]]>
        </description>
        <link>https://www.freecodecamp.org/espanol/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Josué Leiva - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/espanol/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 18 Jun 2026 04:55:59 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/espanol/news/author/technopy/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ %.2f en Python – ¿Que significa? ]]>
                </title>
                <description>
                    <![CDATA[ En Python existen varios métodos para dar formato a los tipos de datos. El operador de formato %f es usado específicamente para dar formato a valores flotantes (números con decimales) Podemos utilizar %f para especificar el número de dígitos decimales a ser retornados cuando un número con punto flotante (decimal) ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/2f-en-python-que-significa/</link>
                <guid isPermaLink="false">6499ada917b3ed088cdd38f6</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Josué Leiva ]]>
                </dc:creator>
                <pubDate>Mon, 31 Jul 2023 00:19:49 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2023/06/susan-holt-simpson-GQ327RPuxhI-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/2f-in-python-what-does-it-mean/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">%.2f in Python – What does it Mean?</a>
      </p><p>En Python existen varios métodos para dar formato a los tipos de datos.</p><p>El operador de formato <code>%f</code> es usado específicamente para dar formato a valores flotantes (números con decimales)</p><p>Podemos utilizar <code>%f</code> para especificar el número de dígitos decimales a ser retornados cuando un número con punto flotante (decimal) es redondeado.</p><h2 id="c-mo-usar-el-formateador-f-en-python">Cómo usar el formateador <code>%f</code> en Python</h2><p>En esta sección, verás algunos ejemplos en cómo usar el formateador <code>%f</code> y los varios parámetros con los que puede ser usado para retornar diversos resultados.</p><p>Este es el primer ejemplo:</p><pre><code class="language-python">numero_flotante = 1.9876

print("%f" % numero_flotante)
# 1.987600</code></pre><p>El usar <code>%f</code> en el ejemplo de arriba añadió dos ceros al número. Pero no hay nada muy especial sobre ello. Pronto veremos que más podemos hacer para modificar el valor resultante.</p><p>Nota que el operador <code>%f</code> debe estar anidado dentro de comillas, y debe estar separado del número flotante al cual está formateando mediante el operador módulo (%): &nbsp;<code>"%f" % numero_flotante</code>.</p><p>Veamos otro ejemplo:</p><pre><code class="language-python">numero_flotante = 1.9876
print("%.1f" % numero_flotante)

#2.0</code></pre><p>En el código de arriba, hemos añadido .1 entre <code>%</code> y <code>f</code> en el operador <code>%f</code>. Esto significa que queremos redondear el número hasta un dígito decimal.</p><p>Nota que obtendrás un resultado similar al primer ejemplo si omites el punto (.) que viene antes del dígito que le entregamos en medio % y f.</p><p>El valor resultante en nuestro ejemplo es <code>2.0</code> el cual fue retornado cuando 1.9876 fue redondeado a un dígito decimal.</p><p>Usemos <code>%.2f</code> y veamos que sucede.</p><pre><code class="language-python">numero_flotante = 1.9876
print("%.2f" % numero_flotante)
# 1.99</code></pre><p>Como era de esperarse, el número de punto flotante (1.9876) fue redondeado hasta dos lugares decimales - 1.99. Por lo que <code>%.2f</code> significa redondear a dos espacios decimales.</p><p>Puedes jugar con el código para ver que sucede mientras cambias el número en el operador de formato.</p><h2 id="c-mo-usar-el-formateador-d-en-python">Cómo usar el formateador <code>%d</code> en Python</h2><p>Otro método de formateo que podemos usar con números de punto flotante en Python es el formateador <code>%d</code>. Este retorna el número entero en un número de punto flotante.</p><p>Aquí hay un ejemplo:</p><pre><code class="language-python">numero_flotante = 1.9876
print("%d" % numero_flotante)
# 1</code></pre><p>En el ejemplo de arriba, hemos creado un número de punto flotante: <code>numero_flotante = 1.9876</code></p><p>Cuando la variable <code>numero_flotante</code> es formateada usando <code>%d</code>, solo 1 fue retornado.</p><p>El formateador <code>%d</code> ignora los números decimales y solo retorna la parte entera. </p><h2 id="resumen">Resumen</h2><p>En este artículo, hablamos sobre el operador de formato <code>%f</code> en Python. Lo utilizas para darle formato a los números flotantes.</p><p>Dependiendo en los parámetros entregados, el formateador <code>%f</code> redondea un valor flotante al decimal más cercano provisto.</p><p>También hablamos del operador de formato, <code>%d</code> el cual solo retorna el valor entero de un número flotante.</p><p>¡Feliz codificación!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo crear APIs seguras con Flask y Auth0 ]]>
                </title>
                <description>
                    <![CDATA[ Las APIs son el corazón del desarrollo moderno. Soportan todo tipo de sistemas, desde móviles, web, y aplicaciones de escritorio, hasta dispositivos IoT y autos autónomos. Son un puente entre tus clientes y la lógica y almacenamiento de tu aplicación.  Este punto central de acceso a la información de ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-crear-apis-seguras-con-flask-y-auth0/</link>
                <guid isPermaLink="false">6402bea42154fe0736d6379e</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Josué Leiva ]]>
                </dc:creator>
                <pubDate>Mon, 17 Apr 2023 02:48:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2023/04/secureApis.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/build-secure-apis-with-flask-and-auth0/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Build Secure APIs with Flask and Auth0</a>
      </p><p>Las APIs son el corazón del desarrollo moderno. Soportan todo tipo de sistemas, desde móviles, web, y aplicaciones de escritorio, hasta dispositivos IoT y autos autónomos. Son un puente entre tus clientes y la lógica y almacenamiento de tu aplicación. </p><p>Este punto central de acceso a la información de tu aplicación plantea la pregunta: ¿Cómo puedes proveer acceso a la información a quienes lo necesitan mientras deniegas el acceso a peticiones no autorizadas?</p><p>La industria ha provisto de diversos protocolos y buenas prácticas para asegurar APIs. Hoy nos enfocaremos en <a href="https://auth0.com/docs/authorization/protocols/protocol-oauth2?utm_source=freecodecamp?utm_source=freecodecamp&amp;utm_medium=sc&amp;utm_campaign=securing_flask">OAuth2</a> <em>(artículo en inglés)</em>, una de las opciones más populares para autorizar clientes en nuestras APIs.</p><p>Pero ¿cómo implementamos OAuth2? hay dos formas de hacerlo:</p><ol><li>La forma DIY (hacerlo tu mismo)</li><li>Trabajar con un tercero seguro como <a href="https://auth0.com/?utm_source=freecodecamp&amp;utm_medium=sc&amp;utm_campaign=securing_flask">Auth0</a></li></ol><p>En este artículo, te guiaré a través de una implementación de OAuth2 para Python y <a href="https://flask.palletsprojects.com/">Flask</a> <em>(documentación en inglés)</em> usando Autho como nuestro proveedor de identidad. Pero primero, debemos discutir la forma DIY.</p><h2 id="-por-qu-no-construir-tu-propia-autenticaci-n-y-autorizaci-n">¿Por qué no construir tu propia autenticación y autorización?</h2><p>Por algunos años ahora, he querido devolver a la comunidad que tanto me ha ayudado al enseñarme programación y ayudarme a progresar en mi búsqueda de conocimiento. Siempre he pensado que una &nbsp;gran forma de contribuir era al tener mi propio blog, algo que he intentado más de algunas veces y fallado. </p><p>Pero ¿dónde fallé? En vez de enfocarme en escribir, intenté construir mi propio motor de blog, ya que está en mi naturaleza. Es lo que los desarrolladores hacen. Les encanta construir.</p><p>Pero ¿por qué menciono esto aquí? Ya que muchos caen en la misma trampa al momento de construir APIs. Permíteme explicarlo con un ejemplo.</p><p>Bob es un gran desarrollador, y él tiene esta gran idea de hacer una app de ToDo <em>(lista de tareas pendientes)</em> que podría ser la siguiente gran app. Bob está en conocimiento de que para una implementación exitosa, los usuarios pueden acceder sólo su propia información.</p><p>Esta es la línea de tiempo de la app de Bob:</p><ul><li>Sprint 0: Investigar ideas y crear prototipos.</li><li>Sprint 1: Construir la tabla de usuarios y vista de inicio de sesión con una API.</li><li>Sprint 2: Añadir vistas de reset de contraseñas y construir las plantillas de email</li><li>Sprint 3: Construir, crear y listar las vistas de ToDos</li><li>Sprint 4: MVP se publica</li><li>Retroalimentación de usuarios:</li><li>Algunos usuarios no pueden ingresar debido a un bug.</li><li>Algunos usuarios se sienten inseguros sin autenticación de 2 factores.</li><li>Algunos usuarios no quieren otra contraseña más. Prefieren ingresar usando Google o Facebook.</li></ul><p>Hablemos sobre lo que ha pasado. Bob pasó los primeros sprints no construyendo su app pero construyendo los bloques básicos, como las funcionalidades de login, notificaciones de email y así. Este valioso tiempo lo podría haber usado de forma distinta, pero lo que sucede a continuación es más preocupante.</p><p>El backlog de Bob se comienza a llenar. Ahora él necesita improvisar un método de autenticación de 2do factor, añadir un ingreso rápido (google) y algunas funciones no relacionadas con el producto que podrían potencialmente retrasar su producto.</p><p>Y aún hay una gran pregunta para ser respondida: ¿Implementó Bob todos los mecanismos de seguridad correctamente? Un error crítico podría exponer toda la información de los usuarios a terceros.</p><p>Lo que Bob hizo es lo que yo hice en mi blog muchas ocasiones. Algunas veces, es de gran ayuda descansar en terceros si queremos hacer las cosas bien.</p><p>Hoy en día, los hackers y ataques se han hecho tan sofisticados que la seguridad ya no es un factor trivial. Es un sistema complicado en sí, y es usualmente mejor dejar esa implementación a expertos - no solo para que se haga bien, sino para que también nos podamos enfocar en lo que importa: construir nuestra aplicación y sus APIs.</p><h2 id="c-mo-configurar-una-cuenta-gratuita-de-administraci-n-de-identidades-en-auth0"><strong>Cómo Configurar una Cuenta Gratuita de Administración de Identidades en Auth0 </strong></h2><p><a href="https://auth0.com/?utm_source=freecodecamp&amp;utm_medium=sc&amp;utm_campaign=securing_flask">Auth0</a> es un proveedor líder en autorización y autenticación, pero veamos cómo puede ayudar a Bob (o a tí) a construir una app mejor.</p><ol><li>Ahorra tiempo</li><li>Es segura</li><li>Tiene un plan gratis</li></ol><p>Es tiempo de ponerse prácticos. Primero, asegúrate de tener una cuenta Auth0. Sino, puedes crear una <a href="https://auth0.com/es/signup?place=header&amp;type=button&amp;text=registrarse">aquí gratuitamente</a>.</p><h3 id="crear-una-nueva-api-auth0"><strong>Crear una nueva API Auth0</strong></h3><p>Aún hay una cosa que debemos hacer antes de comenzar a programar. Ve a la sección de <a href="https://manage.auth0.com/#/apis?utm_source=freecodecamp&amp;utm_medium=sc&amp;utm_campaign=securing_flask">APIs</a> de tu dashboard en Auth0 y haz click en el botón "Crear API" <em>(Create API).</em> Después de ello, &nbsp;llena el formulario con tus detalles. Sin embargo, asegúrate que selecciones &nbsp;<code>RS256</code> como <code>Signing Algorithm</code>.</p><p>Tu formulario debiera verse así:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://lh5.googleusercontent.com/XccGez21ClEDsCECuKwiF_1AF5gj2OXXaJKEXVUOBFmxQ7Ci11a1g1O3cu_io185YbdnSJkAlu3dmP0pt6Ww-N6cPqQLTIeweSi2hNv4ototIkuSZhfiprjqcMrFhcMLaGkKfedkm8D0PR2IcjdLPGUChKS27wsiPMvqCsysQRJyGANVYc5Q5EbFdaFo" class="kg-image" alt="XccGez21ClEDsCECuKwiF_1AF5gj2OXXaJKEXVUOBFmxQ7Ci11a1g1O3cu_io185YbdnSJkAlu3dmP0pt6Ww-N6cPqQLTIeweSi2hNv4ototIkuSZhfiprjqcMrFhcMLaGkKfedkm8D0PR2IcjdLPGUChKS27wsiPMvqCsysQRJyGANVYc5Q5EbFdaFo" width="1298" height="1252" loading="lazy"><figcaption>Creating the API – image showing fields to fill out</figcaption></figure><p>Después de crear un API exitosamente, se abre la página de detalles. Mantén esa pestaña abierta, ya que contiene información que necesitaremos para configurar nuestra aplicación. Si la cierras, no te preocupes, siempre puedes acceder a ella nuevamente.</p><h2 id="c-mo-impulsar-nuestra-aplicaci-n"><strong>Cómo impulsar nuestra aplicación</strong></h2><p>Ya que nos enfocaremos sólo en aspectos de seguridad, tomaremos algunos atajos al construir nuestra demo de API. Sin embargo, cuando desarrolles <a href="https://livecodestream.dev/post/python-flask-api-starter-kit-and-project-layout/">APIs reales</a> <em>(artículo en inglés)</em>, por favor sigue las <a href="https://auth0.com/blog/best-practices-for-flask-api-development/">mejores prácticas para APIs Flask</a> <em>(artículo en inglés).</em> </p><h3 id="instalar-las-dependencias"><strong>Instalar las dependencias</strong></h3><p>Primero, instala las siguientes dependencias para establecer Flask y autenticar usuarios.</p><pre><code class="language-shell">pipenv install flask python-dotenv python-jose flask-cors six</code></pre><h3 id="construir-los-endpoints"><strong>Construir los endpoints</strong></h3><p>Nuestra API será directa. Consistirá sólo de tres endpoints, las cuales todas serán, por ahora, accesibles públicamente. Sin embargo, arreglaremos eso pronto. Acá están nuestros endpoints:</p><ul><li><code>/</code> (endpoint público)</li><li><code>/user</code> (requiere un usuario autenticado)</li><li><code>/admin</code> (sólo los usuarios de rol admin)</li></ul><p>Vamos a ello:</p><pre><code class="language-python">from flask import Flask

app = Flask(__name__)

@app.route("/")
def index_view():
    """
    Endpoint por defecto, es publico y puede ser accedido por cualquiera
    """
    return jsonify(msg="Hello world!")

@app.route("/user")
def user_view():
    """
    Endpoint Usuario, solo puede ser accedido por un usuario autorizado
    """
    return jsonify(msg="Hello user!")

@app.route("/admin")
def admin_view():
    """
    Endpoint Admin, solo puede ser accedido por un admin
    """
    return jsonify(msg="Hello admin!")
</code></pre><p>Bastante simple, no? Vamos a ejecutarlo:</p><pre><code class="language-shell">~ pipenv run flask run
* Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)</code></pre><p>Y si accedemos nuestro endpoint:</p><pre><code class="language-shell">~ curl -i http://localhost:5000
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 23
Server: Werkzeug/2.0.1 Python/3.9.1
Date: Tue, 24 Jan 2023 21:24:57 GMT

{"msg":"Hello world!"}

~ curl -i http://localhost:5000/user
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 22
Server: Werkzeug/2.0.1 Python/3.9.1
Date: Tue, 24 Jan 2023 21:25:42 GMT

{"msg":"Hello user!"}

~ curl -i http://localhost:5000/admin
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 23
Server: Werkzeug/2.0.1 Python/3.9.1
Date: Tue, 24 Jan 2023 21:26:18 GMT

{"msg":"Hello admin!"}</code></pre><h2 id="c-mo-asegurar-los-endpoints"><strong>Cómo asegurar los endpoints</strong></h2><p>Ya que estamos usando OAuth, vamos a autenticar las peticiones al validar un token de acceso en <a href="https://auth0.com/es/learn/json-web-tokens">formato JWT</a>. Lo enviaremos a la API en cada petición como parte de las cabeceras HTTP.</p><h3 id="variables-de-configuraci-n-auth0"><strong>Variables de Configuración Auth0</strong></h3><p>Como mencionado en la sección previa, nuestra API necesita ser consciente y requerirá información de nuestro dashboard Auth0. Así que ve devuelta a tu <a href="https://manage.auth0.com/#/apis?utm_source=freecodecamp&amp;utm_medium=sc&amp;utm_campaign=securing_flask">página detallada de API</a> y agarra dos valores distintos.</p><p><strong>Primero, el identificador de API: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></p><p>Este es el valor requerido cuando la API es creada. También puedes obtenerla desde tu página detallada de API:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://lh5.googleusercontent.com/UffKcasZXNZmXldeB8nhDEjzmOPVao3PR6EUVPbtWzXStuDzcCw2kr5ztEnr0VlWCkBLbhleAM-D11Cv5Cv8fcII8m24D6TfEe4XfxWe8HXN1aNrF-dHeN05zeVeoNfQISWh-VPf0__x8uVfJPL3GGHYIC87utfrr6734Z9Wdk-9eJUApslcdUKOyoSh" class="kg-image" alt="UffKcasZXNZmXldeB8nhDEjzmOPVao3PR6EUVPbtWzXStuDzcCw2kr5ztEnr0VlWCkBLbhleAM-D11Cv5Cv8fcII8m24D6TfEe4XfxWe8HXN1aNrF-dHeN05zeVeoNfQISWh-VPf0__x8uVfJPL3GGHYIC87utfrr6734Z9Wdk-9eJUApslcdUKOyoSh" width="1600" height="699" loading="lazy"><figcaption>How to find the API identifier on the API details page</figcaption></figure><p><strong>Siguiente, dominio Auth0:</strong> </p><p>A menos que estés utilizando un dominio customizado, este valor será <a href="about:blank"><code>[NOMBRE_TENANT].auth0.com</code></a>, y puedes agarrarlo desde la pestaña <code>Test</code> (asegúrate no incluir <code>https://</code> &nbsp;y la última barra diagonal <code>/</code>).</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://lh4.googleusercontent.com/cA63NdLr4AWOz2O3jTWBXTTqc7DrGOr1aPOIpNDRYl97-o84I_lX8KtotCm6hRWF06ai0RjiJzgTjS_zRlySKFAB-XO1w737N05i7-bC2-GZioOpcWuS5gaRoEnDL63gXnm5CyP6JOEQusRLQMF1sY_1vjfXtdMVIr5uCW1PMIpokH76lpMq2VFZSIyf" class="kg-image" alt="cA63NdLr4AWOz2O3jTWBXTTqc7DrGOr1aPOIpNDRYl97-o84I_lX8KtotCm6hRWF06ai0RjiJzgTjS_zRlySKFAB-XO1w737N05i7-bC2-GZioOpcWuS5gaRoEnDL63gXnm5CyP6JOEQusRLQMF1sY_1vjfXtdMVIr5uCW1PMIpokH76lpMq2VFZSIyf" width="1600" height="1037" loading="lazy"><figcaption>Getting the Auth0 domain</figcaption></figure><p>Siguiente, traspasa esos valores a variables para que así puedan ser utilizadas en las funciones de validación.</p><pre><code class="language-python">AUTH0_DOMAIN = 'TU-DOMINIO-AUTH0'
API_IDENTIFIER = 'IDENTIFICADOR-API'
ALGORITHMS = ["RS256"]</code></pre><h3 id="m-todos-de-error"><strong>Métodos de error</strong></h3><p>Durante esta implementación, necesitaremos una manera de lanzar errores cuando la autenticación falla. Así que usaremos los siguientes ayudantes para esos menesteres:</p><pre><code class="language-python">class AuthError(Exception):
    def __init__(self, error, status_code):
        self.error = error
        self.status_code = status_code

@app.errorhandler(AuthError)
def handle_auth_error(ex):
    response = jsonify(ex.error)
    response.status_code = ex.status_code
    return response</code></pre><h3 id="c-mo-capturar-el-token-jwt"><strong>Cómo capturar el token JWT</strong></h3><p>El primer paso para validar un usuario es obtener el token JWT desde las cabeceras HTTP. Esto es bastante simple, pero hay algunas cosas a tener en mente. Aquí hay un ejemplo de ello:</p><pre><code class="language-python">def get_token_auth_header():
    """
    Obtiene el Token de Acceso desde la cabecera Authorization
    """
    auth = request.headers.get("Authorization", None)
    if not auth:
        raise AuthError({"code": "authorization_header_missing",
                        "description":
                            "Se esperaba cabecera de autenticacion"}, 401)

    parts = auth.split()

    if parts[0].lower() != "bearer":
        raise AuthError({"code": "invalid_header",
                        "description":
                            "La cabecera de autenticacion debe comenzar con"
                            " Bearer"}, 401)
    elif len(parts) == 1:
        raise AuthError({"code": "invalid_header",
                        "description": "Token no encontrado"}, 401)
    elif len(parts) &gt; 2:
        raise AuthError({"code": "invalid_header",
                        "description":
                            "La cabecera debe ser"
                            " Bearer token"}, 401)

    token = parts[1]
    return token</code></pre><h3 id="c-mo-validar-el-token"><strong>Cómo validar el token</strong></h3><p>Tener un token traspasado a nuestra API es una buena señal, pero no significa que es un cliente válido. Necesitamos revisar la firma del token.</p><p>Ya que la lógica para requerir autenticación puede ser utilizada para más de un endpoint, sería importante abstraerla y hacerla fácilmente accesible para los desarrolladores su implementación. La mejor forma de hacer esto es usando <a href="https://book.pythontips.com/en/latest/decorators.html">decoradores</a> <em>(artículos en inglés)</em>.</p><pre><code class="language-python">def requires_auth(f):
    """
	Determina si el Token de Acceso es valido
    """
    @wraps(f)
    def decorated(*args, **kwargs):
        token = get_token_auth_header()
        jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json")
        jwks = json.loads(jsonurl.read())
        unverified_header = jwt.get_unverified_header(token)
        rsa_key = {}
        for key in jwks["keys"]:
            if key["kid"] == unverified_header["kid"]:
                rsa_key = {
                    "kty": key["kty"],
                    "kid": key["kid"],
                    "use": key["use"],
                    "n": key["n"],
                    "e": key["e"]
                }
        if rsa_key:
            try:
                payload = jwt.decode(
                    token,
                    rsa_key,
                    algorithms=ALGORITHMS,
                    audience=API_IDENTIFIER,
                    issuer="https://"+AUTH0_DOMAIN+"/"
                )
            except jwt.ExpiredSignatureError:
                raise AuthError({"code": "token_expired",
                                "description": "token is expired"}, 401)
            except jwt.JWTClaimsError:
                raise AuthError({"code": "invalid_claims",
                                "description":
                                    "incorrect claims,"
                                    "please check the audience and issuer"}, 401)
            except Exception:
                raise AuthError({"code": "invalid_header",
                                "description":
                                    "Unable to parse authentication"
                                    " token."}, 401)

            _request_ctx_stack.top.current_user = payload
            return f(*args, **kwargs)
        raise AuthError({"code": "invalid_header",
                        "description": "Unable to find appropriate key"}, 401)
    return decorated</code></pre><p>El nuevo decorador creado <code>requires_auth</code>, cuando aplicado a un endpoint, automáticamente rechazará la petición si no hay un usuario válido que pueda ser autenticado. </p><h3 id="c-mo-requerir-una-petici-n-autenticada-para-un-endpoint"><strong>Cómo requerir una petición autenticada para un endpoint</strong></h3><p>Ya estamos listos para asegurar nuestros endpoints, actualicemos los <code>user</code> y <code>admin</code> para utilizar nuestro decorador.</p><pre><code class="language-python">@app.route("/user")
@requires_auth
def user_view():
    """
    Endpoint Usuario, solo puede ser accedido por un usuario autorizado
    """
    return jsonify(msg="Hello user!")

@app.route("/admin")
@requires_auth
def admin_view():
    """
    Endpoint Admin, solo puede ser accedido por un admin
    """
    return jsonify(msg="Hello admin!")</code></pre><p>Nuestro único cambio fue añadir <code>@required_auth</code> al comienzo de la declaración de cada función de endpoint, y con ello podemos probar una vez más:</p><pre><code class="language-shell">~ curl -i http://localhost:5000/user
HTTP/1.0 401 UNAUTHORIZED
Content-Type: application/json
Content-Length: 89
Server: Werkzeug/2.0.1 Python/3.9.1
Date: Tue, 24 Jan 2023 21:42:26 GMT

{"code":"authorization_header_missing","description":"Se esperaba cabecera de autenticacion"}

~ curl -i http://localhost:5000/admin
HTTP/1.0 401 UNAUTHORIZED
Content-Type: application/json
Content-Length: 89
Server: Werkzeug/2.0.1 Python/3.9.1
Date: Tue, 24 Jan 2023 21:42:42 GMT

{"code":"authorization_header_missing","description":"Se esperaba cabecera de autenticacion"}</code></pre><p>Como se esperaba, no podemos acceder a nuestros endpoints ya que la cabecera de autorización no se encuentra. Pero antes de añadir una, veamos si nuestro endpoint público aún funciona:</p><p>Como esperado</p><pre><code class="language-shell">~ curl -i http://localhost:5000
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 23
Server: Werkzeug/2.0.1 Python/3.9.1
Date: Tue, 24 Jan 2023 21:43:55 GMT

{"msg":"Hello world!"}</code></pre><p>Increíble, funciona como se esperaba.</p><h3 id="c-mo-probarlo"><strong>Cómo probarlo</strong></h3><p>Para probar nuestros nuevos endpoints asegurados, necesitamos obtener un token de acceso válido que podamos pasar a la petición. Podemos hacer eso directamente en la pestaña <code>Test</code> de la página detallada de la API, y es tan simple como copiar un valor desde la pantalla:<br></p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://lh5.googleusercontent.com/XCAWL5taQUs3_5qcAdukl9FP_aTVLya-jyS_4IivFW6JCAfX5d2hbPPCIV4PB8QgcuceQrzC__YYpWMQB1y8HT9AnKO01XH5rCiofvQJAmiAPnGF42FcJFxaVHTLLQcL9UpzFjYgan0Qasna69DlZ8AIkoATbqAtqtqibWUszhvakHZiytPNduTU7_Hb" class="kg-image" alt="XCAWL5taQUs3_5qcAdukl9FP_aTVLya-jyS_4IivFW6JCAfX5d2hbPPCIV4PB8QgcuceQrzC__YYpWMQB1y8HT9AnKO01XH5rCiofvQJAmiAPnGF42FcJFxaVHTLLQcL9UpzFjYgan0Qasna69DlZ8AIkoATbqAtqtqibWUszhvakHZiytPNduTU7_Hb" width="1600" height="1062" loading="lazy"><figcaption>Copying the token for testing</figcaption></figure><p>Once we have the token we can change our curl request accordingly:</p><p>Una vez que tengamos el token podemos cambiar nuestra petición de curl acorde a:</p><pre><code class="language-shell">~ curl -i -H "Authorization: bearer [TOKEN_DE_ACCESO]"  http://localhost:5000/user
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 22
Server: Werkzeug/2.0.1 Python/3.9.1
Date: Tue, 24 Jan 2023 22:17:06 GMT

{"msg":"Hello user!"}</code></pre><p>Por favor recuerda reemplazar <code>TOKEN_DE_ACCESO</code> con el valor que copiaste desde tu dashboard.</p><p>¡Funciona! Pero aún tenemos algo de trabajo que hacer. Incluso cuando nuestro endpoint <code>/admin</code> está asegurado, puede ser accedido por cualquier usuario:</p><pre><code class="language-shell">~ curl -i -H "Authorization: bearer [TOKEN_DE_ACCESO]"  http://localhost:5000/admin
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 23
Server: Werkzeug/2.0.1 Python/3.9.1
Date: Tue, 24 Jan 2023 22:21:09 GMT

{"msg":"Hello admin!"}</code></pre><h3 id="control-de-acceso-basado-en-roles"><strong>Control de acceso basado en roles</strong></h3><p>Para el control de acceso basado en roles hay algunas cosas que debemos hacer:</p><ol><li>Crear permisos para la API</li><li>Activar el añadir permisos a el token JWT para la API</li><li>Actualizar el código</li><li>Probar con usuarios</li></ol><p>Los primeros 2 puntos están bastante bien explicados en los <a href="https://auth0.com/docs/manage-users/access-control/configure-core-rbac">documentos Auth0</a> <em>(artículo en inglés)</em>,<em> </em>así que sólo asegúrate de añadir los permisos correspondientes en tu API. </p><p>Siguiente, necesitamos actualizar el código. Necesitamos una función para revisar si dado permiso existe en el token de acceso y retornar <code>True</code> si lo hace y <code>False</code>si no:</p><pre><code class="language-python">def requires_scope(required_scope):
    """
	Determina si el objetivo requerido esta presente en el Token de Acceso
    Args:
    	required_scope (str): El objetivo requerido para acceder al recurso
    """
    token = get_token_auth_header()
    unverified_claims = jwt.get_unverified_claims(token)
    if unverified_claims.get("scope"):
            token_scopes = unverified_claims["scope"].split()
            for token_scope in token_scopes:
                if token_scope == required_scope:
                    return True
    return False</code></pre><p>Y últimamente, puede ser como a continuación:</p><pre><code class="language-python">@app.route("/admin")
@requires_auth
def admin_view():
    """
	Endpoint Admin, solo puede ser accedido por un admin
    """
    if requires_scope("read:admin"):
        return jsonify(msg="Hello admin!")

    raise AuthError({
        "code": "Unauthorized",
        "description": "No tienes acceso a este recurso"
    }, 403)</code></pre><p>Ahora, sólo usuarios con el permiso <code>read:admin</code> pueden acceder a nuestro endpoint admin.</p><p>Con el fin de probar tu implementación final, puedes seguir los pasos detallados en <a href="https://auth0.com/docs/quickstart/backend/python/02-using#obtaining-an-access-token?utm_source=freecodecamp&amp;utm_medium=sc&amp;utm_campaign=securing_flask">obtener un token de acceso</a> <em>(artículo en inglés)</em> para un usuario determinado.</p><p>También puedes usar el Dashboard de Auth0 para probar los permisos, pero eso va fuera del enfoque de este artículo. Si te gustaría aprender más de ello, lee <a href="https://auth0.com/blog/permission-based-security-aspnet-webapi/#Testing-Permissions">aquí</a>.</p><h2 id="conclusi-n"><strong>Conclusión</strong></h2><p>Hoy hemos aprendido cómo asegurar una API Flask. Hemos explorado la forma hágalo-usted-mismo, y hemos construido una API segura con tres niveles de acceso - acceso público, acceso privado y acceso privado-focalizado.</p><p>Hay muchas cosas más que Auth0 puede hacer por tus APIs y también para las aplicaciones de tus clientes. Hoy sólo hemos raspado la superficie, y depende de tí y tu equipo cuando se trabajan en escenarios de la vida real el explorar el potencial de sus servicios.</p><p>El código completo está disponible en <a href="https://gist.github.com/bajcmartinez/5062aa41ccbe2df1bbf4f1a9b95bd085">Github</a>.</p><p>¡Gracias por leer! Si te gusta mi estilo de enseñanza, puedes <a href="https://livecodestream.dev/newsletter/">Suscribirte a mi newsletter semanal</a> para desarrolladores y constructores y obtener un email semanal lleno de contenido relevante.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Tutorial de Spring Boot: Cómo crear Apps Java modernas y rápidas ]]>
                </title>
                <description>
                    <![CDATA[ En este artículo te voy a guiar en la construcción de un prototipo con Spring Boot. Piénsalo como crear un proyecto para una hackaton o un proyecto para tu startup en poco tiempo. En otras palabras, no estamos intentando crear algo perfecto sino algo que funcione. Si te atascas en ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/tutorial-spring-boot-crear-apps-java-modernas-y-rapidas/</link>
                <guid isPermaLink="false">63ee414022450a0629adcb2e</guid>
                
                    <category>
                        <![CDATA[ spring boot ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Josué Leiva ]]>
                </dc:creator>
                <pubDate>Tue, 14 Mar 2023 20:36:09 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2023/02/pexels-ramdas-ware-102896.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/spring-boot-tutorial-build-fast-modern-java-app/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Spring Boot Tutorial – How to Build Fast and Modern Java Apps</a>
      </p><p>En este artículo te voy a guiar en la construcción de un prototipo con Spring Boot. Piénsalo como crear un proyecto para una hackaton o un proyecto para tu startup en poco tiempo.</p><p>En otras palabras, no estamos intentando crear algo perfecto sino algo que funcione.</p><p>Si te atascas en cualquier parte de este tutorial o si olvidé mencionar algo, puedes revisar el repositorio GitHub que he incluido en la <strong>Conclusión</strong>.</p><h3 id="requisitos-previos">Requisitos previos</h3><ul><li>Fundamentos de JAVA y POO</li><li>Conocimiento básico de bases de datos relacionales (uno a uno, varios a varios, y así)</li><li>Los fundamentos de Spring serían de gran ayuda.</li><li>Nivel básico de HTML.</li></ul><p>También asegúrate de tener lo siguiente:</p><ul><li><a href="https://www.oracle.com/java/technologies/javase-downloads.html">JDK (Java Development Kit)</a> actualizado.</li><li><a href="https://www.jetbrains.com/es-es/idea/">IntelliJ IDEA</a> o algún otro IDE de JAVA.</li></ul><h2 id="-qu-estamos-construyendo"><strong>¿Qué estamos construyendo?</strong></h2><p>Vamos a construir un sistema de reservación de servicios donde los usuarios ingresarán y harán una reservación para utilizar algún servicio como un gimnasio, piscina, o sauna.</p><p>Cada servicio tendrá cierta capacidad (número de personas que pueden utilizar el servicio al mismo tiempo) así las personas podrán hacer uso de las comodidades de forma segura durante la pandemia del Covid-19.</p><h3 id="lista-de-caracter-sticas-para-la-app"><strong>Lista de características para la App</strong></h3><p>Podemos pensar de nuestra app como el sistema de reservaciones para un departamento complejo.</p><ul><li>Los usuarios debieran poder ingresar.</li><li>Asumiremos que las cuentas de los residentes son pre-creadas y no habrá registro de usuarios.</li><li>Los usuarios deben poder ver sus reservaciones.</li><li>Los usuarios debieran poder crear nuevas reservas al seleccionar el tipo de servicio, fecha y hora.</li><li><strong>Sólo los usuarios ingresados</strong> debieran poder ver la página de reservas y crear reservas.</li><li>Debemos revisar la capacidad y sólo crear nuevas reservaciones si el número actual de reservas no excede la capacidad.</li></ul><h3 id="tecnolog-as-que-usaremos"><strong>Tecnologías que usaremos</strong></h3><p>Vamos a aprender sobre muchas tecnologías útiles que te harán más eficiente como desarrollador Spring Boot. Mencionaré brevemente qué son y para qué son buenas y después las veremos en acción.</p><ul><li>Bootify</li><li>Hibernate</li><li>Spring Boot</li><li>Maven</li><li>JPA</li><li>Swagger</li><li>H2 (Base de datos en memoria)</li><li>Thymeleaf</li><li>Bootstrap</li><li>Spring Security</li></ul><h2 id="-por-qu-spring-boot"><strong>¿Por qué Spring Boot?</strong></h2><p>El framework Spring es generalmente utilizado para trabajos a gran escala de nivel empresarial. Usualmente, no es la primera opción para proyectos pequeños, pero debo argumentar que puede ser un tanto rápido para hacer prototipos.</p><p>Tiene las siguientes ventajas:</p><ul><li>La programación basada en anotaciones genera mucho código para ti detrás de escenas. Y especialmente con la disponibilidad de librerías como Lombok, se ha vuelto mucho más fácil enfocarse en la lógica del trabajo.</li><li>Tiene un buen soporte para bases de datos en memoria, por lo que no tenemos que crear una base de datos real y conectarnos a ella. (H2)</li><li>Tiene un ecosistema maduro, así que puedes encontrar fácilmente respuestas a la mayoría de las preguntas.</li><li>Casi "no requiere" configuración. Con la ayuda de Spring Boot, nos deshacemos de las feas configuraciones XML en el lado Spring de las cosas y configurar tu aplicación es realmente fácil.</li><li>Hay muchas cosas pasando detrás de las escenas. Spring provee tanta magia y hace tantas cosas para hacer que las cosas funcionen. Así que usualmente no tienes que preocuparte de eso y simplemente debes dejar que el framework maneje las cosas.</li><li>Tenemos <a href="https://github.com/spring-projects/spring-security">Spring Security</a> (repositorio en inglés). Teniendo uno de los frameworks de seguridad más completos y probados para la batalla a tu lado te otorga mayor confianza en la seguridad de tu aplicación. También se preocupa de gran parte del trabajo duro por ti.</li></ul><h2 id="c-mo-crear-el-proyecto-con-bootify"><strong>Cómo crear el proyecto con Bootify</strong></h2><p>Para crear el proyecto, vas a utilizar <a href="https://bootify.io/"><strong><strong>Bootify</strong></strong></a>. Es un servicio freemium (De gratis y pago dependiendo del plan) que hace el desarrollo en Spring Boot más rápido al generar muchos modelos de código por ti, lo cual te permite enfocarte en el trabajo lógico.</p><p><strong>Bootify</strong> nos permite especificar nuestras preferencias y automáticamente importar las dependencias, similar a <strong><strong>Spring Initializr</strong></strong>.</p><p>Pero hay más que eso. También puedes especificar tus entidades y generará el modelo correspondiente y sus clases DTO. Puede incluso generar el servicio y el código a nivel de control para operaciones comunes de <strong>CRUD</strong>.</p><p>Creo que es una herramienta más conveniente para el desarrollo de APIs que para aplicaciones MVC debido a que genera el código de REST API por defecto. Pero aun así hará nuestras vidas más fáciles, incluso con una aplicación Spring Boot MVC que contiene vistas. Sólo tenemos que hacerle algunos ajustes al código generado.</p><p>Abramos el sitio web de <strong>Bootify</strong> y hagamos clic en el botón "Start Project" en la esquina superior derecha.</p><p>Debes seleccionar:</p><ul><li><strong><strong>Maven</strong></strong> como el tipo de compilación</li><li>Versión de Java: 14</li><li>Habilita "<strong>Enable <strong>Lombok</strong>"</strong> para activar Lombok</li><li>Base de datos: <strong>H2</strong></li><li>Habilita "<strong><strong>add</strong></strong> <strong><strong>dateCreated/lastUpdated</strong></strong> to entities"</li><li>Packages: "Technical"</li><li>Habilita "<strong><strong>OpenAPI/Swagger UI</strong>"</strong></li><li>Añade <strong><strong>org.springframework.boot:spring-boot-devtools</strong></strong> en "further dependencies"</li></ul><p>Después de terminar, deberías ver esto:</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/04/screencapture-bootify-io-app-8U9U2BBTLEAX-2021-04-09-16_06_29-1024x754.png" class="kg-image" alt="screencapture-bootify-io-app-8U9U2BBTLEAX-2021-04-09-16_06_29-1024x754" width="600" height="400" loading="lazy"></figure><p>Ahora vamos a especificar nuestras entidades. Comienza por clicar la pestaña <strong>Entities</strong> en el menú de la izquierda.</p><p>Vamos a tener las siguientes entidades y relaciones:</p><ol><li>"<strong>Reservation</strong>" que contiene la información relacionada con cada reservación, tales como la fecha de reserva, la hora de inicio de reserva, la hora de término de la reserva, y el usuario que tiene esta reserva.</li><li>La entidad "<strong>User</strong>" que contiene nuestro modelo de usuario y que tendrá relaciones con "<strong>Reservation</strong>".</li><li>La entidad "<strong>Amenity</strong>" (Servicio) que contiene el tipo de servicio y su capacidad (el número máximo de reservaciones por cierto tiempo, por ejemplo: 2 personas pueden utilizar y reservar la Sauna al mismo tiempo).</li></ol><p>Vamos a definir nuestra entidad <strong>Reservation</strong> como se muestra a continuación y habilitaremos "Add REST endpoints" (<em>Añadir endpoints REST</em>) (aunque modificaremos la salida). Después haz clic en el botón "Save" (guardar).</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/04/image-1-1024x577.png" class="kg-image" alt="image-1-1024x577" width="600" height="400" loading="lazy"></figure><p>Vamos a especificar las relaciones más tarde, así que el único campo que nuestra entidad "User" (usuario) tiene es id.</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/04/image-1024x445.png" class="kg-image" alt="image-1024x445" width="600" height="400" loading="lazy"></figure><p>Podríamos crear una entidad para "Amenities" (servicios) para guardar la información sobre el nombre del servicio y su capacidad y después podríamos referenciarlo desde <strong>Reservation</strong>. Pero la relación entre <strong>Amenity </strong>y <strong>Reservation</strong> sería uno-a-uno.</p><p>Así que en vez de ello, por la simplicidad, vamos a crear una enum llamado <strong>AmenityType</strong> (tipo de servicio) y guardaremos <strong>AmenityType</strong> dentro de <strong>Reservation</strong>.</p><p>Ahora crearemos una relación entre las entidades <strong>User </strong>(usuario) y <strong>Reservation</strong> (Reserva) al hacer click en el botón + al lado del menú <strong>Relations</strong> (relaciones).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://erinc.io/wp-content/uploads/2021/04/image-2.png" class="kg-image" alt="image-2" width="600" height="400" loading="lazy"><figcaption>Menú para crear relaciones</figcaption></figure><p>Será una relación Varios-a-Uno, ya que un usuario puede tener diversas reservaciones, pero una reserva debe tener uno y sólo un usuario. Nos aseguraremos que así sea el caso al activar la casilla requerida.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://erinc.io/wp-content/uploads/2021/04/image-3-1024x507.png" class="kg-image" alt="image-3-1024x507" width="600" height="400" loading="lazy"><figcaption>User-Reservation Relation</figcaption></figure><p>Hacemos clic en "Save Changes" y terminamos. Tu modelo final debería verse así:</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/04/image-4-1024x481.png" class="kg-image" alt="image-4-1024x481" width="600" height="400" loading="lazy"></figure><p>Ahora haz clic en el enlace de descarga en el menú de la izquierda para descargar el código del proyecto generado para así poder empezar a trabajar en el. Puedes ver el primer commit del repositorio github del proyecto para comparar y ver si tienes algún problema.</p><p>Después de descargar el proyecto, ábrelo en un IDE - Yo usaré <strong><strong>IntelliJ IDEA</strong></strong>. Tu estructura de archivos debiese verse así:</p><pre><code>├── amenity-reservation-system.iml
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
│&nbsp;&nbsp; └── main
│&nbsp;&nbsp;     ├── java
│&nbsp;&nbsp;     │&nbsp;&nbsp; └── com
│&nbsp;&nbsp;     │&nbsp;&nbsp;     └── amenity_reservation_system
│&nbsp;&nbsp;     │&nbsp;&nbsp;         ├── AmenityReservationSystemApplication.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         ├── HomeController.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         ├── config
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── DomainConfig.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── JacksonConfig.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; └── RestExceptionHandler.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         ├── domain
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── Reservation.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; └── User.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         ├── model
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── ErrorResponse.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── FieldError.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── ReservationDTO.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; └── UserDTO.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         ├── repos
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── ReservationRepository.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; └── UserRepository.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         ├── rest
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── ReservationController.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         │&nbsp;&nbsp; └── UserController.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;         └── service
│&nbsp;&nbsp;     │&nbsp;&nbsp;             ├── ReservationService.java
│&nbsp;&nbsp;     │&nbsp;&nbsp;             └── UserService.java
│&nbsp;&nbsp;     └── resources
│&nbsp;&nbsp;         └── application.yml
└── target
    ├── classes
    │&nbsp;&nbsp; ├── application.yml
    │&nbsp;&nbsp; └── com
    │&nbsp;&nbsp;     └── amenity_reservation_system
    │&nbsp;&nbsp;         ├── AmenityReservationSystemApplication.class
    │&nbsp;&nbsp;         ├── HomeController.class
    │&nbsp;&nbsp;         ├── config
    │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── DomainConfig.class
    │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── JacksonConfig.class
    │&nbsp;&nbsp;         │&nbsp;&nbsp; └── RestExceptionHandler.class
    │&nbsp;&nbsp;         ├── domain
    │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── Reservation.class
    │&nbsp;&nbsp;         │&nbsp;&nbsp; └── User.class
    │&nbsp;&nbsp;         ├── model
    │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── ErrorResponse.class
    │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── FieldError.class
    │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── ReservationDTO.class
    │&nbsp;&nbsp;         │&nbsp;&nbsp; └── UserDTO.class
    │&nbsp;&nbsp;         ├── repos
    │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── ReservationRepository.class
    │&nbsp;&nbsp;         │&nbsp;&nbsp; └── UserRepository.class
    │&nbsp;&nbsp;         ├── rest
    │&nbsp;&nbsp;         │&nbsp;&nbsp; ├── ReservationController.class
    │&nbsp;&nbsp;         │&nbsp;&nbsp; └── UserController.class
    │&nbsp;&nbsp;         └── service
    │&nbsp;&nbsp;             ├── ReservationService.class
    │&nbsp;&nbsp;             └── UserService.class
    └── generated-sources
        └── annotations</code></pre><h2 id="c-mo-probar-y-explorar-el-c-digo-generado"><strong>Cómo probar y explorar el código generado</strong></h2><p>Tomaremos nuestro tiempo para experimentar el código generado y entenderlo capa por capa.</p><p>La carpeta <strong>Repos</strong> contiene el código de la capa de acceso de datos, es decir nuestros repositorios. Vamos a usar métodos <strong>JPA</strong> para obtener nuestros datos, los cuales son métodos prefabricados que puedes utilizar al definirlos dentro de la interfaz del repositorio.</p><p>Nota que las clases de nuestro repositorio heredan la interfaz <strong>JpaRepository</strong>. Esta es la interfaz que nos permitirá utilizar los métodos mencionados.</p><p>Las peticiones JPA siguen cierta convención, y cuando creamos el método que obedece las convenciones, este sabrá automáticamente qué datos quieres recuperar, detrás de las escenas. Si aún no comprendes, no te preocupes, veremos ejemplos.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://erinc.io/wp-content/uploads/2021/04/image-5-1024x719.png" class="kg-image" alt="image-5-1024x719" width="600" height="400" loading="lazy"><figcaption>Ejemplos de palabras clave, oraciones y sus peticiones (snippets) JPQL correspondientes.</figcaption></figure><p>Las clases <strong>Model</strong> presentan nuestro modelo de datos, y qué clases tendrán qué campos. </p><p>Cada clase <em>model(modelo)</em> corresponde a una tabla de base de datos con el mismo nombre y los campos en la clase modelo serán las columnas en la tabla correspondiente.</p><p>Vea la anotación @<strong>Entity</strong> al comienzo de nuestras clases modelo. Esta anotación es manejada por <a href="https://hibernate.org/"><strong><strong>Hibernate</strong></strong></a> <em>(artículo en inglés)</em> y cuando Hibernate ve <strong>@Entity</strong>, creará una tabla usando el nombre de nuestra clase como el nombre de la tabla.</p><p>Si te preguntas, "¿Qué es Hibernate de todos modos?", es una herramienta de &nbsp;mapeo de objetos relacionales (ORM por sus siglas en inglés) en Java que nos permite crear mapas de <strong>POJOs</strong> (Objeto Java Plano antiguo) hacia las tablas de la base de datos. También provee cualidades tales como restricción de validación de datos, pero no profundizaremos en Hibernate en este post debido a que es un tema vasto de por sí.</p><p>Una cualidad increíble de Hibernate es que maneja toda la creación de tablas y las operaciones de borrado para que así no debas usar scripts <strong>SQL</strong> adicionales.</p><p>También representamos las relaciones entre objetos en las clases modelo. Para ver un ejemplo, demos un vistazo en nuestra clase <strong>User</strong>:</p><pre><code class="language-java">    @OneToMany(mappedBy = "user")
    private Set&lt;Reservation&gt; userReservations;</code></pre><p>Esta tiene un objeto <strong><strong>userReservations</strong></strong> que contiene un set de referencias que se asemeja a las reservaciones de este usuario en particular. En la clase <strong>Reservation</strong> tenemos la relación inversa:</p><pre><code class="language-java">@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;</code></pre><p>Teniendo referencias en los dos lados hace posible acceder el otro lado de la relación (objeto usuario hacia reservación y viceversa).</p><p><strong>Controllers</strong> &nbsp;va a manejar las peticiones que son pasadas a este controlador por el handler de peticiones y retornará las vistas correspondientes en este caso.</p><p>Los controladores que fueron generados por Bootify están configurados para devolver respuestas JSON, y las vamos a modificar en la siguiente sección para retornar nuestras vistas.</p><p><strong>Services</strong> tendrá la lógica de nuestra aplicación. La mejor práctica es mantener los controladores simples, esto al mantener la lógica del programa en un lugar separado, las clases de servicio.</p><p>Los controladores no debieran interactuar con los repositorios directamente, sino llamar el servicio que interactúa con el repositorio, realizar cualquier operación adicional y devolver el resultado al controlador.</p><h3 id="-probemos-la-api-"><strong>¡Probemos la API!</strong></h3><p>Ahora vamos a la parte divertida, intentar ver la API en acción. Ejecuta la aplicación Spring en tu IDE favorito. Abre tu navegador y ve a esta dirección:</p><pre><code>http://localhost:8080/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/</code></pre><p>Swagger automáticamente documenta nuestro código y nos permite enviar peticiones fácilmente. Deberías estar viendo esto:</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/04/screencapture-localhost-8080-swagger-ui-index-html-2021-04-17-21_27_48-1024x914.png" class="kg-image" alt="screencapture-localhost-8080-swagger-ui-index-html-2021-04-17-21_27_48-1024x914" width="600" height="400" loading="lazy"></figure><p>Vamos a primero crear un usuario al mandar una petición <strong>POST</strong> a <strong>UserController</strong>. Vamos a hacer eso al hacer click en la última caja (la verde) bajo la lista user-controller (controlador de usuario).</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/04/Screen-Shot-2021-04-17-at-21.30.41-1024x565.png" class="kg-image" alt="Screen-Shot-2021-04-17-at-21.30.41-1024x565" width="600" height="400" loading="lazy"></figure><p><strong>Swagger</strong> nos muestra los parámetros que este endpoint espera - por ahora solo el id - &nbsp;y también las respuestas que la API retorna.</p><p>Haz clic en el botón "Try it out" (pruébalo) en la esquina superior derecha. Te pide que ingreses un id. Sé que no tiene sentido y que el código siquiera va a utilizar el id que ingreses, pero vamos a arreglar eso en la sección siguiente (solo es un problema con el código generado)</p><p>Por el bien de la experimentación, ingresa cualquier número, como 1 para el id, y haz clic en el botón execute (ejecutar).</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/04/screencapture-localhost-8080-swagger-ui-index-html-2021-04-17-21_39_32-547x1024.png" class="kg-image" alt="screencapture-localhost-8080-swagger-ui-index-html-2021-04-17-21_39_32-547x1024" width="600" height="400" loading="lazy"></figure><p>El cuerpo de la respuesta contiene el id del objeto creado. Podemos confirmar que es creado en la base de datos al crear la consola H2.</p><p>Pero antes de hacer eso, tenemos que hacer un pequeño ajuste al archivo <strong>application.yml</strong> que contiene la configuración y ajustes de la app.</p><p>Abre tu archivo <strong>application.yml</strong> y pega el siguiente código:</p><pre><code>spring:
  datasource:
    url: ${JDBC_DATABASE_URL:jdbc:h2:mem:amenity-reservation-system}
    username: ${JDBC_DATABASE_USERNAME:sa}
    password: ${JDBC_DATABASE_PASSWORD:}
  dbcp2:
    max-wait-millis: 30000
    validation-query: "SELECT 1"
    validation-query-timeout: 30
  jpa:
    hibernate:
      ddl-auto: update
    open-in-view: false
    properties:
      hibernate:
        jdbc:
          lob:
            non_contextual_creation: true
        id:
          new_generator_mappings: true
springdoc:
  pathsToMatch: /api/**</code></pre><p>Luego deberíamos poder acceder la consola H2 al ir a esta dirección: </p><pre><code>http://localhost:8080/h2-console/</code></pre><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/04/image-6-1024x724.png" class="kg-image" alt="image-6-1024x724" width="600" height="400" loading="lazy"></figure><p>Aquí tienes que revisar que el username (nombre de usuario) es "sa" y hacer clic en el botón Connect (conectar)</p><p>Haz clic en la tabla USER en el menú de la izquierda y la consola escribirá la petición select all por ti.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://erinc.io/wp-content/uploads/2021/04/image-7-1024x573.png" class="kg-image" alt="image-7-1024x573" width="600" height="400" loading="lazy"><figcaption>Panel de administración de la base de datos H2</figcaption></figure><p>Vamos a hacer click en el botón <strong>Run</strong>(ejecutar) que está arriba de la consulta.</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/04/image-8-1024x466.png" class="kg-image" alt="image-8-1024x466" width="600" height="400" loading="lazy"></figure><p>Y podemos ver que el objeto <strong>User</strong> es en efecto creado - excelente!</p><p>Ya tenemos una API funcional en este punto y no hemos escrito ni una línea de código.</p><h3 id="c-mo-ajustar-el-c-digo-para-nuestro-caso-particular"><strong>Cómo Ajustar el Código para Nuestro Caso Particular</strong></h3><p>Como mencioné antes, el código generado no se adapta totalmente al uso que necesitamos y tenemos que hacerle algunos ajustes.</p><p>Vamos a remover la carpeta "model" que contiene los DTOs y cosas que no vamos a usar. En cambio vamos a mostrar la información dentro de las vistas.</p><figure class="kg-card kg-code-card"><pre><code>cd src/main/java/com/amenity_reservation_system/ 
rm -rf model</code></pre><figcaption>Eliminamos la carpeta model dentro del proyecto</figcaption></figure><p>Por ahora tendremos muchos errores ya que el código utiliza clases DTO, pero nos desharemos de la mayoría de ellos después de remover las clases controladoras actuales.</p><p>Eliminaremos las clases controladoras (controllers) porque ya no queremos exponer la funcionalidad de modificar nuestra información. Nuestros usuarios debieran ser capaces de hacer eso al interactuar con nuestra UI, y para ello vamos a crear nuevos controladores para retornar los componentes de las vistas en la siguiente sección.</p><figure class="kg-card kg-code-card"><pre><code>rm -rf rest</code></pre><figcaption>Eliminamos las clases controladoras</figcaption></figure><p></p><p>Finalmente, necesitamos refactorizar un poco nuestras clases service ya que las clases DTO ya no están presentes:</p><figure class="kg-card kg-code-card"><pre><code class="language-java">package com.amenity_reservation_system.service;

import com.amenity_reservation_system.domain.User;
import com.amenity_reservation_system.repos.UserRepository;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;


@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(final UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public List&lt;User&gt; findAll() {
        return userRepository.findAll();
    }

    public User get(final Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -&gt; new ResponseStatusException(HttpStatus.NOT_FOUND));
    }

    public Long create(final User user) {
        return userRepository.save(user).getId();
    }

    public void update(final Long id, final User user) {
        final User existingUser = userRepository.findById(id)
                .orElseThrow(() -&gt; new ResponseStatusException(HttpStatus.NOT_FOUND));
        
        userRepository.save(user);
    }

    public void delete(final Long id) {
        userRepository.deleteById(id);
    }
}</code></pre><figcaption>Clases service refactorizadas</figcaption></figure><p>Básicamente eliminamos el código relacionado con las clases DTO de la clase <strong>UserService</strong> y reemplazamos los typos de retorno con <strong>User</strong>. Hagamos lo mismo para <strong>ReservationService</strong>.</p><pre><code class="language-java">package com.amenity_reservation_system.service;

import com.amenity_reservation_system.domain.Reservation;
import com.amenity_reservation_system.domain.User;
import com.amenity_reservation_system.repos.ReservationRepository;
import com.amenity_reservation_system.repos.UserRepository;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;


@Service
public class ReservationService {

    private final ReservationRepository reservationRepository;
    private final UserRepository userRepository;

    public ReservationService(final ReservationRepository reservationRepository,
            final UserRepository userRepository) {
        this.reservationRepository = reservationRepository;
        this.userRepository = userRepository;
    }

    public List&lt;Reservation&gt; findAll() {
        return reservationRepository.findAll();
    }

    public Reservation get(final Long id) {
        return reservationRepository.findById(id)
                .orElseThrow(() -&gt; new ResponseStatusException(HttpStatus.NOT_FOUND));
    }

    public Long create(final Reservation reservation) {
        return reservationRepository.save(reservation).getId();
    }

    public void update(final Long id, final Reservation reservation) {
        final Reservation existingReservation = reservationRepository.findById(id)
                .orElseThrow(() -&gt; new ResponseStatusException(HttpStatus.NOT_FOUND));
        reservationRepository.save(reservation);
    }

    public void delete(final Long id) {
        reservationRepository.deleteById(id);
    }

}</code></pre><p>Vamos también a eliminar las clases config:</p><pre><code>rm -rf config</code></pre><p>Y renombramos la carpeta "domain" a "model". Si estás usando un IDE, te recomiendo fuertemente que utilices la función de renombre de tu IDE para cambiar el nombre de esta carpeta, ya que renombrará automáticamente los imports para que sean iguales al nuevo nombre del paquete.</p><pre><code>mv domain model</code></pre><p>También, revisa que tus clases modelo (<strong>User</strong> y <strong>Reservation</strong>) tienen los nombres de paquete correctos después de esta operación. La primera línea de estos dos archivos debiera ser:</p><pre><code>package com.amenity_reservation_system.model;</code></pre><p>Si se mantiene como paquete domain, puedes tener errores.</p><p>En este punto, deberías ser capaz de compilar y correr el proyecto sin ningún problema. </p><h2 id="como-crear-los-archivos-de-controladores-y-vistas-para-mostrar-informaci-n"><strong>Como crear los Archivos de Controladores y Vistas para Mostrar Información</strong></h2><p><strong>Thymeleaf</strong> es un motor de plantillas para Spring que nos permite crear UIs y mostrar nuestros modelos de información a los usuarios.</p><p>Podemos acceder a los objetos Java dentro de la plantilla Thymeleaf, y también podemos usar HTML, CSS y JavaScript. Si sabes sobre JSPs, esto es JSP con esteroides.</p><p>Vamos a crear algunas plantillas Thymeleaf que no harán nada sino que sólo mostrarán la información por ahora. Les daremos estilo en la siguiente sección. También crearemos los controladores que nos retornarán esas vistas.</p><p>Antes de comenzar con las plantillas Thymeleaf, necesitamos añadir una dependencia Maven para Spring Boot Thymeleaf, Tus dependencias deberían verse así en tu archivo <strong>pom.xml</strong>:</p><pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
    &lt;parent&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
        &lt;version&gt;2.4.4&lt;/version&gt;
        &lt;relativePath /&gt;&lt;!-- lookup parent from repository --&gt;
    &lt;/parent&gt;
    &lt;groupId&gt;com&lt;/groupId&gt;
    &lt;artifactId&gt;amenity-reservation-system&lt;/artifactId&gt;
    &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
    &lt;name&gt;amenity-reservation-system&lt;/name&gt;

    &lt;properties&gt;
        &lt;java.version&gt;14&lt;/java.version&gt;
    &lt;/properties&gt;

    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-validation&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.h2database&lt;/groupId&gt;
            &lt;artifactId&gt;h2&lt;/artifactId&gt;
            &lt;scope&gt;runtime&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springdoc&lt;/groupId&gt;
            &lt;artifactId&gt;springdoc-openapi-ui&lt;/artifactId&gt;
            &lt;version&gt;1.5.2&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
            &lt;version&gt;1.18.20&lt;/version&gt;
            &lt;scope&gt;provided&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
            &lt;scope&gt;test&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt;
        &lt;/dependency&gt;

    &lt;/dependencies&gt;

    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;
&lt;/project&gt;
</code></pre><p>Si quieres puedes simplemente copiar y pegar el contenido de la etiqueta "dependencies". Ahora le diremos a Maven que instale las dependencias.</p><figure class="kg-card kg-code-card"><pre><code>mvn clean install</code></pre><figcaption>Realizamos la instalación limpia de todas las dependencias</figcaption></figure><p>Ahora estamos listos para crear nuestras vistas. Vamos a crear una carpeta bajo resources para mantener nuestras plantillas, así:</p><pre><code>cd ../../../resources
mkdir templates</code></pre><p>Y crearemos un archivo de vista:</p><pre><code>cd templates
touch index.html</code></pre><p>Copia y pega el siguiente fragmento de código en el. Este archivo será nuestra página de inicio en el futuro.</p><pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html lang="en" xmlns:th="http://www.thymeleaf.org"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"/&gt;
    &lt;title&gt;Amenities Reservation App&lt;/title&gt;

    &lt;link th:rel="stylesheet" th:href="@{/webjars/bootstrap/4.0.0-2/css/bootstrap.min.css} "/&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;div&gt;
hello world!
&lt;/div&gt;

&lt;script th:src="@{/webjars/jquery/3.0.0/jquery.min.js}"&gt;&lt;/script&gt;
&lt;script th:src="@{/webjars/popper.js/1.12.9-1/umd/popper.min.js}"&gt;&lt;/script&gt;
&lt;script th:src="@{/webjars/bootstrap/4.0.0-2/js/bootstrap.min.js}"&gt;&lt;/script&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre><p>También vamos a necesitar crear un controlador que nos devuelva esta vista para así poder verla en el navegador.</p><pre><code>cd ../java/com/amenity_reservation_system
mkdir controller &amp;&amp; cd controller
touch HomeController</code></pre><p>Pega este código en HomeController:</p><pre><code>package com.amenity_reservation_system.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;


@Controller
public class HomeController {

    @GetMapping("/")
    public String index(Model model) {

        return "index";
    }
}
</code></pre><p>Nota cómo anotamos nuestro método con <strong>@Controller</strong> en vez de <strong>@RestController</strong> esta vez. La anotación @RestController implica que el controlador retornará una respuesta REST mientras que <strong>@Controller</strong> puede retornar vistas HTML pre-renderizadas (Renderizado del lado del Servidor o SSR en inglés)</p><p>Cuando una petición es recibida en nuestra aplicación, Spring automáticamente ejecutará este método controlador. Después encontrará el archivo <strong>index.html</strong> que creamos previamente bajo la carpeta "resources" y enviará ese archivo al cliente.</p><p>Confirmemos que está funcionando al enviar una petición a nuestra aplicación. No te olvides de reiniciarla, después envía esta petición:</p><pre><code>GET localhost:8080</code></pre><p>Deberías poder ver el mensaje "Hello World" en el navegador.</p><h2 id="c-mo-definir-distintos-tipos-de-servicios"><strong>Cómo Definir Distintos Tipos de Servicios</strong></h2><p>Tenemos la clase <strong>Reservation</strong> pero no hemos creado alguna forma de especificar qué tipo de servicio está siendo reservado (piscina, sauna, gimnasio, etc)</p><p>Hay múltiples formas de hacer esto. Una de ellas sería crear una entidad llamada Amenity (Servicio) para guardar la información compartida entre entidades. Después crearemos las clases <strong>PoolAmenity</strong>, <strong>SaunaAmenity</strong>, y <strong>GymAmenity </strong>las cuales se extenderían de la clase Amenity.</p><p>Esta es una solución extensible pero se siente un tanto exagerada para nuestra simple aplicación, ya que no tenemos mucha información por cada tipo de servicio y solo vamos a especificar la capacidad de cada servicio.</p><p>Para mantener las cosas simples y no molestarnos con tablas de herencia y otras cosas complicadas, solo crearemos una clase enum para indicar el tipo de servicio como un String y dejar que cada reservación tenga una de ellas.</p><p>Iremos a la carpeta de modelos desde el controlador de archivos y crearemos la clase enum para <strong>AmenityType</strong> (Tipo de servicio):</p><pre><code>cd ../model
touch AmenityType.java</code></pre><pre><code>public enum AmenityType {
    POOL("POOL"), SAUNA("SAUNA"), GYM("GYM");

    private final String name;

    private AmenityType(String value) {
        name = value;
    }

    @Override
    public String toString() {
        return name;
    }
}</code></pre><p>En esta enum, definiremos una variable name (nombre) para mantener el nombre de la enum y crear un constructor privado para solo permitir conjuntos limitados de tipos. Nota que las declaraciones del tipo de servicio llaman al constructor desde dentro de la clase con sus valores de nombre.</p><p>Ahora necesitamos modificar la clase "Reservation" para que contenga una referencia a <strong>AmenityType</strong>:</p><pre><code class="language-java">@Enumerated(EnumType.STRING)
@Column(nullable = false)
private AmenityType amenityType;</code></pre><p>Usaremos la anotación <strong>@Enumerated</strong> para describir cómo queremos guardar la enum en nuestra base de datos. También vamos a hacerla no nula, ya que cada <strong>Reservation</strong> (reservación) debe tener un <strong>AmenityType</strong> (tipo de servicio).</p><h2 id="como-mostrar-las-reservaciones-de-un-usuario"><strong>Como Mostrar las Reservaciones de un Usuario</strong></h2><p>¿Cuál es la característica más crucial de nuestra app? Crear y mostrar las reservaciones de un usuario.</p><p>Aún no tenemos una forma de autenticar a los usuarios, por lo que no podemos realmente pedirle al usuario que inicie sesión y mostrarle sus reservaciones. Pero aun así queremos implementarlo y probar la funcionalidad de hacer y mostrar las reservas.</p><p>Con ese propósito, podemos pedirle a Spring que ponga alguna información inicial en nuestra base de datos cada vez que nuestra aplicación se ejecute. Después podemos pedir esa información para ver si nuestras peticiones realmente funcionan. Y en ese momento, podemos proceder a llamar estos servicios desde <strong>Views</strong> y añadir autenticación en nuestra aplicación para las siguientes secciones.</p><p>Vamos a utilizar un bean <strong>CommandLineRunner</strong> para ejecutar el código inicial. Cada vez que el Container de Spring encuentre un bean de tipo CommandLineRunner ejecutará el código dentro de sí. Antes de ese paso, añadiremos unos cuantos métodos a nuestras clases modelo para hacer la creación de objetos más fácil y menos verbosa.</p><p>Dale un vistazo a las anotaciones de las clases modelo y deberías ver anotaciones como <strong>@Getter</strong> y <strong>@Setter</strong>. Esas son anotaciones <strong>Lombok</strong>.</p><p>Lombok es un procesador de anotaciones que podemos utilizar para hacer nuestra experiencia de código mejor al permitir que genere el código por nosotros. Cuando anotamos una clase con <strong>@Getter</strong> y <strong>@Setter</strong>, Lombok genera los setters y getters para cada campo de esta clase.</p><p>Spring usa métodos setter y getter para muchas de las operaciones triviales detrás de las escenas, así que son casi siempre requeridos. Además de que crearlos para cada entidad, se vuelve un problema sin la ayuda de Lombok.</p><p>Sin embargo Lombok puede hacer más que ello. También añadiremos las siguientes anotaciones a nuestras clases <strong>Reservation</strong> y <strong>User</strong>:</p><pre><code>@Builder
@NoArgsConstructor
@AllArgsConstructor</code></pre><p>Con estas anotaciones, Lombok implementa el patrón creacional de construcción para esta clase y también crea 2 constructores: Uno sin argumentos (constructor por defecto) y otro con todos los argumentos. Creo que es increíble que podamos hacer tanto con solo unas pocas anotaciones.</p><p>Ahora estamos listos para añadir información inicial. Ve a tu clase principal (<strong><strong>AmenityReservationSystemApplication.java</strong></strong>) y añade este método:</p><pre><code class="language-java">package com.amenity_reservation_system;

import com.amenity_reservation_system.model.AmenityType;
import com.amenity_reservation_system.model.Reservation;
import com.amenity_reservation_system.model.User;
import com.amenity_reservation_system.repos.ReservationRepository;
import com.amenity_reservation_system.repos.UserRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Date;


@SpringBootApplication
public class AmenityReservationSystemApplication {

    public static void main(String[] args) {
        SpringApplication.run(AmenityReservationSystemApplication.class, args);
    }

    @Bean
    public CommandLineRunner loadData(UserRepository userRepository,
                                      ReservationRepository reservationRepository) {
        return (args) -&gt; {
            User user = userRepository.save(new User());
            DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
            Date date = new Date();
            LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            Reservation reservation = Reservation.builder()
                    .reservationDate(localDate)
                    .startTime(LocalTime.of(12, 00))
                    .endTime(LocalTime.of(13, 00))
                    .user(user)
                    .amenityType(AmenityType.POOL)
                    .build();

            reservationRepository.save(reservation);
        };
    }
}
</code></pre><p>Si tienes un error sobre las operaciones de guardado, algo como "Inferred type 'S' for parameter ... does not match" <em>(El tipo inferido 'S' para el parámetro ... no coincide)</em>, es porque renombramos el dominio del directorio a model. Ve a las clases del repositorio y arregla las rutas de importación a <strong>model.User</strong> y <strong>model.Reservation</strong>.</p><p>Nota cómo hemos usado el <strong>builder pattern</strong> (patrón de constructor) para crear los objetos de reservación más fácilmente. Cuando la creación de objetos se torna compleja y el constructor requiere muchos parámetros, es fácil olvidar el orden de los parámetros o revolver el orden.</p><p>Sin el patrón de constructor, necesitamos o llamar a un constructor con muchos parámetros o llamar al constructor por defecto y escribir código #properties para llamar a los setters.</p><p>Después de que estés listo, corre tu aplicación nuevamente para insertar la información inicial y conéctate a la <strong>consola H2</strong> como aprendimos antes para así confirmar que nuestra información está efectivamente insertada. Si no tienes ningún error, deberías ser capaz de ver que el usuario y la reservación son insertadas exitosamente.</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/04/image-9-1024x325.png" class="kg-image" alt="image-9-1024x325" width="600" height="400" loading="lazy"></figure><p>Hemos insertado una reservación para ser capaces de probar la funcionalidad de listado de las reservaciones, pero nuestras vistas actualmente no tienen una forma de mostrar las reservaciones y añadir las reservaciones. Necesitamos crear una UI para ello.</p><p>No tenemos un mecanismo de autenticación o registro aún, así que actuaremos como si el usuario con ID 10001 estuviese con la sesión iniciada. Más tarde mejoraremos ello al revisar dinámicamente quién está conectado y mostraremos una página distinta si el usuario no está conectado (si no ha hecho inicio de sesión).</p><h3 id="como-crear-vistas-con-thymeleaf"><strong>Como Crear Vistas con Thymeleaf</strong></h3><p>Comenzaremos por crear una página home simple y una barra de navegación para nosotros mismos. Utilizaremos fragmentos de Thymeleaf para el código de la "navbar".</p><p>Los fragmentos de Thymeleaf nos permiten crear componentes reusables de estructura similar a los componentes de React/Vue si es que estás familiarizado. Vamos a crear una carpeta para nuestros fragmentos bajo "templates" y la llamaremos "fragments".</p><pre><code>mkdir fragments
touch nav.html</code></pre><p>Pondremos nuestra navbar dentro del archivo <strong>nav.html</strong>. Copia y pega el siguiente código:</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en" xmlns:th="http://www.thymeleaf.org"&gt;
&lt;body&gt;
&lt;nav th:fragment="nav" class="navbar navbar-expand navbar-dark bg-primary"&gt;
    &lt;div class="navbar-nav w-100"&gt;
        &lt;a class="navbar-brand text-color" href="/"&gt;Amenities Reservation System&lt;/a&gt;
    &lt;/div&gt;
&lt;/nav&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><figcaption>Fragmento Thymeleaf de la barra de navegación</figcaption></figure><p>En su estado actual no hace mucho, pero en el futuro podríamos añadir un botón de inicio de sesión o algunos enlaces.</p><p>Ahora crearemos una página de inicio simple que servirá a los usuarios que no están conectados. Tendremos nuestro fragmento de navbar arriba y tendremos un botón de login que le pedirá al usuario que inicie sesión antes de utilizar la aplicación.</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html lang="en" xmlns:th="http://www.thymeleaf.org"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"/&gt;
    &lt;title&gt;Amenities Reservation App&lt;/title&gt;

    &lt;link th:rel="stylesheet" th:href="@{/webjars/bootstrap/4.0.0-2/css/bootstrap.min.css} "/&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;div&gt;
    &lt;div th:insert="fragments/nav :: nav"&gt;&lt;/div&gt;
    &lt;div class="text-light" style="background-image: url('https://source.unsplash.com/1920x1080/?nature');
                                   position: absolute;
                                   left: 0;
                                   top: 0;
                                   opacity: 0.6;
                                   z-index: -1;
                                   min-height: 100vh;
                                   min-width: 100vw;"&gt;
    &lt;/div&gt;

    &lt;div class="container" style="padding-top: 20vh; display: flex; flex-direction: column; align-items: center;"&gt;
        &lt;h1 class="display-3"&gt;Reservation management made easy.&lt;/h1&gt;
        &lt;p class="lead"&gt;Lorem, ipsum dolor sit amet consectetur adipisicing elit.
            Numquam in quia natus magnam ducimus quas molestias velit vero maiores.
            Eaque sunt laudantium voluptas. Fugiat molestiae ipsa delectus iusto vel quod.&lt;/p&gt;
        &lt;a href="/reservations" class="btn btn-success btn-lg my-2"&gt;Reserve an Amenity&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;script th:src="@{/webjars/jquery/3.0.0/jquery.min.js}"&gt;&lt;/script&gt;
&lt;script th:src="@{/webjars/popper.js/1.12.9-1/umd/popper.min.js}"&gt;&lt;/script&gt;
&lt;script th:src="@{/webjars/bootstrap/4.0.0-2/js/bootstrap.min.js}"&gt;&lt;/script&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre><figcaption>nav.html</figcaption></figure><p>Debería verse así:</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/05/image-1024x533.png" class="kg-image" alt="image-1024x533" width="600" height="400" loading="lazy"></figure><p>Crearemos otra página para mostrar si el usuario ya inició sesión. Para mantenerlo simple también la trataremos como página principal, y si el usuario ya está conectado, serán capaces de ver sus reservas en la página.</p><p>También es bueno en términos de practicidad para el usuario debido a que disminuye los pasos que deben tomar para ver sus reservaciones.</p><p>Ahora crearemos esta página como otro endpoint. Pero antes de añadir el login a nuestra aplicación mostraremos esta página previa si el usuario no está conectado y la página siguiente si están conectados, dinámicamente.</p><p>Antes de comenzar a trabajar en nuestra nueva página, añadiremos otro mapeo a <strong>HomeController</strong> que nos retornará nuestra nueva página. Más tarde fusionaremos estos dos controladores:</p><figure class="kg-card kg-code-card"><pre><code class="language-java">package com.amenity_reservation_system;

import com.amenity_reservation_system.domain.User;
import com.amenity_reservation_system.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;


@Controller
public class HomeController {

    final UserService userService;

    public HomeController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/")
    public String index(Model model) {
        return "index";
    }

    @GetMapping("/reservations")
    public String reservations(Model model) {
        User user = userService.get(10000L);
        model.addAttribute("user", user);

        return "reservations";
    }
}</code></pre><figcaption>HomeController.java</figcaption></figure><p>Si una petición es recibida en "/reservations", este código llamará a nuestro "userService" y preguntará por el usuario con id 10000L. Después añadirá este usuario a <strong>Model</strong>.</p><p>View accederá a este modelo y presentará la información sobre las reservaciones de este usuario. También hemos "auto-conectado" el servicio de usuario para que lo utilice.</p><p>Navega a la carpeta "templates" si es que ya no estás allí y crea otro archivo llamado "reservations.html":</p><pre><code>touch reservations.html</code></pre><p>Copia y pega el siguiente código:</p><pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html lang="en" xmlns:th="http://www.thymeleaf.org"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"/&gt;
    &lt;title&gt;Reservations&lt;/title&gt;

    &lt;link th:rel="stylesheet" th:href="@{/webjars/bootstrap/4.0.0-2/css/bootstrap.min.css} "/&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;div&gt;
    &lt;div th:insert="fragments/nav :: nav"&gt;&lt;/div&gt;
    &lt;div class="container" style="padding-top: 10vh; display: flex; flex-direction: column; align-items: center;"&gt;
        &lt;h3&gt;Welcome &lt;span th:text=" ${user.getFullName()}"&gt;&lt;/span&gt;&lt;/h3&gt;
        &lt;br&gt;
        &lt;table class="table"&gt;
            &lt;thead&gt;
                &lt;tr&gt;
                    &lt;th scope="col"&gt;Amenity&lt;/th&gt;
                    &lt;th scope="col"&gt;Date&lt;/th&gt;
                    &lt;th scope="col"&gt;Start Time&lt;/th&gt;
                    &lt;th scope="col"&gt;End Time&lt;/th&gt;
                &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
                &lt;tr th:each="reservation : ${user.getReservations()}"&gt;
                    &lt;td th:text="${reservation.getAmenityType()}"&gt;&lt;/td&gt;
                    &lt;td th:text="${reservation.getReservationDate()}"&gt;&lt;/td&gt;
                    &lt;td th:text="${reservation.getStartTime()}"&gt;&lt;/td&gt;
                    &lt;td th:text="${reservation.getEndTime()}"&gt;&lt;/td&gt;
                &lt;/tr&gt;
            &lt;/tbody&gt;
        &lt;/table&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;script th:src="@{/webjars/jquery/3.0.0/jquery.min.js}"&gt;&lt;/script&gt;
&lt;script th:src="@{/webjars/popper.js/1.12.9-1/umd/popper.min.js}"&gt;&lt;/script&gt;
&lt;script th:src="@{/webjars/bootstrap/4.0.0-2/js/bootstrap.min.js}"&gt;&lt;/script&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre><p>En esta plantilla <strong>Thymeleaf</strong>, importamos <strong>Bootstrap</strong> y <strong>Thymeleaf</strong> tal y como antes y accedemos a la variable usuario que fue añadida al modelo en nuestro controlador mediante el uso de la sintaxis ${}.</p><p>Para acceder a la información, Thymeleaf utiliza los métodos getter de los objetos y podemos imprimir esa información al usar el atributo <code>th:text</code>. Thymeleaf también soporta bucles. En el <code>tbody</code> tenemos un bucle <code>th:each</code>, el cual lo podemos pensar como un bucle foreach sobre las reservaciones del usuario.</p><p>Puede que tengas un error que diga algo como "Could not initialize proxy, ... lazy loading". Esto sucede ya que la view está intentando acceder al objeto "reservations" mientras que aún no existe. Para sacarnos de encima este problema podemos modificar las siguientes líneas en <strong>User.java</strong>:</p><pre><code class="language-java">    @OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
    private Set&lt;Reservation&gt; reservations = new HashSet&lt;&gt;();</code></pre><p>Añadimos una declaración que le indica a Java que traiga este objeto prematuramente:</p><p>Ahora deberías poder ver la página de reservaciones:</p><figure class="kg-card kg-image-card"><img src="https://erinc.io/wp-content/uploads/2021/05/image-1-1024x488.png" class="kg-image" alt="image-1-1024x488" width="600" height="400" loading="lazy"></figure><h3 id="c-mo-crear-una-reservaci-n"><strong>Cómo Crear una Reservación</strong></h3><p>También necesitaremos alguna forma de crear nuevas reservas, así que construiremos ese mecanismo para nuestro usuario pre-creado que mostramos en las reservaciones. Después podeemos alterarlo para mostrar las reservas del usuario conectado.</p><p>Antes de proseguir, necesitaremos actualizar los formatos de fecha en nuestro archivo <strong>Reservation.java</strong> para evitar cualquier problema de formatos no coincidentes. Asegúrate que tus formatos para estas variables son iguales:</p><pre><code class="language-java">    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Column(nullable = false)
    private LocalDate reservationDate;

    @DateTimeFormat(pattern = "HH:mm")
    @Column
    private LocalTime startTime;

    @DateTimeFormat(pattern = "HH:mm")
    @Column
    private LocalTime endTime;</code></pre><p>En la sección previa, creamos nuestro controlador <strong>reservations</strong>. Ahora necesitamos modificarlo un poco para añadirle otro atributo al modelo.</p><p>Aprendimos que podemos acceder a los objetos que son añadidos al modelo al utilizar la sintaxis ${}. Ahora haremos algo similar:</p><pre><code class="language-java">@GetMapping("/reservations")
    public String reservations(Model model, HttpSession session) {
        User user = userService.get(10000L);
        session.setAttribute("user", user);
        Reservation reservation = new Reservation();
        model.addAttribute("reservation", reservation);

        return "reservations";
    }</code></pre><p>Estamos actualizando nuestro controlador de reservaciones para mover el objeto usuario a la sesión ya que queremos que sea accesible desde otro método de controlador y no solo desde una plantilla.</p><p>Piénsalo así: una vez que el usuario inicia sesión, la cuenta de este usuario será responsable por cada acción que sea hecha después de ese punto. Puedes pensar de "Session" como una variable global que es accesible desde cualquier parte.</p><p>También creamos un objeto <strong>Reservation</strong> y lo añadimos al modelo. <strong>Thymeleaf</strong> tendrá acceso a este objeto recién creado en nuestra plantilla de vista usando este modelo y llamará a los setters para establecer sus campos.</p><p>Ahora vamos a crear la vista para crear la reservación. Utilizaremos <a href="https://getbootstrap.com/docs/4.0/components/modal/"><strong>Bootstrap Modal</strong></a> <em>(Bootstrap v4.0)</em> para mostrar un formulario modal después de hacer click en el botón.</p><p>Primero podemos manejar el código para llamar el modal que crearemos en el siguiente paso, ve al archivo "reservations.html", y añade este fragmento después de la tabla que añadimos anteriormente:</p><pre><code class="language-html">&lt;button
  type="button"
  class="btn btn-primary"
  data-toggle="modal"
  data-target="#createReservationModal"
&gt;
  Create Reservation
&lt;/button&gt;

&lt;!-- Modal --&gt;
&lt;div
  th:insert="fragments/modal :: modal"
  th:with="reservation=${reservation}"
&gt;&lt;/div&gt;</code></pre><p>Este botón gatillará nuestro modal. En el div, insertamos este modal que crearemos usando la etiqueta <code>th:with</code> para pasar el objeto reservation que fue puesto en el modelo, en el controlador. Si no hacemos esto, el fragmento no sabrá sobre el objeto reservation.</p><p>También debemos cambiar la manera en como accedemos el usuario para imprimir su nombre ya que no lo almacenaremos en el modal, sino que en la sesión:</p><pre><code class="language-html">&lt;h3&gt;Welcome &lt;span th:text=" ${session.user.getFullName()}"&gt;&lt;/span&gt;&lt;/h3&gt;
</code></pre><p>Así que finalmente tu archivo <strong>reservations.html</strong> debiera verse así:</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en" xmlns:th="http://www.thymeleaf.org"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;title&gt;Reservations&lt;/title&gt;

    &lt;link
      th:rel="stylesheet"
      th:href="@{/webjars/bootstrap/4.0.0-2/css/bootstrap.min.css} "
    /&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div&gt;
      &lt;div th:insert="fragments/nav :: nav"&gt;&lt;/div&gt;
      &lt;div
        class="container"
        style="padding-top: 10vh; display: flex; flex-direction: column; align-items: center;"
      &gt;
        &lt;h3&gt;Welcome &lt;span th:text=" ${session.user.getFullName()}"&gt;&lt;/span&gt;&lt;/h3&gt;
        &lt;br /&gt;
        &lt;table class="table"&gt;
          &lt;thead&gt;
            &lt;tr&gt;
              &lt;th scope="col"&gt;Amenity&lt;/th&gt;
              &lt;th scope="col"&gt;Date&lt;/th&gt;
              &lt;th scope="col"&gt;Start Time&lt;/th&gt;
              &lt;th scope="col"&gt;End Time&lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr th:each="reservation : ${session.user.getReservations()}"&gt;
              &lt;td th:text="${reservation.getAmenityType()}"&gt;&lt;/td&gt;
              &lt;td th:text="${reservation.getReservationDate()}"&gt;&lt;/td&gt;
              &lt;td th:text="${reservation.getStartTime()}"&gt;&lt;/td&gt;
              &lt;td th:text="${reservation.getEndTime()}"&gt;&lt;/td&gt;
            &lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;

        &lt;button
          type="button"
          class="btn btn-primary"
          data-toggle="modal"
          data-target="#createReservationModal"
        &gt;
          Create Reservation
        &lt;/button&gt;

        &lt;!-- Modal --&gt;
        &lt;div
          th:insert="fragments/modal :: modal"
          th:with="reservation=${reservation}"
        &gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;script th:src="@{/webjars/jquery/3.0.0/jquery.min.js}"&gt;&lt;/script&gt;
    &lt;script th:src="@{/webjars/popper.js/1.12.9-1/umd/popper.min.js}"&gt;&lt;/script&gt;
    &lt;script th:src="@{/webjars/bootstrap/4.0.0-2/js/bootstrap.min.js}"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre><p>Ahora estamos listos para crear el fragmento modal. Podemos crear un fragmento para el modal igual a como lo hicimos con el nav.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">pwd
/src/main/resources
cd templates/fragments
touch modal.html
</code></pre><figcaption>Creando el archivo modal.html</figcaption></figure><p>Y añade la siguiente plantilla de código:</p><pre><code class="language-html">&lt;html lang="en" xmlns:th="http://www.thymeleaf.org"&gt;
  &lt;body&gt;
    &lt;div
      class="modal fade"
      th:fragment="modal"
      id="createReservationModal"
      tabindex="-1"
      role="dialog"
      aria-labelledby="createReservationModalTitle"
      aria-hidden="true"
    &gt;
      &lt;div class="modal-dialog" role="document"&gt;
        &lt;div class="modal-content"&gt;
          &lt;div class="modal-header"&gt;
            &lt;h5 class="modal-title" id="createReservationModalTitle"&gt;
              Create Reservation
            &lt;/h5&gt;
            &lt;button
              type="button"
              class="close"
              data-dismiss="modal"
              aria-label="Close"
            &gt;
              &lt;span aria-hidden="true"&gt;&amp;times;&lt;/span&gt;
            &lt;/button&gt;
          &lt;/div&gt;

          &lt;div class="modal-body"&gt;
            &lt;form
              action="#"
              th:action="@{/reservations-submit}"
              th:object="${reservation}"
              method="post"
            &gt;
              &lt;div class="form-group row"&gt;
                &lt;label for="type-select" class="col-2 col-form-label"
                  &gt;Amenity&lt;/label
                &gt;
                &lt;div class="col-10"&gt;
                  &lt;select
                    class="form-control"
                    id="type-select"
                    th:field="*{amenityType}"
                  &gt;
                    &lt;option value="POOL"&gt;POOL&lt;/option&gt;
                    &lt;option value="SAUNA"&gt;SAUNA&lt;/option&gt;
                    &lt;option value="GYM"&gt;GYM&lt;/option&gt;
                  &lt;/select&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="form-group row"&gt;
                &lt;label for="start-date" class="col-2 col-form-label"
                  &gt;Date&lt;/label
                &gt;
                &lt;div class="col-10"&gt;
                  &lt;input
                    class="form-control"
                    type="date"
                    id="start-date"
                    name="trip-start"
                    th:field="*{reservationDate}"
                    value="2018-07-22"
                    min="2021-05-01"
                    max="2021-12-31"
                  /&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="form-group row"&gt;
                &lt;label for="start-time" class="col-2 col-form-label"
                  &gt;From&lt;/label
                &gt;
                &lt;div class="col-10"&gt;
                  &lt;input
                    class="form-control"
                    type="time"
                    id="start-time"
                    name="time"
                    th:field="*{startTime}"
                    min="08:00"
                    max="19:30"
                    required
                  /&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="form-group row"&gt;
                &lt;label for="end-time" class="col-2 col-form-label"&gt;To&lt;/label&gt;
                &lt;div class="col-10"&gt;
                  &lt;input
                    class="form-control"
                    type="time"
                    id="end-time"
                    name="time"
                    th:field="*{endTime}"
                    min="08:30"
                    max="20:00"
                    required
                  /&gt;
                  &lt;small&gt;Amenities are available from 8 am to 8 pm&lt;/small&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="modal-footer"&gt;
                &lt;button
                  type="button"
                  class="btn btn-secondary"
                  data-dismiss="modal"
                &gt;
                  Close
                &lt;/button&gt;
                &lt;button type="submit" class="btn btn-primary" value="Submit"&gt;
                  Save changes
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/form&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre><p>Hay algunos puntos importantes aquí de los cuales debes tomar nota.</p><p>Nota como hemos accedido al objeto reservation en la etiqueta form:</p><pre><code class="language-html">&lt;form
  action="#"
  th:action="@{/reservations-submit}"
  th:object="${reservation}"
  method="post"
&gt;&lt;/form&gt;</code></pre><p></p><p>La etiqueta <strong><strong><code>th:object</code></strong></strong> asocia este formulario con el objeto <em>reservation</em> que creamos antes. <strong><strong><code>th:action</code></strong></strong> determina donde este objeto será enviado cuando el formulario es enviado, y nuestro método de envío será <strong>POST</strong>. Crearemos este controlador con la ruta <strong><strong>/reservations-submit</strong></strong> después de este paso.</p><p>Usamos la etiqueta <strong><strong><code>th:field</code></strong></strong> para enlazar las entradas de los campos de nuestro objeto reservation. Thymeleaf llama los setters del objeto reservation cuando los valores de los campos de entrada cambian.</p><p>Ahora vamos a crear el controlador que recibirá este formulario. Ve a <strong>HomeController</strong> y añade el siguiente método:</p><pre><code class="language-java">@PostMapping("/reservations-submit")
    public String reservationsSubmit(@ModelAttribute Reservation reservation,
                                     @SessionAttribute("user") User user) {

        // Save to DB after updating
        assert user != null;
        reservation.setUser(user);
        reservationService.create(reservation);
        Set&lt;Reservation&gt; userReservations = user.getReservations();
        userReservations.add(reservation);
        user.setReservations(userReservations);
        userService.update(user.getId(), user);
        return "redirect:/reservations";
    }</code></pre><p>Y también añade <strong>ReservationService</strong> a nuestras dependencias:</p><pre><code class="language-java">    final UserService userService;
    final ReservationService reservationService;

    public HomeController(UserService userService, ReservationService reservationService) {
        this.userService = userService;
        this.reservationService = reservationService;
    }</code></pre><p>Después que nuestro fragmento modal envía el objeto reservation a este controlador, ese objeto será enlazado con la anotación <strong><strong>@ModelAttribute</strong></strong>. También necesitamos el usuario así que usamos <strong><strong>@SessionAttribute</strong></strong> para tener una referencia a él.</p><p>Los campos del objeto reservation debieran ser todos establecidos por el formulario. Ahora solo necesitamos guardarlo en la base de datos.</p><p>Esto lo hacemos al llamar al método <strong>create</strong>. Después añadimos la nueva reserva a la lista de reservas del usuario y actualizamos el usuario para reflejar esos cambios. Después entonces redirigimos el usuario a la página de reservaciones para mostrar la lista de reservas actualizada.</p><p>Tu nueva página de reservaciones debiera verse así:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/LFJE0Ad---Imgur.png" class="kg-image" alt="LFJE0Ad---Imgur" width="600" height="400" loading="lazy"></figure><p>Y cuando haces click en el botón, el modal para crear la reserva debería aparecer.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/image-42.png" class="kg-image" alt="image-42" width="600" height="400" loading="lazy"></figure><h2 id="c-mo-a-adir-autenticaci-n-y-autorizaci-n-a-la-app"><strong>Cómo Añadir Autenticación y Autorización a la App</strong></h2><p>Utilizaremos <strong>Spring Security</strong> para añadir autenticación y autorización en nuestra aplicación. Queremos asegurarnos que nadie pueda ver las reservas de nadie más y que los usuarios deban iniciar sesión para crear reservas.</p><p>Si deseas aprender más de ello, escribí un artículo que provee una vista general de <a href="https://auth0.com/blog/spring-security-overview/">Spring Security</a> <em>(Artículo en inglés)</em>.</p><p>Lo mantendremos simple y mayormente usaremos los elementos por defecto ya que es un elemento complejo ya de por sí. Si quieres aprender cómo establecer Spring Security Auth debidamente, puedes revisar <a href="https://www.freecodecamp.org/news/how-to-setup-jwt-authorization-and-authentication-in-spring/">mi artículo</a> <em>(artículo en inglés)</em> en ello.</p><p>Necesitamos añadir "Spring Security" y "Thymeleaf Spring Security" a nuestras dependencias, así que abre tu pom.xml y añade lo siguiente a tu lista de dependencias:</p><pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;org.thymeleaf.extras&lt;/groupId&gt;
    &lt;artifactId&gt;thymeleaf-extras-springsecurity5&lt;/artifactId&gt;
    &lt;version&gt;3.0.4.RELEASE&lt;/version&gt;
&lt;/dependency&gt;</code></pre><p>Ahora, por defecto, Spring Security protege todos los endpoints, así que necesitaremos configurarlo para que permita ver la página de inicio.</p><p>Vamos a crear una carpeta de configuración para guardar nuestro archivo <strong>WebSecurityConfig</strong>. Asumiendo que estás en la carpeta raíz:</p><pre><code class="language-bash">cd /src/main/java/com/amenity_reservation_system
mkdir config &amp;&amp; cd config
touch WebSecurityConfig.java</code></pre><p>Este debiera ser el contenido de tu archivo config:</p><figure class="kg-card kg-code-card"><pre><code class="language-java">package com.amenity_reservation_system.config;

import com.amenity_reservation_system.service.UserDetailsServiceImpl;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsServiceImpl userDetailsService;

    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    public WebSecurityConfig(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/", "/webjars/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .logout()
                .permitAll()
                .logoutSuccessUrl("/");
    }

    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

}</code></pre><figcaption>WebSecurityConfig.java</figcaption></figure><p>No iré en los detalles, pero aquí hay un resumen de lo que ha pasado:</p><ul><li>Configuramos Spring Security para permitir todas las peticiones hechas a la página de inicio ("/")</li><li>Configuramos nuestros estilos ("/webjars/**")</li><li>Le solicitamos que nos provea con formularios de inicio y cierre de sesión.</li><li>Le solicitamos que permita las peticiones a ellos así como redirigir a la página principal después que el cierre de sesión sea exitoso.</li></ul><p>¿No es increíble lo que puedes lograr usando solo unas pocas declaraciones?</p><p>Tambien configuramos nuestro <strong><strong>AuthenticationManagerBuilder</strong></strong> para usar bCryptPasswordEncoder y userDetailsService. Pero espera, aún no tenemos ninguno de ellos, y tu IDE puede que se esté quejando de eso. Así que los crearemos.</p><p>Antes de seguir adelante, puede ser una buena idea añadir campos <strong>username</strong> y passwordHash a nuestra clase <strong>User</strong>. Los utilizaremos para autenticar el usuario en vez de usar su nombre completo. Después lo añadiremos al constructor.</p><figure class="kg-card kg-code-card"><pre><code class="language-java">package com.amenity_reservation_system.model;

import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;


@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @Id
    @Column(nullable = false, updatable = false)
    @SequenceGenerator(
            name = "primary_sequence",
            sequenceName = "primary_sequence",
            allocationSize = 1,
            initialValue = 10000
    )
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE,
            generator = "primary_sequence"
    )
    private Long id;

    @Column(nullable = false, unique = true)
    private String fullName;

    @Column(nullable = false, unique = true)
    private String username;

    @Column
    private String passwordHash;

    @OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
    private Set&lt;Reservation&gt; reservations = new HashSet&lt;&gt;();

    @Column(nullable = false, updatable = false)
    private OffsetDateTime dateCreated;

    @Column(nullable = false)
    private OffsetDateTime lastUpdated;

    @PrePersist
    public void prePersist() {
        dateCreated = OffsetDateTime.now();
        lastUpdated = dateCreated;
    }

    @PreUpdate
    public void preUpdate() {
        lastUpdated = OffsetDateTime.now();
    }

    public User(String fullName, String username, String passwordHash) {
        this.fullName = fullName;
        this.username = username;
        this.passwordHash = passwordHash;
    }
}</code></pre><figcaption>User.java</figcaption></figure><p>Crea un archivo llamado <strong><strong>UserDetailsServiceImpl</strong></strong> bajo la carpeta services:</p><pre><code class="language-bash">cd service
touch UserDetailsServiceImpl.java</code></pre><figure class="kg-card kg-code-card"><pre><code class="language-java">package com.amenity_reservation_system.service;

import com.amenity_reservation_system.model.User;
import com.amenity_reservation_system.repos.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private UserRepository userRepository;

    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        final User user = userRepository.findUserByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException(username);
        }

        UserDetails userDetails = org.springframework.security.core.userdetails.User.withUsername(
                user.getUsername()).password(user.getPwHash()).roles("USER").build();

        return userDetails;
    }
}
</code></pre><figcaption>UserDetailsServiceImpl.java</figcaption></figure><p>Esto básicamente le dice a Spring Security que queremos usar la entidad <strong>User</strong> &nbsp;que creamos antes al obtener el objeto <strong>User</strong> de nuestra base de datos y que use el método JPA de nuestro repositorio. Pero nuevamente, no tenemos el método <strong><strong>findUserByUsername</strong></strong> <em>(encontrarUsuarioPorNombredeusuario) </em>en nuestro <strong><strong>UserRepository</strong> </strong><em>(RepositorioUsuario)</em>. Puedes intentar arreglar esto por tu cuenta a modo de reto, es bastante simple.</p><p>Recuerda, no necesitamos escribir consultas. Es suficiente proveer la firma y dejar que JPA haga el trabajo.</p><figure class="kg-card kg-code-card"><pre><code class="language-java">package com.amenity_reservation_system.repos;

import com.amenity_reservation_system.model.User;
import org.springframework.data.jpa.repository.JpaRepository;


public interface UserRepository extends JpaRepository&lt;User, Long&gt; {

    User findUserByUsername(String username);
}
</code></pre><figcaption>UserRepository.java</figcaption></figure><p>We also need a <strong><strong>BCryptPasswordEncoder</strong></strong> bean to satisfy that dependency in <strong><strong>WebSecurityConfig</strong></strong> and to make it work. Let's modify our main class to add a bean and change the constructor parameters to give our predefined <strong><strong>User</strong></strong> a username.</p><p>También necesitamos el bean <strong><strong>BCryptPasswordEncoder</strong></strong> para satisfacer esa dependencia en <strong><strong>WebSecurityConfig</strong></strong> y para hacer que funcione. Modificaremos nuestra clase principal para añadir un bean y cambiar los parámetros del constructor para darle a nuestro <strong>User</strong> predefinido un username <em>(nombre de usuario).</em></p><pre><code class="language-java">package com.amenity_reservation_system;

import com.amenity_reservation_system.model.AmenityType;
import com.amenity_reservation_system.model.Reservation;
import com.amenity_reservation_system.model.User;
import com.amenity_reservation_system.repos.ReservationRepository;
import com.amenity_reservation_system.repos.UserRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Date;


@SpringBootApplication
public class AmenityReservationSystemApplication {

    public static void main(String[] args) {
        SpringApplication.run(AmenityReservationSystemApplication.class, args);
    }


    @Bean
    public CommandLineRunner loadData(UserRepository userRepository,
                                      ReservationRepository reservationRepository) {
    return (args) -&gt; {
      User user =
          userRepository.save(
              new User("Yigit Kemal Erinc",
                      "yigiterinc",
                      bCryptPasswordEncoder().encode("12345")));
      DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
      Date date = new Date();
      LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
      Reservation reservation =
          Reservation.builder()
              .reservationDate(localDate)
              .startTime(LocalTime.of(12, 00))
              .endTime(LocalTime.of(13, 00))
              .user(user)
              .amenityType(AmenityType.POOL)
              .build();

      reservationRepository.save(reservation);
    };
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}</code></pre><p>Tu aplicación ahora debería estar lista para compilar y ya tendría que redirigirte a la página de login si envías una petición a "/reservations".</p><p>Sería agradable tener botones para iniciar y cerrar sesión en la barra de navegación, y queremos mostrar el inicio de sesión si no está autenticado y viceversa. Podemos hacerlo de esta manera en <strong>nav.htm</strong>: </p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml"&gt;
&lt;body&gt;
&lt;nav th:fragment="nav" class="navbar navbar-expand navbar-dark bg-primary"&gt;
    &lt;div class="navbar-nav w-100"&gt;
        &lt;a class="navbar-brand text-color" href="/"&gt;Amenities Reservation System&lt;/a&gt;
    &lt;/div&gt;
        &lt;a sec:authorize="isAnonymous()"
           class="navbar-brand text-color" th:href="@{/login}"&gt;Log in&lt;/a&gt;
        &lt;a sec:authorize="isAuthenticated()"
               class="navbar-brand text-color" th:href="@{/logout}"&gt;Log out&lt;/a&gt;
&lt;/nav&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><figcaption>nav.html</figcaption></figure><p>El enlace para iniciar sesión ahora debiera ser visible en la navbar.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screen-Shot-2021-09-10-at-02.19.09.png" class="kg-image" alt="Screen-Shot-2021-09-10-at-02.19.09" width="600" height="400" loading="lazy"><figcaption>Página principal sin la sesión iniciada.</figcaption></figure><h2 id="c-mo-mostrar-las-reservaciones-de-los-usuarios-conectados"><strong>Cómo Mostrar las Reservaciones de los Usuarios Conectados</strong></h2><p>Nuestra página de reservaciones actualmente está mostrando las reservaciones de un usuario fijo y no las reservas del usuario conectado.</p><figure class="kg-card kg-code-card"><pre><code class="language-java">    @GetMapping("/reservations")
    public String reservations(Model model, HttpSession session) {
        User user = userService.get(10000L);
        session.setAttribute("user", user);
        Reservation reservation = new Reservation();
        model.addAttribute("reservation", reservation);

        return "reservations";
    }
</code></pre><figcaption>Controlador de reservas actual</figcaption></figure><p>Necesitamos mostrar las reservas del usuario actual (con la sesión iniciada). Para lograr eso, debemos usar algo de Spring Security.</p><p>Ve a la clase <strong>HomeController</strong> (Lo sé, ese nombre es un tanto problemático ahora) y cámbiala con el código a continuación:</p><figure class="kg-card kg-code-card"><pre><code class="language-java">@GetMapping("/reservations")
    public String reservations(Model model, HttpSession session) {
        UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String name = principal.getUsername();
        User user = userService.getUserByUsername(name);

        // This should always be the case 
        if (user != null) {
            session.setAttribute("user", user);

            // Empty reservation object in case the user creates a new reservation
            Reservation reservation = new Reservation();
            model.addAttribute("reservation", reservation);

            return "reservations";
        }

        return "index";    
        }</code></pre><figcaption>Controlador de reservas</figcaption></figure><p>Debido a que añadimos Spring Security al proyecto, este automáticamente crea el objeto <strong><strong>Authentication</strong></strong> detrás de las escenas - obtenemos eso desde <strong><strong>SecurityContextHolder</strong></strong>.</p><p>Obtenemos el objeto <strong><strong><a href="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/UserDetails.html#:~:text=Interface%20UserDetails&amp;text=Provides%20core%20user%20information.,later%20encapsulated%20into%20Authentication%20objects.">UserDetails</a></strong></strong> que guarda la información relacionada al usuario. Después evaluamos si el objeto usuario es nulo. Esto debería ser siempre el caso ya que <em><em>reservations</em></em> es un endpoint protegido y el usuario siempre debe iniciar sesión para ver esa página - pero siempre es bueno asegurarse que todo es según lo esperado.</p><p>Después llamámos a la clase <strong>UserService</strong> para obtener el objeto <strong>User</strong> que tiene su nombre de usuario - pero aún no hemos añadido el método <strong><strong>getUserByUsername</strong></strong>. Así que iremos a <strong>UserService</strong> y añadiremos este simple método.</p><pre><code class="language-java">    public User getUserByUsername(String username) {
        return userRepository.findUserByUsername(username);
    }</code></pre><p>Ahora deberías poder ver las reservas del usuario conectado. Puedes intentarlo al añadir otro usuario y crear reservas para ese usuario también.</p><h3 id="c-mo-evaluar-la-capacidad"><strong>Cómo Evaluar la Capacidad</strong></h3><p>Actualmente no tenemos un mecanismo para almacenar la capacidad de cada tipo de servicio. Necesitamos almacenarlos de alguna forma y también evaluar si hay suficiente capacidad antes de aprobar alguna reservación.</p><p>Con ese propósito, vamos a crear una clase llamada <strong>Capacity</strong> bajo la carpeta model.</p><figure class="kg-card kg-code-card"><pre><code class="language-java">package com.amenity_reservation_system.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Capacity {

    @Id
    @Column(nullable = false, updatable = false)
    @SequenceGenerator(
            name = "primary_sequence",
            sequenceName = "primary_sequence",
            allocationSize = 1,
            initialValue = 10000
    )
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE,
            generator = "primary_sequence"
    )
    private Long id;

    @Column(nullable = false, unique = true)
    @Enumerated(EnumType.STRING)
    private AmenityType amenityType;

    @Column(nullable = false)
    private int capacity;

    public Capacity(AmenityType amenityType, int capacity) {
        this.amenityType = amenityType;
        this.capacity = capacity;
    }
}</code></pre><figcaption>Capacity.java</figcaption></figure><p>Esta es la entidad que representará nuestro construct lógico a ser guardado en nuestra base de datos. Es básicamente una entrada de ruta con un AmenityType <em>(tipo de servicio) </em>y su capacidad correspondiente. </p><p>También necesitamos un repositorio para guardar las entradas de <strong>Capacity</strong>, así que vamos a crear CapacityRepository bajo la carpeta <strong>repos</strong>.</p><pre><code class="language-java">package com.amenity_reservation_system.repos;

import com.amenity_reservation_system.model.Capacity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CapacityRepository extends JpaRepository&lt;Capacity, Long&gt; {
}
</code></pre><p>Necesitamos llenar esta nueva tabla con las capacidades iniciales. Podríamos leerlas desde un archivo de configuración o algo, pero lo mantendremos simple y lo estableceremos directamente usando loadData en nuestro método principal. </p><pre><code class="language-java">package com.amenity_reservation_system;

import com.amenity_reservation_system.model.AmenityType;
import com.amenity_reservation_system.model.Capacity;
import com.amenity_reservation_system.model.Reservation;
import com.amenity_reservation_system.model.User;
import com.amenity_reservation_system.repos.CapacityRepository;
import com.amenity_reservation_system.repos.ReservationRepository;
import com.amenity_reservation_system.repos.UserRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@SpringBootApplication
public class AmenityReservationSystemApplication {

  private Map&lt;AmenityType, Integer&gt; initialCapacities =
      new HashMap&lt;&gt;() {
        {
          put(AmenityType.GYM, 20);
          put(AmenityType.POOL, 4);
          put(AmenityType.SAUNA, 1);
        }
      };

  public static void main(String[] args) {
    SpringApplication.run(AmenityReservationSystemApplication.class, args);
  }

  @Bean
  public CommandLineRunner loadData(
      UserRepository userRepository,
      CapacityRepository capacityRepository) {
    return (args) -&gt; {
      userRepository.save(
          new User("Yigit Kemal Erinc", "yigiterinc", bCryptPasswordEncoder().encode("12345")));

      for (AmenityType amenityType : initialCapacities.keySet()) {
        capacityRepository.save(new Capacity(amenityType, initialCapacities.get(amenityType)));
      }
    };
  }

  @Bean
  public BCryptPasswordEncoder bCryptPasswordEncoder() {
    return new BCryptPasswordEncoder();
  }
}
</code></pre><p>Acabo de añadir las capacidades dentro del mapa <strong>initialCapacities</strong> <em>(capacidades iniciales)</em> y luego las guarde en <strong>CapacityRepository</strong> <em>(repositorio de capacidades) </em>dentro del método <strong>loadData</strong>.</p><p>Ahora podemos revisar si el número de reservaciones en la hora solicitada excede la capacidad y rechazar la petición de reserva si lo hace.</p><p>Esta es la lógica: Necesitamos obtener el número de reservas que están en un mismo día y superponerlas con la petición actual. Después necesitamos obtener el aforo para este tipo de servicio y si la capacidad es excedida podemos lanzar una excepción.</p><p>Por lo tanto, necesitamos una consulta para obtener el número de posibles reservas superpuestas. No es la consulta más fácil de escribir, pero JPA es bastante conveniente y tenemos acceso a esa consulta dentro de nuestro <strong><strong>ReservationRepository</strong></strong> sin la necesidad de escribir SQL o HQL.</p><p>Te animo a que lo intentes por tu cuenta antes de seguir, ya que esta es la única razón de por qué he añadido este concepto de capacidad en este tutorial (para mostrar un ejemplo más avanzado de consultas JPA)</p><p>Así que así es como el método <strong>ReservationService</strong> se ve. Necesitas reemplazar el 0 (cero) con una llamada a reservationRepository para obtener el número de reservas superpuestas.</p><p>Si el número actual de reservas superpuestas es igual a la capacidad, significa que la siguiente la excederá, así que lanzamos una excepción.</p><pre><code class="language-java">public Long create(final Reservation reservation) {
        int capacity = capacityRepository.findByAmenityType(reservation.getAmenityType()).getCapacity();
        int overlappingReservations = 0; // TODO

        if (overlappingReservations &gt;= capacity) {
            // Throw a custom exception
        }

        return reservationRepository.save(reservation).getId();
    }</code></pre><p>Para encontrar las reservaciones superpuestas hay algunas condiciones que debemos evaluar:</p><p>Primero que todo, la fecha de la reservación debe ser la misma que de la petición:</p><ol><li>La hora de inicio puede ser antes que el startTime (hora de inicio) de una nueva petición. En ese caso, la hora de término debe ser más tarde que en nuestra petición, en orden de que solapen (startTimeBeforeAndEndTimeAfter) <em>(Hora de inicio antes y hora de término después)</em>.</li><li>O que la hora de término puede ser posteriormente, pero la hora de inicio puede estar entre la hora de inicio o la hora de término de la petición (endTimeAfterOrStartTimeBetween) <em>(Hora de término después u hora de inicio entre medio)</em>. </li></ol><p>Así que nuestra consulta final debiera retornar todas las reservas que coincidan con cualquiera de esas 2 posibilidades.</p><p>Podemos expresarlo así:</p><pre><code class="language-java">List&lt;Reservation&gt; findReservationsByReservationDateAndStartTimeBeforeAndEndTimeAfterOrStartTimeBetween
            (LocalDate reservationDate, LocalTime startTime, LocalTime endTime, LocalTime betweenStart, LocalTime betweenEnd);
</code></pre><p>Y el método create final se ve así:</p><pre><code class="language-java"> public Long create(final Reservation reservation) {
        int capacity = capacityRepository.findByAmenityType(reservation.getAmenityType()).getCapacity();
        int overlappingReservations = reservationRepository
                .findReservationsByReservationDateAndStartTimeBeforeAndEndTimeAfterOrStartTimeBetween(
                        reservation.getReservationDate(),
                        reservation.getStartTime(), reservation.getEndTime(),
                        reservation.getStartTime(), reservation.getEndTime()).size();

        if (overlappingReservations &gt;= capacity) {
            throw new CapacityFullException("This amenity's capacity is full at desired time");
        }

        return reservationRepository.save(reservation).getId();
    }</code></pre><p>No necesitas preocuparte sobre la excepción customizada, pero si estás interesado, aquí está el código:</p><pre><code class="language-java">package com.amenity_reservation_system.exception;

public class CapacityFullException extends RuntimeException {
    public CapacityFullException(String message) {
        super(message);
    }
}</code></pre><p>Normalmente deberíamos mostrar un error modal si la capacidad es excedida pero lo saltaré para evitar cosas de UI repetitivas. Puedes intentarlo como un reto si deseas.</p><h2 id="conclusi-n">Conclusión</h2><p>En este tutorial, hemos aprendido sobre tantas tecnologías que hacen el desarrollo de Spring Boot más fácil y más rápido.</p><p>Creo que muchas personas subestiman el framework en términos de velocidad de desarrollo y la calidad del trabajo resultante.</p><p>Asumiendo que eres fluido con la tecnología, argumentaría que Spring Boot no es más lento (en desarrollo) que cualquier otro framework backend si haces todo en la moda moderna.</p><p>Puedes encontrar todo el código en este repositorio <em>(en inglés)</em>:</p><p><a href="https://github.com/yigiterinc/amenity-reservation-system.git">https://github.com/yigiterinc/amenity-reservation-system.git</a></p><p>Si estás interesado en leer más contenido como este, siéntete libre de suscribirte a mi blog en <a href="https://erinc.io/" rel="noopener">https://erinc.io</a> <em>(blog en inglés)</em> para ser notificado de mis nuevas publicaciones.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo cambiar el brillo de la pantalla en Windows 10: Ajustes de brillo y cómo reducirlo ]]>
                </title>
                <description>
                    <![CDATA[ Si tu pantalla es muy brillante, te puede dar problemas oculares - especialmente si te sientas en frente de un computador programando todo el día. Esto es porque una pantalla muy brillante puede forzar el ojo y fatigarlo, lo que puede derivar en dolores de cabeza y otros padecimientos. Si ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-cambiar-el-brillo-de-la-pantalla-en-windows10/</link>
                <guid isPermaLink="false">63f6f39f2154fe0736d60dfd</guid>
                
                    <category>
                        <![CDATA[ Windows 10 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Josué Leiva ]]>
                </dc:creator>
                <pubDate>Fri, 03 Mar 2023 02:10:51 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2023/02/windows10.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/how-to-change-screen-brightness-on-windows-10/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Change Screen Brightness on Windows 10 – Brightness Settings and How to Turn Brightness Down</a>
      </p><p>Si tu pantalla es muy brillante, te puede dar problemas oculares - especialmente si te sientas en frente de un computador programando todo el día.</p><p>Esto es porque una pantalla muy brillante puede forzar el ojo y fatigarlo, lo que puede derivar en dolores de cabeza y otros padecimientos.</p><p>Si eres un usuario regular de la computadora, la salud ocular es crítica para un desempeño óptimo. Así que en este artículo, te mostraré 5 formas diferentes de cambiar el brillo de tu pantalla en Windows 10.</p><h2 id="c-mo-cambiar-el-ajuste-del-brillo-en-el-centro-de-acciones-de-windows-10">Cómo cambiar el ajuste del brillo en el centro de acciones de Windows 10</h2><p>La forma más rápida de bajar el brillo o ajustar el brillo de tu pantalla es en Centro de Acción.</p><p>Para cambiar el brillo, haz clic en el ícono de notificación al extremo derecho de la barra de tareas. Tendrás acceso a un deslizador con el cual puedes incrementar o decrementar el brillo.</p><p>Si no ves el deslizador aparecer inmediatamente, haz clic en "expandir" para revelarlo.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/08/brightness-action-center.gif" class="kg-image" alt="brightness-action-center" width="600" height="400" loading="lazy"></figure><h2 id="c-mo-cambiar-los-ajustes-del-brillo-en-el-centro-de-movilidad-de-windows"><strong>Cómo cambiar los ajustes del brillo en el centro de movilidad de Windows</strong></h2><p>Otra forma rápida de cambiar el brillo de tu pantalla es hacerlo en el Centro de Movilidad de Windows.</p><p>Hay varias formas de llegar hasta ahí. Una es hacer clic derecho en tu medidor de batería y seguidamente seleccionar "Centro de Movilidad de Windows". Otra es al hacer clic derecho en el menú de Inicio (logo de Windows) y de ahí seleccionar "Centro de Movilidad"</p><p>La primera pestaña que verás es un deslizador para ajustar el brillo a cualquier nivel que desees.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2023/02/image-12.png" class="kg-image" alt="image-12" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2023/02/image-12.png 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2023/02/image-12.png 1000w, https://www.freecodecamp.org/espanol/news/content/images/2023/02/image-12.png 1016w" sizes="(min-width: 720px) 720px" width="1016" height="763" loading="lazy"></figure><h2 id="c-mo-cambiar-el-brillo-en-los-ajustes-de-windows-10"><strong>Cómo cambiar el brillo en los ajustes de Windows 10</strong></h2><p>También puedes ajustar el brillo en el menú de ajustes.</p><p><strong>Paso 1: </strong>Hacer clic en el menú de Inicio (Logo de Windows).</p><p><strong>Paso 2:</strong> Hacer clic en Configuración.</p><p><strong>Paso 3:</strong> Abrir Sistema y luego haz clic en Pantalla.</p><p><strong>Paso 4:</strong> Justo bajo Brillo y Color, utiliza el deslizador para ajustar el Brillo de la pantalla a uno que te acomode. Deslizar hacia la izquierda disminuye el brillo, y hacia la derecha lo aumenta.</p><p>Si te gusta utilizar un atajo teclado, presiona y mantiene las teclas Windows e I (Windows + I) para abrir la configuración, luego haz clic en Pantalla para obtener tener acceso al deslizador para aumentar y disminuir el nivel de brillo. </p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/08/brightness-in-settings.gif" class="kg-image" alt="brightness-in-settings" width="600" height="400" loading="lazy"></figure><h2 id="c-mo-cambiar-el-ajuste-de-brillo-autom-ticamente-seg-n-la-vida-til-de-tu-bater-a">Cómo cambiar el ajuste de brillo automáticamente según la vida útil de tu batería</h2><p>Esta es una buena idea tanto para tus ojos como para tu computadora. Windows 10 tiene una opción que te permite disminuir el brillo de la pantalla cuando la vida útil de tu batería se encuentra en o bajo cierto porcentaje.</p><p>Para hacerlo, sigue los pasos abajo:</p><p><strong>Paso 1:</strong> Haz clic en Inicio, Configuración y luego en Sistema. O simplemente presiona y mantiene Win + I.</p><p><strong>Paso 2:</strong> Haz clic en "Sistema" y luego en "Batería".</p><p><strong>Paso 3:</strong> Bajo "Ahorro de Batería", habilita "Activar automáticamente el ahorro de batería a las"</p><p><strong>Paso 4: </strong>Usa el selector para seleccionar el porcentaje al cual deseas que ahorrar batería se encienda.</p><p><strong>Paso 5:</strong> Habilita "Reducir el brillo de la pantalla con el ahorro de batería activado"</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2023/02/image-11.png" class="kg-image" alt="image-11" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2023/02/image-11.png 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2023/02/image-11.png 1000w, https://www.freecodecamp.org/espanol/news/content/images/2023/02/image-11.png 1015w" sizes="(min-width: 720px) 720px" width="1015" height="723" loading="lazy"><figcaption>(El ahorro de batería debe estar activado)</figcaption></figure><h2 id="c-mo-cambiar-el-brillo-de-la-pantalla-con-un-atajo-del-teclado"><strong>Cómo cambiar el brillo de la pantalla con un atajo del teclado</strong></h2><p>La mayoría de los teclados tienen atajos para disminuir o aumentar el brillo de la pantalla, especialmente si estás utilizando una laptop (o portátil),</p><p>Las dos teclas usualmente tienen un ícono de sol con una flecha apuntando hacia abajo para disminuir el brillo, y otra flecha apuntando hacia arriba para aumentar el brillo. Algunos no tienen flechas, sino que solo en ícono de sol.</p><p>Las combinaciones más comunes de atajos del teclado son la "tecla de función" (fn) + la tecla designada para aumentar o disminuir el brillo.</p><p>Aparte de los 5 métodos que comparto arriba, puedes automáticamente ajustar el brillo según la luz ambiental, pero no todas las computadoras Windows 10 pueden hacer esto.</p><p>Para tener acceso a esta opción, abre Configuración, haz clic en Sistema, y después en Pantalla. Si eres capaz de ver "Ajustar el brillo automáticamente según luz ambiental", entonces tu computador tiene el sensor que detecta la luz ambiental. Si no lo ves, entonces tu computador no tiene acceso a esta cualidad.</p><h2 id="en-conclusi-n">En conclusión</h2><p>Espero que este artículo te ayude a familiarizarte con las varias maneras en las que puedes disminuir el brillo de tu computador con Windows 10. Esto te ayudará a proteger tus ojos y a desempeñarte mejor en tu trabajo.</p><p>Gracias por leer y que tengas un buen rato.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo generar un Reporte de Excel en una API REST de Spring Boot con Apache POI y Kotlin ]]>
                </title>
                <description>
                    <![CDATA[ En este artículo, me gustaría mostrarte cómo generar reportes Excel en los formatos .xls y .xlsx (también conocidos como Open XML) en una API REST de Spring Boot con Apache POI y Kotlin. Después del término de esta guía, tendrás un entendimiento fundamental sobre cómo crear de forma personalizada, formatos ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/generar-reporte-excel-en-rest-api-spring/</link>
                <guid isPermaLink="false">63e982f622450a0629adc3eb</guid>
                
                    <category>
                        <![CDATA[ sprint boot ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Josué Leiva ]]>
                </dc:creator>
                <pubDate>Tue, 21 Feb 2023 14:31:57 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2023/02/New-Banner.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/generate-excel-report-in-spring-rest-api/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Generate an Excel Report in a Spring Boot REST API with Apache POI and Kotlin</a>
      </p><p>En este artículo, me gustaría mostrarte cómo generar reportes Excel en los formatos <strong>.xls</strong> y <strong>.xlsx </strong>(también conocidos como Open XML) en una <strong>API REST de Spring Boot</strong> con <strong>Apache POI y Kotlin</strong>.</p><p>Después del término de esta guía, tendrás un entendimiento fundamental sobre cómo crear de forma personalizada, formatos de celdas, estilos y fuentes. Al final, te mostraré cómo crear endpoints para que así puedas descargar los archivos generados fácilmente.</p><p>Para visualizar mejor lo que aprenderemos, revisa la vista previa del resultado final.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/12/result_file_preview.png" class="kg-image" alt="result_file_preview" width="600" height="400" loading="lazy"></figure><h2 id="paso-1-a-adir-las-dependencias-necesarias-"><strong>Paso 1: Añadir las dependencias necesarias.</strong></h2><p>Como primer paso, vamos a crear un proyecto Spring Boot (Recomiendo altamente, utilizar la página <a href="https://start.spring.io/">Spring Initializr</a>) e importar las siguientes dependencias.</p><pre><code class="language-groovy">implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.apache.poi:poi:4.1.2")
implementation("org.apache.poi:poi-ooxml:4.1.2")</code></pre><p>Déjame explicar el propósito de cada librería.</p><ul><li>El <strong><strong>Spring Boot Starter Web</strong></strong> es necesario para crear la API REST en nuestra aplicación.</li><li><strong>Apache POI</strong> es una librería compleja de Java para trabajar con archivos Excel. Si quisiéramos trabajar solo con el formato <strong>.xls</strong>, entonces la dependencia <em>poi</em> sería suficiente. En nuestro caso, queremos añadir soporte para el formato <strong>.xlsx</strong>, por lo que el componente<em> poi-ooxml</em>, también es necesario.</li></ul><h2 id="paso-2-crear-los-modelos"><strong>Paso 2: Crear los modelos</strong></h2><p>Como siguiente paso, vamos a crear una clase tipo enum llamada <strong>CustomCellStyle</strong> con 4 constantes:</p><pre><code class="language-kotlin">enum class CustomCellStyle {
    GREY_CENTERED_BOLD_ARIAL_WITH_BORDER,
    RIGHT_ALIGNED,
    RED_BOLD_ARIAL_WITH_BORDER,
    RIGHT_ALIGNED_DATE_FORMAT
}</code></pre><p>Si bien el propósito de esta clase enum pueda verse un tanto enigmática al momento, se volverá clara en las siguientes secciones.</p><h2 id="paso-3-preparar-los-estilos-de-celdas"><strong>Paso 3: Preparar los estilos de celdas</strong></h2><p>La librería Apache POI viene con la interfaz <strong>CellStyle</strong>, la cual podemos utilizar para definir estilos y formatos personalizados entre filas, columnas y celdas.</p><p>Vamos a crear un componente <strong>StylesGenerator</strong>, el cual será responsable de preparar un mapa que contenga nuestros estilos personalizados.</p><pre><code class="language-kotlin">@Component
class StylesGenerator {

    fun prepareStyles(wb: Workbook): Map&lt;CustomCellStyle, CellStyle&gt; {
        val boldArial = createBoldArialFont(wb)
        val redBoldArial = createRedBoldArialFont(wb)

        val rightAlignedStyle = createRightAlignedStyle(wb)
        val greyCenteredBoldArialWithBorderStyle =
            createGreyCenteredBoldArialWithBorderStyle(wb, boldArial)
        val redBoldArialWithBorderStyle =
            createRedBoldArialWithBorderStyle(wb, redBoldArial)
        val rightAlignedDateFormatStyle =
            createRightAlignedDateFormatStyle(wb)

        return mapOf(
            CustomCellStyle.RIGHT_ALIGNED to rightAlignedStyle,
            CustomCellStyle.GREY_CENTERED_BOLD_ARIAL_WITH_BORDER to greyCenteredBoldArialWithBorderStyle,
            CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER to redBoldArialWithBorderStyle,
            CustomCellStyle.RIGHT_ALIGNED_DATE_FORMAT to rightAlignedDateFormatStyle
        )
    }
}</code></pre><p>Como puedes ver, con este acercamiento, creamos cada estilo una vez y lo ponemos dentro de un mapa para que así lo podamos referenciar después.</p><p>Hay bastantes técnicas de diseño que podríamos utilizar aquí, pero creo que utilizando un mapa y constantes enum es una de las mejores maneras para mantener el código más limpio y más fácil de modificar.</p><p>Dicho esto, añadamos algunas funciones faltantes dentro de la clase generadora. Comencemos primero con las fuentes personalizadas:</p><pre><code class="language-kotlin">private fun createBoldArialFont(wb: Workbook): Font {
    val font = wb.createFont()
    font.fontName = "Arial"
    font.bold = true
    return font
}</code></pre><p>La función <strong><strong>createBoldArialFont</strong></strong> crea una nueva instancia en negrita de la fuente Arial, la cual la usaremos más tarde.</p><p>Similarmente, vamos a implementar una función <strong><strong>createRedBoldArialFont</strong></strong> y establecer el color de la fuente a rojo:</p><pre><code class="language-kotlin">private fun createRedBoldArialFont(wb: Workbook): Font {
    val font = wb.createFont()
    font.fontName = "Arial"
    font.bold = true
    font.color = IndexedColors.RED.index
    return font
}</code></pre><p>Después de eso, podemos añadir otras funciones responsables de crear instancias <strong>CellStyle</strong> individuales.</p><pre><code class="language-kotlin">private fun createRightAlignedStyle(wb: Workbook): CellStyle {
    val style: CellStyle = wb.createCellStyle()
    style.alignment = HorizontalAlignment.RIGHT
    return style
}

private fun createBorderedStyle(wb: Workbook): CellStyle {
    val thin = BorderStyle.THIN
    val black = IndexedColors.BLACK.getIndex()
    val style = wb.createCellStyle()
    style.borderRight = thin
    style.rightBorderColor = black
    style.borderBottom = thin
    style.bottomBorderColor = black
    style.borderLeft = thin
    style.leftBorderColor = black
    style.borderTop = thin
    style.topBorderColor = black
    return style
}

private fun createGreyCenteredBoldArialWithBorderStyle(wb: Workbook, boldArial: Font): CellStyle {
    val style = createBorderedStyle(wb)
    style.alignment = HorizontalAlignment.CENTER
    style.setFont(boldArial)
    style.fillForegroundColor = IndexedColors.GREY_25_PERCENT.getIndex();
    style.fillPattern = FillPatternType.SOLID_FOREGROUND;
    return style
}

private fun createRedBoldArialWithBorderStyle(wb: Workbook, redBoldArial: Font): CellStyle {
    val style = createBorderedStyle(wb)
    style.setFont(redBoldArial)
    return style
}

private fun createRightAlignedDateFormatStyle(wb: Workbook): CellStyle {
    val style = wb.createCellStyle()
    style.alignment = HorizontalAlignment.RIGHT
    style.dataFormat = 14
    return style
}</code></pre><p>Por favor hay que tener en mente que los ejemplos de arriba son solo una parte pequeña de las posibilidades de <strong>CellStyle </strong>(estilo de celda). Si te gustaría ver una lista completa, por favor refiérate a la documentación <a href="https://poi.apache.org/apidocs/dev/org/apache/poi/ss/usermodel/CellStyle.html">aquí</a> (artículo en inglés).</p><h2 id="paso-4-crear-la-clase-reportservice"><strong>Paso 4: Crear la clase ReportService</strong></h2><p>Como siguiente paso vamos a implementar la clase <strong>ReportService</strong> responsable de crear los archivos <strong>.xlsx</strong> y <strong>.xls</strong> y retornarlos como instancias ByteArray:</p><pre><code class="language-kotlin">@Service
class ReportService(
    private val stylesGenerator: StylesGenerator
) {
    fun generateXlsxReport(): ByteArray {
        val wb = XSSFWorkbook()

        return generateReport(wb)
    }

    fun generateXlsReport(): ByteArray {
        val wb = HSSFWorkbook()

        return generateReport(wb)
    }
 }   </code></pre><p>Como puedes ver, la única diferencia entre la generación de estos dos formatos es el tipo de implementación del <strong>Workbook</strong> (libro de trabajo) que hemos utilizado. Para el formato .xlsx vamos a utilizar la clase <strong><strong>XSSFWorkbook</strong></strong>, y para el .xls utilizaremos <strong><strong>HSSFWorkbook</strong></strong>.</p><p>Vamos a añadir el resto del código a <strong>ReportService</strong>:</p><pre><code class="language-kotlin">private fun generateReport(wb: Workbook): ByteArray {
    val styles = stylesGenerator.prepareStyles(wb)
    val sheet: Sheet = wb.createSheet("Example sheet name")

    setColumnsWidth(sheet)

    createHeaderRow(sheet, styles)
    createStringsRow(sheet, styles)
    createDoublesRow(sheet, styles)
    createDatesRow(sheet, styles)

    val out = ByteArrayOutputStream()
    wb.write(out)

    out.close()
    wb.close()

    return out.toByteArray()
}

private fun setColumnsWidth(sheet: Sheet) {
    sheet.setColumnWidth(0, 256 * 20)

    for (columnIndex in 1 until 5) {
        sheet.setColumnWidth(columnIndex, 256 * 15)
    }
}

private fun createHeaderRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    val row = sheet.createRow(0)

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue("Column $columnNumber")
        cell.cellStyle = styles[CustomCellStyle.GREY_CENTERED_BOLD_ARIAL_WITH_BORDER]
    }
}

private fun createRowLabelCell(row: Row, styles: Map&lt;CustomCellStyle, CellStyle&gt;, label: String) {
    val rowLabel = row.createCell(0)
    rowLabel.setCellValue(label)
    rowLabel.cellStyle = styles[CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER]
}

private fun createStringsRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    val row = sheet.createRow(1)
    createRowLabelCell(row, styles, "Strings row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue("String $columnNumber")
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
    }
}

private fun createDoublesRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    val row = sheet.createRow(2)
    createRowLabelCell(row, styles, "Doubles row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue(BigDecimal("${columnNumber}.99").toDouble())
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
    }
}

private fun createDatesRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    val row = sheet.createRow(3)
    createRowLabelCell(row, styles, "Dates row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue((LocalDate.now()))
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED_DATE_FORMAT]
    }
}</code></pre><p>Como puedes ver, la primera cosa que la función <strong>generateReport</strong> hace, es que estiliza la inicialización. Pasamos la instancia <strong>Workbook</strong> al <strong>StylesGenerator</strong> (generador de estilos) y en retorno, obtenemos un mapa, el cual utilizaremos más tarde para obtener CellStyles (estilos de celda) apropiados.</p><p>Después de esto, crea una nueva hoja dentro de nuestro libro de trabajo y le entrega un nombre para este (el libro).</p><p>Después, invoca las funciones responsables de configurar el ancho de las columnas y operar nuestra hoja fila por fila.</p><p>Finalmente, escribe nuestro libro de trabajo a un ByteArrayOutputStream.</p><p>Tomemos un minuto y analicemos que hace exactamente cada función:</p><pre><code class="language-kotlin">private fun setColumnsWidth(sheet: Sheet) {
    sheet.setColumnWidth(0, 256 * 20)

    for (columnIndex in 1 until 5) {
        sheet.setColumnWidth(columnIndex, 256 * 15)
    }
}</code></pre><p>Como el nombre sugiere, <strong><strong>setColumnsWidth</strong></strong> (establecerAnchoColumnas) es responsable de configurar los anchos de las columnas en nuestra hoja. El primer parámetro pasado a &nbsp;<strong><strong>setColumnsWidth</strong></strong> indica el columnIndex (indiceColumna), y el segundo establece el ancho (en unidades de 1/256 de ancho de carácter).</p><pre><code class="language-kotlin">private fun createRowLabelCell(row: Row, styles: Map&lt;CustomCellStyle, CellStyle&gt;, label: String) {
    val rowLabel = row.createCell(0)
    rowLabel.setCellValue(label)
    rowLabel.cellStyle = styles[CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER]
}</code></pre><p>La función <strong><strong>createRowLabelCell</strong></strong> (crearEtiquetaFilaCelda) es responsable de añadir una celda en la primera columna de la fila entregada, paralelamente configura su valor a la etiqueta especificada y configura su estilo. He decidido añadir esta función para reducir un poco la redundacia del código.</p><p>Todas las funciones de abajo son bastante similares. Su propósito es crear una nueva fila, invocar la función <strong><strong>createRowLabelCell</strong></strong> (a excepción de <strong><strong>createHeaderRow</strong></strong><em>) </em>y añadir cinco columnas con información a nuestra hoja.</p><pre><code class="language-kotlin">private fun createHeaderRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    val row = sheet.createRow(0)

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue("Column $columnNumber")
        cell.cellStyle = styles[CustomCellStyle.GREY_CENTERED_BOLD_ARIAL_WITH_BORDER]
    }
}</code></pre><pre><code class="language-kotlin">private fun createStringsRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    val row = sheet.createRow(1)
    createRowLabelCell(row, styles, "Strings row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue("String $columnNumber")
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
    }
}</code></pre><pre><code class="language-kotlin">private fun createDoublesRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    val row = sheet.createRow(2)
    createRowLabelCell(row, styles, "Doubles row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue(BigDecimal("${columnNumber}.99").toDouble())
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
    }
}</code></pre><pre><code class="language-kotlin">private fun createDatesRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    val row = sheet.createRow(3)
    createRowLabelCell(row, styles, "Dates row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue((LocalDate.now()))
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED_DATE_FORMAT]
    }
}</code></pre><h2 id="paso-5-implementar-el-reportcontroller-rest">Paso 5: Implementar el ReportController REST</h2><p>Como último paso, vamos a implementar una clase llamada <strong><strong>ReportController</strong></strong>. Será responsable de manejar las peticiones POST entrantes a nuestros dos endpoints REST:</p><ul><li><em><em>/api/report/xlsx </em>- </em>crear un reporte en formato <em>.xlsx</em></li><li><em><em>/api/report/xls</em></em> - lo mismo de arriba, pero en formato <em>.xls</em></li></ul><pre><code class="language-kotlin">@RestController
@RequestMapping("/api/report")
class ReportController(
    private val reportService: ReportService
) {

    @PostMapping("/xlsx")
    fun generateXlsxReport(): ResponseEntity&lt;ByteArray&gt; {
        val report = reportService.generateXlsxReport()

        return createResponseEntity(report, "report.xlsx")
    }

    @PostMapping("/xls")
    fun generateXlsReport(): ResponseEntity&lt;ByteArray&gt; {
        val report = reportService.generateXlsReport()

        return createResponseEntity(report, "report.xls")
    }

    private fun createResponseEntity(
        report: ByteArray,
        fileName: String
    ): ResponseEntity&lt;ByteArray&gt; =
        ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"$fileName\"")
            .body(report)

}</code></pre><p>La parte más interesante del código de arriba es la función <strong><strong>createResponseEntity</strong></strong> (crearEntidadRespuesta), la cual establece el ByteArray (Arreglo de Bytes) entregado con su reporte generado como un cuerpo de respuesta.</p><p>Adicionalmente, establecemos el encabezado <strong><strong>Content</strong>-Type</strong> de la respuesta como <strong><strong>application/octet-stream</strong></strong>, y la disposición de contenido (Content-Disposition) como el archivo adjunto <strong><strong>attachment;</strong> filename = &lt;FILENAME&gt;</strong></p><h2 id="paso-6-testear-t-do-con-postman">Paso 6: Testear tódo con Postman</h2><p>Finalmente, podemos correr y probar nuetra aplicación Spring Boot, por ejemplo con el commando <code>gradlew</code>:</p><pre><code>./gradlew bootRun</code></pre><p>Por defecto, la aplicación Spring Boot estará corriendo en el puerto 8080, así que vamos a abrir <a href="https://www.postman.com/">Postman</a> (o cualquier otra herramienta), especificar la petición <strong>POST</strong> a <strong><strong>localhost:8080/api/report/xls</strong></strong> &nbsp;y presionar el botón <strong><strong>Send and Download</strong></strong> (enviar y descargar):</p><p>Si tódo estuvo bien , tendríamos que ver una ventana que nos permita guardar el archivo <strong>.xls</strong>.</p><p>Similarmente, vamos a probar el segundo endpoint:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/12/POST_xlsx.png" class="kg-image" alt="POST_xlsx" width="600" height="400" loading="lazy"></figure><p>Esta vez, la extensión del archivo tendría que ser <strong>.xlsx</strong>.</p><h2 id="resumen">Resumen</h2><p>¡Eso es todo por este artículo! Hemos cubierto el proceso de generar reportes Excel en una API REST de Spring Boot con Apache POI y Kotlin.</p><p>Si lo disfrutaste y te gustaría aprender otros temas a través de artículo similares, porfavor visita my blog <a href="https://codersee.com/"><strong><strong>Codersee</strong></strong></a><strong> </strong>(blog en inglés).</p><p>Y la última cosa: para el código fuente, o un proyecto completamente funcional, porfavor consulte <a href="https://github.com/codersee-blog/freecodecamp-spring-boot-kotlin-excel">este repositorio GitHub</a> (en inglés).</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
