N. Carpeta « lib »

Versión para imprimir.

A. Carpeta « lib / js »

Versión para imprimir.

1. lib / js / bdConsulta.js

1/**
2 * @template T
3 * @param { Promise<IDBDatabase> } bd
4 * @param { string[] } entidades
5 * @param { (
6 * transaccion: IDBTransaction,
7 * resolve: (resultado:T)=>void
8 * ) => void } consulta
9 * @returns {Promise<T>}
10 */
11export async function bdConsulta(bd, entidades, consulta) {
12
13 const base = await bd
14
15 return new Promise((resolve, reject) => {
16 // Inicia una transacción de solo lectura.
17 const transaccion = base.transaction(entidades, "readonly")
18 // Al terminar con error ejecuta la función reject.
19 transaccion.onerror = () => reject(transaccion.error)
20 // Estas son las operaciones para realizar la consulta.
21 consulta(transaccion, resolve)
22 })
23
24}

2. lib / js / bdEjecuta.js

1/**
2 * @param { Promise<IDBDatabase> } bd
3 * @param { string[] } entidades
4 * @param { (t:IDBTransaction) => void } operaciones
5 */
6export async function bdEjecuta(bd, entidades, operaciones) {
7
8 // Espera que se abra la bd
9 const base = await bd
10
11 return new Promise(
12 (resolve, reject) => {
13 // Inicia una transacción de lectura y escritura.
14 const transaccion = base.transaction(entidades, "readwrite")
15 // Al terminar con éxito, ejecuta la función resolve.
16 transaccion.oncomplete = resolve
17 // Al terminar con error, ejecuta la función reject.
18 transaccion.onerror = () => reject(transaccion.error)
19 // Estas son las operaciones de la transacción.
20 operaciones(transaccion)
21 })
22
23}

3. lib / js / consumeJson.js

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

4. lib / js / enviaJson.js

1import { consumeJson } from "./consumeJson.js"
2
3/**
4 * @param { string } url
5 * @param { Object } body
6 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
7 * | "CONNECT" | "HEAD" } metodoHttp
8 */
9export async function enviaJson(url, body, metodoHttp = "POST") {
10 return await consumeJson(fetch(url, {
11 method: metodoHttp,
12 headers: {
13 "Content-Type": "application/json",
14 "Accept": "application/json, application/problem+json"
15 },
16 body: JSON.stringify(body)
17 }))
18}
19
20// Permite que los eventos de html usen la función.
21window["enviaJson"] = enviaJson

5. 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

6. lib / js / muestraError.js

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

7. lib / js / muestraObjeto.js

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

8. lib / js / ProblemDetails.js

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

9. lib / js / registraServiceWorkerSiEsSoportado.js

1import { muestraError } from "./muestraError.js"
2
3/**
4 * @param { string | URL } urlDeServiceWorker
5 */
6export async function registraServiceWorkerSiEsSoportado(urlDeServiceWorker) {
7 try {
8 if ("serviceWorker" in navigator) {
9 const registro = await navigator.serviceWorker.register(urlDeServiceWorker)
10 console.log(urlDeServiceWorker, "registrado.")
11 console.log(registro)
12 }
13 } catch (error) {
14 muestraError(error)
15 }
16}

B. Carpeta « lib / php »

Versión para imprimir.

1. lib / php / BAD_REQUEST.php

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

2. 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

3. 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

4. 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

5. 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

6. lib / php / fetch.php

1<?php
2
3function fetch(
4 PDOStatement|false $statement,
5 $parametros = [],
6 int $mode = PDO::FETCH_OBJ,
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

7. lib / php / fetchAll.php

1<?php
2
3function fetchAll(
4 PDOStatement|false $statement,
5 $parametros = [],
6 int $mode = PDO::FETCH_OBJ,
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

8. lib / php / INTERNAL_SERVER_ERROR.php

1<?php
2
3const INTERNAL_SERVER_ERROR = 500;

9. 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

10. lib / php / recuperaJson.php

1<?php
2
3function recuperaJson()
4{
5 return json_decode(file_get_contents("php://input"));
6}
7

11. lib / php / validaJson.php

1<?php
2
3require_once __DIR__ . "/BAD_REQUEST.php";
4require_once __DIR__ . "/ProblemDetails.php";
5
6function validaJson($objeto)
7{
8
9 if ($objeto === null)
10 throw new ProblemDetails(
11 status: BAD_REQUEST,
12 title: "Los datos recibidos no son JSON.",
13 type: "/error/datosnojson.html",
14 detail: "Los datos recibidos no están en formato JSON.O",
15 );
16
17 return $objeto;
18}
19