22. Autenticación

Versión para imprimir.

A. Introducción

B. Diagrama entidad relación

Diagrama entidad relación

C. Diagrama relacional

Diagrama relacional

D. Diagrama de despliegue

Diagrama de despliegue

E. Hazlo funcionar

  1. Prueba el ejemplo en https://srvaut.rf.gd/.

  2. Descarga el archivo /src/srvaut.zip y descompáctalo.

  3. Crea tu proyecto en GitHub:

    1. Crea una cuenta de email, por ejemplo, pepito@google.com

    2. Crea una cuenta de GitHub usando el email anterior y selecciona el nombre de usuario unsando la parte inicial del correo electrónico, por ejemplo pepito.

    3. Crea un repositorio nuevo. En la página principal de GitHub cliquea 📘 New.

    4. En la página Create a new repository introduce los siguientes datos:

      • Proporciona el nombre de tu repositorio debajo de donde dice Repository name *.

      • Mantén la selección Public para que otros usuarios puedan ver tu proyecto.

      • Verifica la casilla Add a README file. En este archivo se muestra información sobre tu proyecto.

      • Cliquea License: None. y selecciona la licencia que consideres más adecuada para tu proyecto.

      • Cliquea Create repository.

  4. Importa el proyecto en GitHub:

    1. En la página principal de tu proyecto en GitHub, en la pestaña < > Code, cliquea < > Code y en la sección Branches y copia la dirección que está en HTTPS, debajo de Clone.

    2. En Visual Studio Code, usa el botón de la izquierda para Source Control.

      Imagen de Source Control
    3. Cliquea el botón Clone Repository.

    4. Pega la url que copiaste anteriormente hasta arriba, donde dice algo como Provide repository URL y presiona la teclea Intro.

    5. Selecciona la carpeta donde se guardará la carpeta del proyecto.

    6. Abre la carpeta del proyecto importado.

    7. Añade el contenido de la carpeta descompactada que contiene el código del ejemplo.

  5. Edita los archivos que desees.

  6. Haz clic derecho en index.html, selecciona PHP Server: serve project y se abre el navegador para que puedas probar localmente el ejemplo.

  7. Para depurar paso a paso haz lo siguiente:

    1. En el navegador, haz clic derecho en la página que deseas depurar y selecciona inspeccionar.

    2. Recarga la página, de preferencia haciendo clic derecho en el ícono de volver a cargar la página Ïmagen del ícono de recarga y seleccionando vaciar caché y volver a cargar de manera forzada (o algo parecido). Si no aparece un menú emergente, simplemente cliquea volver a cargar la página Ïmagen del ícono de recarga. Revisa que no aparezca ningún error ni en la pestañas Consola, ni en Red.

    3. Selecciona la pestaña Fuentes (o Sources si tu navegador está en Inglés).

    4. Selecciona el archivo donde vas a empezar a depurar.

    5. Haz clic en el número de la línea donde vas a empezar a depurar.

    6. En Visual Studio Code, abre el archivo de PHP donde vas a empezar a depurar.

    7. Haz clic en Run and Debug .

    8. Si no está configurada la depuración, haz clic en create a launch json file.

    9. Haz clic en la flechita RUN AND DEBUG, al lado de la cual debe decir Listen for Xdebug .

    10. Aparece un cuadro con los controles de depuración

    11. Selecciona otra vez el archivo de PHP y haz clic en el número de la línea donde vas a empezar a depurar.

    12. Regresa al navegador, recarga la página y empieza a usarla.

    13. Si se ejecuta alguna de las líneas de código seleccionadas, aparece resaltada en la pestaña de fuentes. Usa los controles de depuración para avanzar, como se muestra en este video.

  8. Sube el proyecto al hosting que elijas sin incluir el archivo .htaccess. En algunos casos puedes usar filezilla (https://filezilla-project.org/)

  9. En algunos host como InfinityFree, tienes que configurar el certificado SSL.

  10. En algunos host como InfinityFree, debes subir el archivo .htaccess cuando el certificado SSL se ha creado e instalado. Sirve para forzar el uso de https.

  11. Abre un navegador y prueba el proyecto en tu hosting.

  12. En el hosting InfinityFree, la primera ves que corres la página, puede marcar un mensaje de error, pero al recargar funciona correctamente. Puedes evitar este problema usando un dominio propio.

  13. Para subir el código a GitHub, en la sección de SOURCE CONTROL, en Message introduce un mensaje sobre los cambios que hiciste, por ejemplo index.html corregido, selecciona v y luego Commit & Push.

    Imagen de Commit & Push

F. Archivos

Haz clic en los triángulos para expandir las carpetas

G. index.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Autenticación</title>
10
11 <script type="module" src="lib/js/muestraError.js"></script>
12 <script type="module" src="./js/protege.js"></script>
13 <script type="module" src="./js/custom/mi-nav.js"></script>
14
15</head>
16
17<body onload="protege('srv/sesion-actual.php')
18 .then(sesion => nav.sesion = sesion)
19 .catch(muestraError)">
20
21 <mi-nav id="nav"></mi-nav>
22
23 <h1>Autenticación</h1>
24
25 <p>Bienvenid@.</p>
26
27</body>
28
29</html>

H. perfil.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Perfil</title>
10
11 <script type="module" src="lib/js/consumeJson.js"></script>
12 <script type="module" src="lib/js/muestraError.js"></script>
13 <script type="module" src="./js/protege.js"></script>
14 <script type="module" src="./js/custom/mi-nav.js"></script>
15
16</head>
17
18<body onload="protege('srv/sesion-actual.php')
19 .then(sesion => {
20 nav.sesion = sesion
21 const cue = sesion.cue
22 if (cue === '') {
23 login.hidden = false
24 outputCue.value = 'No ha iniciado sesión.'
25 outputRoles.value = ''
26 } else {
27 logout.hidden = false
28 outputCue.value = cue
29 const rolIds = sesion.rolIds
30 outputRoles.value = rolIds.size === 0
31 ? 'Sin roles'
32 : Array.from(rolIds).join(', ')
33 }
34 })
35 .catch(muestraError)">
36
37 <mi-nav id="nav"></mi-nav>
38
39 <h1>Perfil</h1>
40
41 <p>
42 <output id="outputCue">
43 <progress max="100">Cargando…</progress>
44 </output>
45 </p>
46
47 <p>
48 <output id="outputRoles">
49 <progress max="100">Cargando…</progress>
50 </output>
51 </p>
52
53 <p>
54
55 <a id="login" hidden href="login.html">Iniciar sesión</a>
56
57 <button type="button" id="logout" hidden
58 onclick="consumeJson('srv/logout.php')
59 .then(json => location.reload())
60 .catch(muestraError)">
61 Terminar sesión
62 </button>
63
64 </p>
65
66</body>
67
68</html>

I. login.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Iniciar sesión</title>
10
11 <script type="module" src="lib/js/submitForm.js"></script>
12 <script type="module" src="lib/js/muestraError.js"></script>
13 <script type="module" src="./js/protege.js"></script>
14
15</head>
16
17<body onload="protege('srv/sesion-actual.php')
18 .then(sesion => {
19 if (sesion.cue !== '') {
20 location.href = 'perfil.html'
21 }
22 })
23 .catch(muestraError)">
24
25 <form id="login" onsubmit="submitForm('srv/login.php', event)
26 .then(sesion => location.href = 'perfil.html')
27 .catch(muestraError)">
28
29 <h1>Iniciar Sesión</h1>
30
31 <p><a href="perfil.html">Cancelar</a></p>
32
33 <p>
34 <label>
35 Cue
36 <input name="cue">
37 </label>
38 </p>
39
40 <p>
41 <label>
42 Match
43 <input type="password" name="match">
44 </label>
45 </p>
46
47 <p><button type="submit">Iniciar sesión</button></p>
48
49 </form>
50
51</body>
52
53</html>

J. admin.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Solo Administradores</title>
10
11 <script type="module" src="lib/js/consumeJson.js"></script>
12 <script type="module" src="lib/js/muestraError.js"></script>
13 <script type="module" src="./js/ROL_ID_ADMINISTRADOR.js"></script>
14 <script type="module" src="./js/protege.js"></script>
15 <script type="module" src="./js/custom/mi-nav.js"></script>
16
17</head>
18
19<body
20 onload="protege('srv/sesion-actual.php', [ROL_ID_ADMINISTRADOR], 'index.html')
21 .then(sesion => {
22 nav.sesion = sesion
23 main.hidden = false
24 })
25 .catch(muestraError)">
26
27 <mi-nav id="nav"></mi-nav>
28
29 <main id="main" hidden>
30
31 <h1>Solo Administradores</h1>
32
33 <p>Hola.</p>
34
35 <p>
36 <button type="button" onclick="consumeJson('srv/saludo-cliente.php')
37 .then(saludo => alert(saludo.body))
38 .catch(muestraError)">
39 Ejecuta servicio
40 </button>
41 </p>
42
43 </main>
44
45</body>
46
47</html>

K. cliente.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Solo Clientes</title>
10
11 <script type="module" src="lib/js/consumeJson.js"></script>
12 <script type="module" src="lib/js/muestraError.js"></script>
13 <script type="module" src="./js/ROL_ID_CLIENTE.js"></script>
14 <script type="module" src="./js/protege.js"></script>
15 <script type="module" src="./js/custom/mi-nav.js"></script>
16
17</head>
18
19<body onload="protege('srv/sesion-actual.php', [ROL_ID_CLIENTE], 'index.html')
20 .then(sesion => {
21 nav.sesion = sesion
22 main.hidden = false
23 })
24 .catch(muestraError)">
25
26 <mi-nav id="nav"></mi-nav>
27
28 <main id="main" hidden>
29
30 <h1>Solo Clientes</h1>
31
32 <p>Hola.</p>
33
34 <p>
35 <button type="button" onclick="consumeJson('srv/saludo-cliente.php')
36 .then(saludo => alert(saludo.body))
37 .catch(muestraError)">
38 Ejecuta servicio
39 </button>
40 </p>
41
42 </main>
43
44</body>
45
46</html>

L. Carpeta « js »

Versión para imprimir.

A. js / CUE.js

1export const CUE = "cue"

B. js / protege.js

1import { consumeJson } from "../lib/js/consumeJson.js"
2import { exportaAHtml } from "../lib/js/exportaAHtml.js"
3import { Sesion } from "./Sesion.js"
4
5/**
6 * @param {string} servicio
7 * @param {string[]} [rolIdsPermitidos]
8 * @param {string} [urlDeProtección]
9 */
10export async function protege(servicio, rolIdsPermitidos, urlDeProtección) {
11 const respuesta = await consumeJson(servicio)
12 const sesion = new Sesion(respuesta.body)
13 if (rolIdsPermitidos === undefined) {
14 return sesion
15 } else {
16 const rolIds = sesion.rolIds
17 for (const rolId of rolIdsPermitidos) {
18 if (rolIds.has(rolId)) {
19 return sesion
20 }
21 }
22 if (urlDeProtección !== undefined) {
23 location.href = urlDeProtección
24 }
25 throw new Error("No autorizado.")
26 }
27}
28
29exportaAHtml(protege)

C. js / ROL_IDS.js

1export const ROL_IDS = "rolIds"

D. js / ROL_ID_ADMINISTRADOR.js

1export const ROL_ID_ADMINISTRADOR = "Administrador"
2
3// Permite que los eventos de html usen la constante.
4window["ROL_ID_ADMINISTRADOR"] = ROL_ID_ADMINISTRADOR

E. js / ROL_ID_CLIENTE.js

1export const ROL_ID_CLIENTE = "Cliente"
2
3// Permite que los eventos de html usen la constante.
4window["ROL_ID_CLIENTE"] = ROL_ID_CLIENTE

F. js / Sesion.js

1import { exportaAHtml } from "../lib/js/exportaAHtml.js"
2import { CUE } from "./CUE.js"
3import { ROL_IDS } from "./ROL_IDS.js"
4
5export class Sesion {
6
7 /**
8 * @param { any } objeto
9 */
10 constructor(objeto) {
11
12 /**
13 * @readonly
14 */
15 this.cue = objeto[CUE]
16 if (typeof this.cue !== "string")
17 throw new Error("cue debe ser string.")
18
19 /**
20 * @readonly
21 */
22 const rolIds = objeto[ROL_IDS]
23 if (!Array.isArray(rolIds))
24 throw new Error("rolIds debe ser arreglo.")
25 /**
26 * @readonly
27 */
28 this.rolIds = new Set(rolIds)
29
30 }
31
32}
33
34exportaAHtml(Sesion)

G. Carpeta « js / custom »

1. js / custom / mi-nav.js

1import { htmlentities } from "../../lib/js/htmlentities.js"
2import { Sesion } from "../Sesion.js"
3import { ROL_ID_ADMINISTRADOR } from "../ROL_ID_ADMINISTRADOR.js"
4import { ROL_ID_CLIENTE } from "../ROL_ID_CLIENTE.js"
5
6export class MiNav extends HTMLElement {
7
8 connectedCallback() {
9
10 this.style.display = "block"
11
12 this.innerHTML = /* html */
13 `<nav>
14 <ul style="display: flex;
15 flex-wrap: wrap;
16 padding:0;
17 gap: 0.5em;
18 list-style-type: none">
19 <li><progress max="100">Cargando…</progress></li>
20 </ul>
21 </nav>`
22
23 }
24
25 /**
26 * @param {Sesion} sesion
27 */
28 set sesion(sesion) {
29 const cue = sesion.cue
30 const rolIds = sesion.rolIds
31 let innerHTML = /* html */ `<li><a href="index.html">Inicio</a></li>`
32 innerHTML += this.hipervinculosAdmin(rolIds)
33 innerHTML += this.hipervinculosCliente(rolIds)
34 const cueHtml = htmlentities(cue)
35 if (cue !== "") {
36 innerHTML += /* html */ `<li>${cueHtml}</li>`
37 }
38 innerHTML += /* html */ `<li><a href="perfil.html">Perfil</a></li>`
39 const ul = this.querySelector("ul")
40 if (ul !== null) {
41 ul.innerHTML = innerHTML
42 }
43 }
44
45 /**
46 * @param {Set<string>} rolIds
47 */
48 hipervinculosAdmin(rolIds) {
49 return rolIds.has(ROL_ID_ADMINISTRADOR) ?
50 /* html */ `<li><a href="admin.html">Para administradores</a></li>`
51 : ""
52 }
53
54 /**
55 * @param {Set<string>} rolIds
56 */
57 hipervinculosCliente(rolIds) {
58 return rolIds.has(ROL_ID_CLIENTE) ?
59 /* html */ `<li><a href="cliente.html">Para clientes</a></li>`
60 : ""
61 }
62}
63
64customElements.define("mi-nav", MiNav)

M. Carpeta « srv »

Versión para imprimir.

A. srv / Bd.php

1<?php
2
3require_once __DIR__ . "/../lib/php/selectFirst.php";
4require_once __DIR__ . "/../lib/php/insert.php";
5require_once __DIR__ . "/../lib/php/insertBridges.php";
6require_once __DIR__ . "/../lib/php/insert.php";
7require_once __DIR__ . "/TABLA_USUARIO.php";
8require_once __DIR__ . "/TABLA_ROL.php";
9require_once __DIR__ . "/TABLA_USU_ROL.php";
10require_once __DIR__ . "/ROL_ID_CLIENTE.php";
11require_once __DIR__ . "/ROL_ID_ADMINISTRADOR.php";
12
13class Bd
14{
15
16 private static ?PDO $pdo = null;
17
18 static function pdo(): PDO
19 {
20 if (self::$pdo === null) {
21
22 self::$pdo = new PDO(
23 // cadena de conexión
24 "sqlite:srvaut.db",
25 // usuario
26 null,
27 // contraseña
28 null,
29 // Opciones: pdos no persistentes y lanza excepciones.
30 [PDO::ATTR_PERSISTENT => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
31 );
32
33 self::$pdo->exec(
34 'CREATE TABLE IF NOT EXISTS USUARIO (
35 USU_ID INTEGER,
36 USU_CUE TEXT NOT NULL,
37 USU_MATCH TEXT NOT NULL,
38 CONSTRAINT USU_PK
39 PRIMARY KEY(USU_ID),
40 CONSTRAINT USU_CUE_UNQ
41 UNIQUE(USU_CUE),
42 CONSTRAINT USU_CUE_NV
43 CHECK(LENGTH(USU_CUE) > 0)
44 )'
45 );
46 self::$pdo->exec(
47 'CREATE TABLE IF NOT EXISTS ROL (
48 ROL_ID TEXT NOT NULL,
49 ROL_DESCRIPCION TEXT NOT NULL,
50 CONSTRAINT ROL_PK
51 PRIMARY KEY(ROL_ID),
52 CONSTRAINT ROL_ID_NV
53 CHECK(LENGTH(ROL_ID) > 0),
54 CONSTRAINT ROL_DESCR_UNQ
55 UNIQUE(ROL_DESCRIPCION),
56 CONSTRAINT ROL_DESCR_NV
57 CHECK(LENGTH(ROL_DESCRIPCION) > 0)
58 )'
59 );
60 self::$pdo->exec(
61 'CREATE TABLE IF NOT EXISTS USU_ROL (
62 USU_ID INTEGER NOT NULL,
63 ROL_ID TEXT NOT NULL,
64 CONSTRAINT USU_ROL_PK
65 PRIMARY KEY(USU_ID, ROL_ID),
66 CONSTRAINT USU_ROL_USU_FK
67 FOREIGN KEY (USU_ID) REFERENCES USUARIO(USU_ID),
68 CONSTRAINT USU_ROL_ROL_FK
69 FOREIGN KEY (ROL_ID) REFERENCES ROL(ROL_ID)
70 )'
71 );
72
73 if (selectFirst(
74 pdo: self::$pdo,
75 from: ROL,
76 where: [ROL_ID => ROL_ID_ADMINISTRADOR]
77 ) === false) {
78 insert(
79 pdo: self::$pdo,
80 into: ROL,
81 values: [
82 ROL_ID => ROL_ID_ADMINISTRADOR,
83 ROL_DESCRIPCION => "Administra el sistema."
84 ]
85 );
86 }
87
88 if (selectFirst(self::$pdo, ROL, [ROL_ID => ROL_ID_CLIENTE]) === false) {
89 insert(
90 pdo: self::$pdo,
91 into: ROL,
92 values: [
93 ROL_ID => ROL_ID_CLIENTE,
94 ROL_DESCRIPCION => "Realiza compras."
95 ]
96 );
97 }
98 }
99
100 if (selectFirst(self::$pdo, USUARIO, [USU_CUE => "pepito"]) === false) {
101 insert(
102 pdo: self::$pdo,
103 into: USUARIO,
104 values: [
105 USU_CUE => "pepito",
106 USU_MATCH => password_hash("cuentos", PASSWORD_DEFAULT)
107 ]
108 );
109 $usuId = self::$pdo->lastInsertId();
110 insertBridges(
111 pdo: self::$pdo,
112 into: USU_ROL,
113 valuesDePadre: [USU_ID => $usuId],
114 valueDeHijos: [ROL_ID => [ROL_ID_CLIENTE]]
115 );
116 }
117
118 if (selectFirst(self::$pdo, USUARIO, [USU_CUE => "susana"]) === false) {
119 insert(
120 pdo: self::$pdo,
121 into: USUARIO,
122 values: [
123 USU_CUE => "susana",
124 USU_MATCH => password_hash("alegria", PASSWORD_DEFAULT)
125 ]
126 );
127 $usuId = self::$pdo->lastInsertId();
128 insertBridges(
129 pdo: self::$pdo,
130 into: USU_ROL,
131 valuesDePadre: [USU_ID => $usuId],
132 valueDeHijos: [ROL_ID => [ROL_ID_ADMINISTRADOR]]
133 );
134 }
135
136 if (selectFirst(self::$pdo, USUARIO, [USU_CUE => "bebe"]) === false) {
137 insert(
138 pdo: self::$pdo,
139 into: USUARIO,
140 values: [
141 USU_CUE => "bebe",
142 USU_MATCH => password_hash("saurio", PASSWORD_DEFAULT)
143 ]
144 );
145 $usuId = self::$pdo->lastInsertId();
146 insertBridges(
147 pdo: self::$pdo,
148 into: USU_ROL,
149 valuesDePadre: [USU_ID => $usuId],
150 valueDeHijos: [ROL_ID => [ROL_ID_ADMINISTRADOR, ROL_ID_CLIENTE]]
151 );
152 }
153
154 return self::$pdo;
155 }
156}
157

B. srv / CUE.php

1<?php
2
3const CUE = "cue";

C. srv / login.php

1<?php
2
3require_once __DIR__ . "/../lib/php/BAD_REQUEST.php";
4require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
5require_once __DIR__ . "/../lib/php/recuperaTexto.php";
6require_once __DIR__ . "/../lib/php/validaCue.php";
7require_once __DIR__ . "/../lib/php/ProblemDetails.php";
8require_once __DIR__ . "/../lib/php/selectFirst.php";
9require_once __DIR__ . "/../lib/php/fetchAll.php";
10require_once __DIR__ . "/../lib/php/devuelveJson.php";
11require_once __DIR__ . "/CUE.php";
12require_once __DIR__ . "/ROL_IDS.php";
13require_once __DIR__ . "/Bd.php";
14require_once __DIR__ . "/TABLA_USUARIO.php";
15require_once __DIR__ . "/protege.php";
16
17ejecutaServicio(function () {
18
19 $sesion = protege();
20
21 if ($sesion->cue !== "")
22 throw new ProblemDetails(
23 status: NO_AUTORIZADO,
24 type: "/error/sesioniniciada.html",
25 title: "Sesión iniciada.",
26 detail: "La sesión ya está iniciada.",
27 );
28
29 $cue = recuperaTexto("cue");
30 $match = recuperaTexto("match");
31
32 $cue = validaCue($cue);
33
34 if ($match === false)
35 throw new ProblemDetails(
36 status: BAD_REQUEST,
37 title: "Falta el match.",
38 type: "/error/faltamatch.html",
39 detail: "La solicitud no tiene el valor de match.",
40 );
41
42 if ($match === "")
43 throw new ProblemDetails(
44 status: BAD_REQUEST,
45 title: "Match en blanco.",
46 type: "/error/matchenblanco.html",
47 detail: "Pon texto en el campo match.",
48 );
49
50 $pdo = Bd::pdo();
51
52 $usuario =
53 selectFirst(pdo: $pdo, from: USUARIO, where: [USU_CUE => $cue]);
54
55 if ($usuario === false || !password_verify($match, $usuario[USU_MATCH]))
56 throw new ProblemDetails(
57 status: BAD_REQUEST,
58 type: "/error/datosincorrectos.html",
59 title: "Datos incorrectos.",
60 detail: "El cue y/o el match proporcionados son incorrectos.",
61 );
62
63 $rolIds = fetchAll(
64 $pdo->query(
65 "SELECT ROL_ID
66 FROM USU_ROL
67 WHERE USU_ID = :USU_ID
68 ORDER BY USU_ID"
69 ),
70 [":USU_ID" => $usuario[USU_ID]],
71 PDO::FETCH_COLUMN
72 );
73
74 $_SESSION[CUE] = $cue;
75 $_SESSION[ROL_IDS] = $rolIds;
76 devuelveJson([
77 CUE => $cue,
78 ROL_IDS => $rolIds
79 ]);
80});
81

D. srv / logout.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/../lib/php/devuelveJson.php";
5require_once __DIR__ . "/CUE.php";
6require_once __DIR__ . "/ROL_IDS.php";
7
8ejecutaServicio(function () {
9
10 session_start();
11
12 if (isset($_SESSION[CUE])) {
13 unset($_SESSION[CUE]);
14 }
15 if (isset($_SESSION[ROL_IDS])) {
16 unset($_SESSION[ROL_IDS]);
17 }
18
19 session_destroy();
20
21 devuelveJson([]);
22});
23

E. srv / protege.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ProblemDetails.php";
4require_once __DIR__ . "/CUE.php";
5require_once __DIR__ . "/ROL_IDS.php";
6require_once __DIR__ . "/ROL_ID_CLIENTE.php";
7require_once __DIR__ . "/Sesion.php";
8
9const NO_AUTORIZADO = 401;
10
11function protege(?array $rolIdsPermitidos = null)
12{
13
14 session_start();
15
16 $cue = isset($_SESSION[CUE]) ? $_SESSION[CUE] : "";
17 $rolIds = isset($_SESSION[ROL_IDS]) ? $_SESSION[ROL_IDS] : [];
18 $sesion = new Sesion($cue, $rolIds);
19
20 if ($rolIdsPermitidos === null) {
21
22 return $sesion;
23 } else {
24
25 foreach ($rolIdsPermitidos as $rolId) {
26 if (array_search($rolId, $rolIds, true) !== false) {
27 return $sesion;
28 }
29 }
30
31 throw new ProblemDetails(
32 status: NO_AUTORIZADO,
33 type: "/error/noautorizado.html",
34 title: "No autorizado.",
35 detail: "No está autorizado para usar este recurso.",
36 );
37 }
38}
39

F. srv / ROL_IDS.php

1<?php
2
3const ROL_IDS = "rolIds";

G. srv / ROL_ID_ADMINISTRADOR.php

1<?php
2
3const ROL_ID_ADMINISTRADOR = "Administrador";
4

H. srv / ROL_ID_CLIENTE.php

1<?php
2
3const ROL_ID_CLIENTE = "Cliente";

I. srv / saludo-cliente.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/../lib/php/devuelveJson.php";
5require_once __DIR__ . "/ROL_ID_CLIENTE.php";
6require_once __DIR__ . "/protege.php";
7
8ejecutaServicio(function () {
9 $sesion = protege([ROL_ID_CLIENTE]);
10 devuelveJson("Hola " . $sesion->cue);
11});
12

J. srv / sesion-actual.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/../lib/php/devuelveJson.php";
5require_once __DIR__ . "/protege.php";
6
7ejecutaServicio(function () {
8 devuelveJson(protege());
9});
10

K. srv / Sesion.php

1<?php
2
3class Sesion
4{
5
6 public string $cue;
7 public array $rolIds;
8
9 public function __construct(string $cue, array $rolIds)
10 {
11 $this->cue = $cue;
12 $this->rolIds = $rolIds;
13 }
14}
15

L. srv / TABLA_ROL.php

1<?php
2
3const ROL = "ROL";
4const ROL_ID = "ROL_ID";
5const ROL_DESCRIPCION = "ROL_DESCRIPCION";
6

M. srv / TABLA_USUARIO.php

1<?php
2
3const USUARIO = "USUARIO";
4const USU_ID = "USU_ID";
5const USU_CUE = "USU_CUE";
6const USU_MATCH = "USU_MATCH";
7

N. srv / TABLA_USU_ROL.php

1<?php
2
3const USU_ROL = "USU_ROL";
4

N. Carpeta « lib »

Versión para imprimir.

A. Carpeta « lib / js »

1. lib / js / consumeJson.js

1import { exportaAHtml } from "./exportaAHtml.js"
2import { ProblemDetails } from "./ProblemDetails.js"
3
4/**
5 * Espera a que la promesa de un fetch termine. Si
6 * hay error, lanza una excepción. Si no hay error,
7 * interpreta la respuesta del servidor como JSON y
8 * la convierte en una literal de objeto.
9 *
10 * @param { string | Promise<Response> } servicio
11 */
12export async function consumeJson(servicio) {
13
14 if (typeof servicio === "string") {
15 servicio = fetch(servicio, {
16 headers: { "Accept": "application/json, application/problem+json" }
17 })
18 } else if (!(servicio instanceof Promise)) {
19 throw new Error("Servicio de tipo incorrecto.")
20 }
21
22 const respuesta = await servicio
23
24 const headers = respuesta.headers
25
26 if (respuesta.ok) {
27 // Aparentemente el servidor tuvo éxito.
28
29 if (respuesta.status === 204) {
30 // No contiene texto de respuesta.
31
32 return { headers, body: {} }
33
34 } else {
35
36 const texto = await respuesta.text()
37
38 try {
39
40 return { headers, body: JSON.parse(texto) }
41
42 } catch (error) {
43
44 // El contenido no es JSON. Probablemente sea texto de un error.
45 throw new ProblemDetails(respuesta.status, headers, texto,
46 "/error/errorinterno.html")
47
48 }
49
50 }
51
52 } else {
53 // Hay un error.
54
55 const texto = await respuesta.text()
56
57 if (texto === "") {
58
59 // No hay texto. Se usa el texto predeterminado.
60 throw new ProblemDetails(respuesta.status, headers, respuesta.statusText)
61
62 } else {
63 // Debiera se un ProblemDetails en JSON.
64
65 try {
66
67 const { title, type, detail } = JSON.parse(texto)
68
69 throw new ProblemDetails(respuesta.status, headers,
70 typeof title === "string" ? title : respuesta.statusText,
71 typeof type === "string" ? type : undefined,
72 typeof detail === "string" ? detail : undefined)
73
74 } catch (error) {
75
76 if (error instanceof ProblemDetails) {
77 // El error si era un ProblemDetails
78
79 throw error
80
81 } else {
82
83 throw new ProblemDetails(respuesta.status, headers, respuesta.statusText,
84 undefined, texto)
85
86 }
87
88 }
89
90 }
91
92 }
93
94}
95
96exportaAHtml(consumeJson)

2. lib / js / exportaAHtml.js

1/**
2 * Permite que los eventos de html usen la función.
3 * @param {function} functionInstance
4 */
5export function exportaAHtml(functionInstance) {
6 window[nombreDeFuncionParaHtml(functionInstance)] = functionInstance
7}
8
9/**
10 * @param {function} valor
11 */
12export function nombreDeFuncionParaHtml(valor) {
13 const names = valor.name.split(/\s+/g)
14 return names[names.length - 1]
15}

3. lib / js / htmlentities.js

1/**
2 * Codifica un texto para que cambie los caracteres
3 * especiales y no se pueda interpretar como
4 * etiiqueta HTML. Esta técnica evita la inyección
5 * de código.
6 * @param { string } texto
7*/
8export function htmlentities(texto) {
9 return texto.replace(/[<>"']/g, textoDetectado => {
10 switch (textoDetectado) {
11 case "<": return "<"
12 case ">": return ">"
13 case '"': return """
14 case "'": return "'"
15 default: return textoDetectado
16 }
17 })
18}
19

4. lib / js / muestraError.js

1import { exportaAHtml } from "./exportaAHtml.js"
2import { ProblemDetails } from "./ProblemDetails.js"
3
4/**
5 * Muestra un error en la consola y en un cuadro de
6 * alerta el mensaje de una excepción.
7 * @param { ProblemDetails | Error | null } error descripción del error.
8 */
9export function muestraError(error) {
10
11 if (error === null) {
12
13 console.error("Error")
14 alert("Error")
15
16 } else if (error instanceof ProblemDetails) {
17
18 let mensaje = error.title
19 if (error.detail) {
20 mensaje += `\n\n${error.detail}`
21 }
22 mensaje += `\n\nCódigo: ${error.status}`
23 if (error.type) {
24 mensaje += ` ${error.type}`
25 }
26
27 console.error(mensaje)
28 console.error(error)
29 console.error("Headers:")
30 error.headers.forEach((valor, llave) => console.error(llave, "=", valor))
31 alert(mensaje)
32
33 } else {
34
35 console.error(error)
36 alert(error.message)
37
38 }
39
40}
41
42exportaAHtml(muestraError)

5. lib / js / muestraObjeto.js

1import { exportaAHtml } from "./exportaAHtml.js"
2
3/**
4 * @param { Document | HTMLElement } raizHtml
5 * @param { any } objeto
6 */
7export function muestraObjeto(raizHtml, objeto) {
8
9 for (const [nombre, definiciones] of Object.entries(objeto)) {
10
11 if (Array.isArray(definiciones)) {
12
13 muestraArray(raizHtml, nombre, definiciones)
14
15 } else if (definiciones !== undefined && definiciones !== null) {
16
17 const elementoHtml = buscaElementoHtml(raizHtml, nombre)
18
19 if (elementoHtml instanceof HTMLInputElement) {
20
21 muestraInput(raizHtml, elementoHtml, definiciones)
22
23 } else if (elementoHtml !== null) {
24
25 for (const [atributo, valor] of Object.entries(definiciones)) {
26 if (atributo in elementoHtml) {
27 elementoHtml[atributo] = valor
28 }
29 }
30
31 }
32
33 }
34
35 }
36
37}
38exportaAHtml(muestraObjeto)
39
40/**
41 * @param { Document | HTMLElement } raizHtml
42 * @param { string } nombre
43 */
44export function buscaElementoHtml(raizHtml, nombre) {
45 return raizHtml.querySelector(
46 `#${nombre},[name="${nombre}"],[data-name="${nombre}"]`)
47}
48
49/**
50 * @param { Document | HTMLElement } raizHtml
51 * @param { string } propiedad
52 * @param {any[]} valores
53 */
54function muestraArray(raizHtml, propiedad, valores) {
55
56 const conjunto = new Set(valores)
57 const elementos =
58 raizHtml.querySelectorAll(`[name="${propiedad}"],[data-name="${propiedad}"]`)
59
60 if (elementos.length === 1) {
61 const elemento = elementos[0]
62
63 if (elemento instanceof HTMLSelectElement) {
64 const options = elemento.options
65 for (let i = 0, len = options.length; i < len; i++) {
66 const option = options[i]
67 option.selected = conjunto.has(option.value)
68 }
69 return
70 }
71
72 }
73
74 for (let i = 0, len = elementos.length; i < len; i++) {
75 const elemento = elementos[i]
76 if (elemento instanceof HTMLInputElement) {
77 elemento.checked = conjunto.has(elemento.value)
78 }
79 }
80
81}
82
83/**
84 * @param { Document | HTMLElement } raizHtml
85 * @param { HTMLInputElement } input
86 * @param { any } definiciones
87 */
88function muestraInput(raizHtml, input, definiciones) {
89
90 for (const [atributo, valor] of Object.entries(definiciones)) {
91
92 if (atributo == "data-file") {
93
94 const img = getImgParaElementoHtml(raizHtml, input)
95 if (img !== null) {
96 input.dataset.file = valor
97 input.value = ""
98 if (valor === "") {
99 img.src = ""
100 img.hidden = true
101 } else {
102 img.src = valor
103 img.hidden = false
104 }
105 }
106
107 } else if (atributo in input) {
108
109 input[atributo] = valor
110
111 }
112 }
113
114}
115
116/**
117 * @param { Document | HTMLElement } raizHtml
118 * @param { HTMLElement } elementoHtml
119 */
120export function getImgParaElementoHtml(raizHtml, elementoHtml) {
121 const imgId = elementoHtml.getAttribute("data-img")
122 if (imgId === null) {
123 return null
124 } else {
125 const input = buscaElementoHtml(raizHtml, imgId)
126 if (input instanceof HTMLImageElement) {
127 return input
128 } else {
129 return null
130 }
131 }
132}

6. lib / js / ProblemDetails.js

1/**
2 * Detalle de los errores devueltos por un servicio.
3 */
4export class ProblemDetails extends Error {
5
6 /**
7 * @param {number} status
8 * @param {Headers} headers
9 * @param {string} title
10 * @param {string} [type]
11 * @param {string} [detail]
12 */
13 constructor(status, headers, title, type, detail) {
14 super(title)
15 /**
16 * @readonly
17 */
18 this.status = status
19 /**
20 * @readonly
21 */
22 this.headers = headers
23 /**
24 * @readonly
25 */
26 this.type = type
27 /**
28 * @readonly
29 */
30 this.detail = detail
31 /**
32 * @readonly
33 */
34 this.title = title
35 }
36
37}

7. lib / js / submitForm.js

1import { consumeJson } from "./consumeJson.js"
2import { exportaAHtml } from "./exportaAHtml.js"
3
4/**
5 * Envía los datos de la forma a la url usando la codificación
6 * multipart/form-data.
7 * @param {string} url
8 * @param {Event} event
9 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
10 * | "CONNECT" | "HEAD" } metodoHttp
11 */
12export function submitForm(url, event, metodoHttp = "POST") {
13
14 event.preventDefault()
15
16 const form = event.target
17
18 if (!(form instanceof HTMLFormElement))
19 throw new Error("event.target no es un elemento de tipo form.")
20
21 return consumeJson(fetch(url, {
22 method: metodoHttp,
23 headers: { "Accept": "application/json, application/problem+json" },
24 body: new FormData(form)
25 }))
26
27}
28
29exportaAHtml(submitForm)

B. Carpeta « lib / php »

1. lib / php / BAD_REQUEST.php

1<?php
2
3const BAD_REQUEST = 400;
4

2. lib / php / calculaArregloDeParametros.php

1<?php
2
3function calculaArregloDeParametros(array $arreglo)
4{
5 $parametros = [];
6 foreach ($arreglo as $llave => $valor) {
7 $parametros[":$llave"] = $valor;
8 }
9 return $parametros;
10}
11

3. lib / php / calculaSqlDeAsignaciones.php

1<?php
2
3function calculaSqlDeAsignaciones(string $separador, array $arreglo)
4{
5 $primerElemento = true;
6 $sqlDeAsignacion = "";
7 foreach ($arreglo as $llave => $valor) {
8 $sqlDeAsignacion .=
9 ($primerElemento === true ? "" : $separador) . "$llave=:$llave";
10 $primerElemento = false;
11 }
12 return $sqlDeAsignacion;
13}
14

4. lib / php / calculaSqlDeCamposDeInsert.php

1<?php
2
3function calculaSqlDeCamposDeInsert(array $values)
4{
5 $primerCampo = true;
6 $sqlDeCampos = "";
7 foreach ($values as $nombreDeValue => $valorDeValue) {
8 $sqlDeCampos .= ($primerCampo === true ? "" : ",") . "$nombreDeValue";
9 $primerCampo = false;
10 }
11 return $sqlDeCampos;
12}
13

5. lib / php / calculaSqlDeValues.php

1<?php
2
3function calculaSqlDeValues(array $values)
4{
5 $primerValue = true;
6 $sqlDeValues = "";
7 foreach ($values as $nombreDeValue => $valorDeValue) {
8 $sqlDeValues .= ($primerValue === true ? "" : ",") . ":$nombreDeValue";
9 $primerValue = false;
10 }
11 return $sqlDeValues;
12}
13

6. lib / php / devuelveErrorInterno.php

1<?php
2
3require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4require_once __DIR__ . "/devuelveProblemDetails.php";
5require_once __DIR__ . "/devuelveProblemDetails.php";
6
7function devuelveErrorInterno(Throwable $error)
8{
9 devuelveProblemDetails(new ProblemDetails(
10 status: INTERNAL_SERVER_ERROR,
11 title: $error->getMessage(),
12 type: "/error/errorinterno.html"
13 ));
14}
15

7. lib / php / devuelveJson.php

1<?php
2
3require_once __DIR__ . "/devuelveResultadoNoJson.php";
4
5function devuelveJson($resultado)
6{
7
8 $json = json_encode($resultado);
9
10 if ($json === false) {
11
12 devuelveResultadoNoJson();
13 } else {
14
15 http_response_code(200);
16 header("Content-Type: application/json");
17 echo $json;
18 }
19}
20

8. lib / php / devuelveProblemDetails.php

1<?php
2
3require_once __DIR__ . "/devuelveResultadoNoJson.php";
4require_once __DIR__ . "/ProblemDetails.php";
5
6function devuelveProblemDetails(ProblemDetails $details)
7{
8
9 $body = ["title" => $details->title];
10 if ($details->type !== null) {
11 $body["type"] = $details->type;
12 }
13 if ($details->detail !== null) {
14 $body["detail"] = $details->detail;
15 }
16
17 $json = json_encode($body);
18
19 if ($json === false) {
20
21 devuelveResultadoNoJson();
22 } else {
23
24 http_response_code($details->status);
25 header("Content-Type: application/problem+json");
26 echo $json;
27 }
28}
29

9. lib / php / devuelveResultadoNoJson.php

1<?php
2
3require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
5function devuelveResultadoNoJson()
6{
7
8 http_response_code(INTERNAL_SERVER_ERROR);
9 header("Content-Type: application/problem+json");
10 echo '{' .
11 '"title": "El resultado no puede representarse como JSON."' .
12 '"type": "/error/resultadonojson.html"' .
13 '}';
14}
15

10. lib / php / ejecutaServicio.php

1<?php
2
3require_once __DIR__ . "/ProblemDetails.php";
4require_once __DIR__ . "/devuelveProblemDetails.php";
5require_once __DIR__ . "/devuelveErrorInterno.php";
6
7function ejecutaServicio(callable $codigo)
8{
9 try {
10 $codigo();
11 } catch (ProblemDetails $details) {
12 devuelveProblemDetails($details);
13 } catch (Throwable $error) {
14 devuelveErrorInterno($error);
15 }
16}
17

11. lib / php / fetch.php

1<?php
2
3function fetch(
4 PDOStatement|false $statement,
5 $parametros = [],
6 int $mode = PDO::FETCH_ASSOC,
7 $opcional = null
8) {
9
10 if ($statement === false) {
11
12 return false;
13 } else {
14
15 if (sizeof($parametros) > 0) {
16 $statement->execute($parametros);
17 }
18
19 if ($opcional === null) {
20 return $statement->fetch($mode);
21 } else {
22 $statement->setFetchMode($mode, $opcional);
23 return $statement->fetch();
24 }
25 }
26}
27

12. lib / php / fetchAll.php

1<?php
2
3function fetchAll(
4 PDOStatement|false $statement,
5 $parametros = [],
6 int $mode = PDO::FETCH_ASSOC,
7 $opcional = null
8): array {
9
10 if ($statement === false) {
11
12 return [];
13 } else {
14
15 if (sizeof($parametros) > 0) {
16 $statement->execute($parametros);
17 }
18
19 $resultado = $opcional === null
20 ? $statement->fetchAll($mode)
21 : $statement->fetchAll($mode, $opcional);
22
23 if ($resultado === false) {
24 return [];
25 } else {
26 return $resultado;
27 }
28 }
29}
30

13. lib / php / insert.php

1<?php
2
3require_once __DIR__ . "/calculaSqlDeCamposDeInsert.php";
4require_once __DIR__ . "/calculaSqlDeValues.php";
5require_once __DIR__ . "/calculaArregloDeParametros.php";
6
7function insert(PDO $pdo, string $into, array $values)
8{
9 $sqlDeCampos = calculaSqlDeCamposDeInsert($values);
10 $sqlDeValues = calculaSqlDeValues($values);
11 $sql = "INSERT INTO $into ($sqlDeCampos) VALUES ($sqlDeValues)";
12 $parametros = calculaArregloDeParametros($values);
13 $pdo->prepare($sql)->execute($parametros);
14}
15

14. lib / php / insertBridges.php

1<?php
2
3require_once __DIR__ . "/calculaSqlDeCamposDeInsert.php";
4require_once __DIR__ . "/calculaSqlDeValues.php";
5
6function insertBridges(
7 PDO $pdo,
8 string $into,
9 array $valuesDePadre,
10 array $valueDeHijos
11) {
12 if (sizeof($valueDeHijos) > 0) {
13 $sqlDeCamposDePadre = calculaSqlDeCamposDeInsert($valuesDePadre);
14 $sqlDeCampoDeHijos = calculaSqlDeCamposDeInsert($valueDeHijos);
15 $sqlDeValuesDePadre = calculaSqlDeValues($valuesDePadre);
16 $sqlDeValueDeHijos = calculaSqlDeValues($valueDeHijos);
17 $insert = $pdo->prepare(
18 "INSERT INTO $into ($sqlDeCamposDePadre, $sqlDeCampoDeHijos)
19 VALUES ($sqlDeValuesDePadre, $sqlDeValueDeHijos)"
20 );
21 $parametros = calculaArregloDeParametros($valuesDePadre);
22 foreach ($valueDeHijos as $nombreDeValueDeHijo => $valoresDeValueDeHijo) {
23 foreach ($valoresDeValueDeHijo as $valorDeValueDeHijo) {
24 $parametros[":$nombreDeValueDeHijo"] = $valorDeValueDeHijo;
25 $insert->execute($parametros);
26 }
27 break;
28 }
29 }
30}
31

15. lib / php / INTERNAL_SERVER_ERROR.php

1<?php
2
3const INTERNAL_SERVER_ERROR = 500;

16. lib / php / ProblemDetails.php

1<?php
2
3/** Detalle de los errores devueltos por un servicio. */
4class ProblemDetails extends Exception
5{
6
7 public int $status;
8 public string $title;
9 public ?string $type;
10 public ?string $detail;
11
12 public function __construct(
13 int $status,
14 string $title,
15 ?string $type = null,
16 ?string $detail = null,
17 Throwable $previous = null
18 ) {
19 parent::__construct($title, $status, $previous);
20 $this->status = $status;
21 $this->type = $type;
22 $this->title = $title;
23 $this->detail = $detail;
24 }
25}
26

17. lib / php / recuperaTexto.php

1<?php
2
3/**
4 * Recupera el texto de un parámetro enviado al
5 * servidor por medio de GET, POST o cookie.
6 *
7 * Si el parámetro no se recibe, devuelve false.
8 */
9function recuperaTexto(string $parametro): false|string
10{
11 /* Si el parámetro está asignado en $_REQUEST,
12 * devuelve su valor; de lo contrario, devuelve false.
13 */
14 $valor = isset($_REQUEST[$parametro])
15 ? $_REQUEST[$parametro]
16 : false;
17 return $valor;
18}
19

18. lib / php / selectFirst.php

1<?php
2
3require_once __DIR__ . "/fetch.php";
4require_once __DIR__ . "/calculaArregloDeParametros.php";
5require_once __DIR__ . "/calculaSqlDeAsignaciones.php";
6
7function selectFirst(
8 PDO $pdo,
9 string $from,
10 array $where = [],
11 string $orderBy = "",
12 int $mode = PDO::FETCH_ASSOC,
13 $opcional = null
14) {
15 $sql = "SELECT * FROM $from";
16
17 if (sizeof($where) > 0) {
18 $sqlDeWhere = calculaSqlDeAsignaciones(" AND ", $where);
19 $sql .= " WHERE $sqlDeWhere";
20 }
21
22 if ($orderBy !== "") {
23 $sql .= " ORDER BY $orderBy";
24 }
25
26 if (sizeof($where) === 0) {
27 $statement = $pdo->query($sql);
28 return fetch($statement, [], $mode, $opcional);
29 } else {
30 $statement = $pdo->prepare($sql);
31 $parametros = calculaArregloDeParametros($where);
32 return fetch($statement, $parametros, $mode, $opcional);
33 }
34}
35

19. lib / php / validaCue.php

1<?php
2
3require_once __DIR__ . "/BAD_REQUEST.php";
4require_once __DIR__ . "/ProblemDetails.php";
5
6function validaCue($cue)
7{
8
9 if ($cue === false)
10 throw new ProblemDetails(
11 status: BAD_REQUEST,
12 title: "Falta el cue.",
13 type: "/error/faltacue.html",
14 detail: "La solicitud no tiene el valor de cue.",
15 );
16
17 $trimCue = trim($cue);
18
19 if ($trimCue === "")
20 throw new ProblemDetails(
21 status: BAD_REQUEST,
22 title: "Cue en blanco.",
23 type: "/error/cuenblanco.html",
24 detail: "Pon texto en el campo cue.",
25 );
26
27 return $trimCue;
28}
29

O. Carpeta « error »

Versión para imprimir.

A. error / cuenblanco.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Cue en blanco</title>
10
11</head>
12
13<body>
14
15 <h1>Cue en blanco</h1>
16
17 <h1>Pon texto en el campo cue.</h1>
18
19</body>
20
21</html>

B. error / datosincorrectos.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Datos incorrectos</title>
10
11</head>
12
13<body>
14
15 <h1>Datos incorrectos</h1>
16
17 <p>El cue y/o el match proporcionados son incorrectos.</p>
18</body>
19
20</html>

C. error / errorinterno.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Error interno del servidor</title>
10
11</head>
12
13<body>
14
15 <h1>Error interno del servidor</h1>
16
17 <p>Se presentó de forma inesperada un error interno del servidor.</p>
18
19</body>
20
21</html>

D. error / faltacue.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Falta el cue</title>
10
11</head>
12
13<body>
14
15 <h1>Falta el cue</h1>
16
17 <p>La solicitud no tiene el valor de cue.</p>
18
19</body>
20
21</html>

E. error / faltadescripcion.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Falta la descripción</title>
10
11</head>
12
13<body>
14
15 <h1>Falta la descripción</h1>
16
17</body>
18
19</html>

F. error / faltaid.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Falta el id</title>
10
11</head>
12
13<body>
14
15 <h1>Falta el id</h1>
16
17 <p>La solicitud no tiene el valor de id.</p>
18
19</body>
20
21</html>

G. error / faltamatch.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Falta el match</title>
10
11</head>
12
13<body>
14
15 <h1>Falta el match</h1>
16
17 <p>La solicitud no tiene el valor de match.</p>
18
19</body>
20
21</html>

H. error / faltanombre.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Falta el nombre</title>
10
11</head>
12
13<body>
14
15 <h1>Falta el nombre</h1>
16
17 <p>La solicitud no tiene el valor de nombre.</p>
18
19</body>
20
21</html>

I. error / idenblanco.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Id en blanco</title>
10
11</head>
12
13<body>
14
15 <h1>Id en blanco</h1>
16
17</body>
18
19</html>

J. error / matchenblanco.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Match en blanco</title>
10
11</head>
12
13<body>
14
15 <h1>Match en blanco</h1>
16
17 <h1>Pon texto en el campo match.</h1>
18
19</body>
20
21</html>

K. error / noautorizado.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>No autorizado</title>
10
11</head>
12
13<body>
14
15 <h1>No autorizado</h1>
16
17 <p>No está autorizado para usar este recurso.</p>
18
19</body>
20
21</html>

L. error / nojson.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>El valor devuelto no puede representarse como JSON</title>
10
11</head>
12
13<body>
14
15 <h1>El valor devuelto no puede representarse como JSON</h1>
16
17 <p>
18 Debido a un error interno del servidor, la respuesta generada, no se puede
19 recuperar.
20 </p>
21
22</body>
23
24</html>

M. error / nombreenblanco.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Nombre en blanco</title>
10
11</head>
12
13<body>
14
15 <h1>Nombre en blanco</h1>
16
17 <p>Pon texto en el campo nombre.</p>
18
19</body>
20
21</html>

N. error / pasatiemponoencontrado.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Pasatiempo no encontrado</title>
10
11</head>
12
13<body>
14
15 <h1>Pasatiempo no encontrado</h1>
16
17 <p>No se encontró ningún pasatiempo con el id solicitado.</p>
18
19</body>
20
21</html>

O. error / resultadonojson.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>El resultado no puede representarse como JSON</title>
10
11</head>
12
13<body>
14
15 <h1>El resultado no puede representarse como JSON</h1>
16
17 <p>
18 Debido a un error interno del servidor, el resultado generado, no se puede
19 recuperar.
20 </p>
21
22</body>
23
24</html>

P. error / rolincorrecto.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Tipo incorrecto para un rol</title>
10
11</head>
12
13<body>
14
15 <h1>Tipo incorrecto para un rol</h1>
16
17</body>
18
19</html>

Q. error / sesioniniciada.html

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Sesión iniciada</title>
10
11</head>
12
13<body>
14
15 <h1>Sesión iniciada</h1>
16
17 <p>La sesión ya está iniciada.</p>
18
19</body>
20
21</html>

P. .htaccess

1RewriteEngine On
2RewriteCond %{HTTP:X-Forwarded-Proto} !https
3RewriteCond %{HTTPS} off
4RewriteCond %{HTTP:CF-Visitor} !{"scheme":"https"}
5RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Q. jsconfig.json

1{
2 "compilerOptions": {
3 "checkJs": true,
4 "strictNullChecks": true,
5 "target": "ES6",
6 "module": "Node16",
7 "moduleResolution": "Node16",
8 "lib": [
9 "ES2017",
10 "WebWorker",
11 "DOM"
12 ]
13 }
14}

R. Resumen