24. Compras sencillas

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 (con videos)

  1. Prueba el ejemplo en http://srvcompras.rf.gd/.

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

  3. Crea una cuenta de email pqra ti, por ejemplo, pepito@google.com. Si ya tienes un email, omite este paso.

  4. 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. Si ya tienes una cuenta, omite este paso.

  5. Crea un repositorio nuevo. En el nombre del repositorio debes poner el nombre de tu sitio; por ejemplo devuelvejson

  6. Importa el proyecto de GitHub a Visual Studio Code

  7. Edita los archivos que desees.

  8. Prueba tu sitio localmente.

  9. Necesitas un hosting. En este ejemplo se muestra como usar el hosting. https://infinityfree.com/ Si no lo has usado, lo primero que tienes que hacer es entrar a registrar tu email con el botón Registrar. Si ya tienes tu email registrado, omite este paso.

  10. Crea una cuenta. Si ya tienes cuenta, entra a ella y crea un nuevo domino. En este ejemplo no se crean los archivos directamente en el hosting.

  11. Sube tus archivos al hosting usando ftp.

  12. Sube tus archivos a GitHub. En este ejemplo no hay archivo sw.js ni necesitas esperar 11 o más minutos.

F. Hazlo funcionar (texto)

  1. Prueba el ejemplo en http://srvcompras.rf.gd/.

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

  3. Crea tu proyecto en GitHub:

    1. Crea una cuenta de email para tí, por ejemplo, pepito@google.com. Si ya tienes un email, omite este paso.

    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. Si ya tienes una cuenta, omite este paso.

    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.

    1. Crea una nueva carpeta para crear un nuevo proyecto que estará conectado directamente al servidor web por ftp.

    2. Abre la nueva carpeta con Visual Studio Code.

    3. Tecle al mismo Mayúsculas+Control+P y selecciona SFTP: Config. Aparece un archivo de configuración de FTP. Llena los datos con la configuración de FTP de tu servidor, excepto la contraseña.

    4. Cliquea el botón de SFTP y luego haz clic en la URL de tu servidos. En la barra superior te pide la contraseña y ENTER.

    5. Pásate a la parte de archivos y coloca tus archivos.

    6. Cliquea con el botón derecho en la sección de archivos y selecciona Sync: Local -> Remote.

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

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

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

G. Archivos

Haz clic en los triángulos para expandir las carpetas

H. 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>Productos</title>
10
11
 <script type="module" src="js/lib/manejaErrores.js"></script>
12
13
</head>
14
15
<body>
16
17
 <h1>Productos</h1>
18
19
 <p><a href="carrito.html">Ver carrito</a></p>
20
21
 <dl id="lista">
22
  <dt>Cargando…</dt>
23
  <dd><progress max="100">Cargando…</progress></dd>
24
 </dl>
25
26
 <script type="module">
27
28
  import { descargaVista } from "./js/lib/descargaVista.js"
29
30
  descargaVista("php/vista-index.php")
31
32
 </script>
33
34
</body>
35
36
</html>

I. agrega.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>Agregar</title>
10
11
 <script type="module" src="js/lib/manejaErrores.js"></script>
12
</head>
13
14
<body>
15
16
 <form id="formulario">
17
18
  <h1>Agregar</h1>
19
20
  <p><a href="index.html">Cancelar</a></p>
21
22
  <input type="hidden" name="id">
23
24
  <p>
25
   <label>
26
    Producto
27
    <output name="producto">
28
     <progress max="100">Cargando…</progress>
29
    </output>
30
   </label>
31
  </p>
32
33
  <p>
34
   <label>
35
    Precio
36
    <output name="precio">
37
     <progress max="100">Cargando…</progress>
38
    </output>
39
   </label>
40
  </p>
41
42
  <p>
43
   <label>
44
    Cantidad *
45
    <input name="cantidad" type="number" min="0" step="0.01">
46
   </label>
47
  </p>
48
49
  <p>* Obligatorio</p>
50
51
  <p><button type="submit">Agregar</button></p>
52
53
 </form>
54
55
 <script type="module">
56
57
  import { descargaVista } from "./js/lib/descargaVista.js"
58
  import { submitAccion } from "./js/lib/submitAccion.js"
59
60
  const params = new URL(location.href).searchParams
61
  descargaDatos()
62
63
  async function descargaDatos() {
64
65
   if (params.size > 0) {
66
67
    await descargaVista("php/vista-agrega.php?" + params)
68
69
    formulario.addEventListener(
70
     "submit",
71
     event => submitAccion(
72
      event, "php/det-venta-agrega.php", formulario, "index.html"
73
     )
74
    )
75
76
   }
77
78
  }
79
80
 </script>
81
82
</body>
83
84
</html>

J. carrito.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>Carrito</title>
10
11
 <script type="module" src="js/lib/manejaErrores.js"></script>
12
13
</head>
14
15
<body>
16
17
 <h1>Carrito</h1>
18
19
 <p>
20
21
  <a href="index.html">Productos</a>
22
23
  <button id="botonProcesar" type='button'>
24
   Procesar compra
25
  </button>
26
27
 </p>
28
29
 <p>
30
  <label>
31
   Folio
32
   <output id="folio">
33
    <progress max="100">Cargando…</progress>
34
   </output>
35
  </label>
36
 </p>
37
38
 <fieldset>
39
40
  <legend>Detalle</legend>
41
42
  <dl id="detalles">
43
   <dt>Cargando…</dt>
44
   <dd><progress max="100">Cargando…</progress></dd>
45
  </dl>
46
47
 </fieldset>
48
49
 <script type="module">
50
51
  import { descargaVista } from "./js/lib/descargaVista.js"
52
  import { consume } from "./js/lib/consume.js"
53
  import { recibeJson } from "./js/lib/recibeJson.js"
54
55
  const params = new URL(location.href).searchParams
56
  descargaDatos()
57
58
  async function descargaDatos() {
59
   await descargaVista("php/vista-carrito.php")
60
   botonProcesar.addEventListener("click", procesa)
61
  }
62
63
  async function procesa() {
64
   if (confirm('Confirma procesar')) {
65
    await consume(recibeJson('php/venta-en-captura-procesa.php'))
66
    location.href = 'index.html'
67
   }
68
  }
69
70
 </script>
71
72
</body>
73
74
</html>

K. modifica.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>Modificar</title>
10
11
 <script type="module" src="js/lib/manejaErrores.js"></script>
12
13
</head>
14
15
<body>
16
17
 <form id="formulario">
18
19
  <h1>Modificar</h1>
20
21
  <p><a href="carrito.html">Cancelar</a></p>
22
23
  <input type="hidden" name="prodId">
24
25
  <p>
26
   <label>
27
    Producto
28
    <output name="prodNombre">
29
     <progress max="100">Cargando…</progress>
30
    </output>
31
   </label>
32
  </p>
33
34
  <p>
35
   <label>
36
    Precio
37
    <output name="precio">
38
     <progress max="100">Cargando…</progress>
39
    </output>
40
   </label>
41
  </p>
42
43
  <p>
44
   <label>
45
    Cantidad *
46
    <input name="cantidad" type="number" min="0" step="0.01">
47
   </label>
48
  </p>
49
50
  <p>* Obligatorio</p>
51
52
  <p>
53
54
   <button type="submit">Guardar</button>
55
56
   <button id="botonEliminar" type="button">
57
    Eliminar
58
   </button>
59
60
  </p>
61
62
 </form>
63
64
 <script type="module">
65
66
  import { descargaVista } from "./js/lib/descargaVista.js"
67
  import { submitAccion } from "./js/lib/submitAccion.js"
68
  import { accionElimina } from "./js/lib/accionElimina.js"
69
70
  const params = new URL(location.href).searchParams
71
  descargaDatos()
72
73
  async function descargaDatos() {
74
75
   if (params.size > 0) {
76
77
    await descargaVista("php/vista-modifica.php?" + params)
78
79
    formulario.addEventListener(
80
     "submit",
81
     event => submitAccion(
82
      event, "php/det-venta-modifica.php", formulario, "carrito.html"
83
     )
84
    )
85
86
    botonEliminar.addEventListener(
87
     "click",
88
     () => accionElimina(
89
      "php/det-venta-elimina.php",
90
      formulario,
91
      "Confirma la eliminación",
92
      "carrito.html"
93
     )
94
    )
95
96
   }
97
98
  }
99
100
 </script>
101
102
</body>
103
104
</html>

L. Carpeta « php »

Versión para imprimir.

A. php / Bd.php

1
<?php
2
3
require_once __DIR__ . "/ventaEnCapturaAgrega.php";
4
5
class Bd
6
{
7
8
 private static ?PDO $pdo = null;
9
10
 public static function pdo(): PDO
11
 {
12
  if (self::$pdo === null) {
13
   self::$pdo = new PDO(
14
    // cadena de conexión
15
    "sqlite:" . __DIR__ . "/srvcompras.db",
16
    // usuario
17
    null,
18
    // contraseña
19
    null,
20
    // Opciones: pdos no persistentes y lanza excepciones.
21
    [PDO::ATTR_PERSISTENT => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
22
   );
23
24
   self::$pdo->exec(
25
    'CREATE TABLE IF NOT EXISTS VENTA (
26
      VENT_ID INTEGER,
27
      VENT_EN_CAPTURA INTEGER NOT NULL,
28
      CONSTRAINT VENT_PK
29
       PRIMARY KEY(VENT_ID)
30
     )'
31
   );
32
   self::$pdo->exec(
33
    'CREATE TABLE IF NOT EXISTS PRODUCTO (
34
      PROD_ID INTEGER,
35
      PROD_NOMBRE TEXT NOT NULL,
36
      PROD_EXISTENCIAS REAL NOT NULL,
37
      PROD_PRECIO REAL NOT NULL,
38
      CONSTRAINT PROD_PK
39
       PRIMARY KEY(PROD_ID),
40
      CONSTRAINT PROD_NOM_UNQ
41
       UNIQUE(PROD_NOMBRE),
42
      CONSTRAINT PROD_NOM_NV
43
       CHECK(LENGTH(PROD_NOMBRE) > 0)
44
     )'
45
   );
46
   self::$pdo->exec(
47
    'CREATE TABLE IF NOT EXISTS DET_VENTA (
48
      VENT_ID INTEGER NOT NULL,
49
      PROD_ID INTEGER NOT NULL,
50
      DTV_CANTIDAD REAL NOT NULL,
51
      DTV_PRECIO REAL NOT NULL,
52
      CONSTRAINT DTV_PK
53
       PRIMARY KEY (VENT_ID, PROD_ID),
54
      CONSTRAINT DTV_VENT_FK
55
       FOREIGN KEY (VENT_ID) REFERENCES VENTA(VENT_ID),
56
      CONSTRAINT DTV_PROD_FK
57
       FOREIGN KEY (PROD_ID) REFERENCES PRODUCTO(PROD_ID)
58
      )'
59
   );
60
61
   $cantidadDeProductos =
62
    self::$pdo->query("SELECT COUNT(PROD_ID) FROM PRODUCTO")->fetchColumn(0);
63
64
   if ($cantidadDeProductos === 0) {
65
    self::$pdo->exec(
66
     "INSERT INTO PRODUCTO
67
       (PROD_NOMBRE, PROD_EXISTENCIAS, PROD_PRECIO)
68
      VALUES
69
       ('Sandwich', 50, 15),
70
       ('Hot dog', 40, 30),
71
       ('Hamburguesa', 30, 40)"
72
    );
73
   }
74
75
   $cantidadDeVentas =
76
    self::$pdo->query("SELECT COUNT(VENT_ID) FROM VENTA")->fetchColumn(0);
77
78
   if ($cantidadDeVentas === 0) {
79
    ventaEnCapturaAgrega(self::$pdo);
80
   }
81
  }
82
83
  return self::$pdo;
84
 }
85
}
86

B. php / det-venta-agrega.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/recibeEnteroObligatorio.php";
5
require_once __DIR__ . "/lib/recibeFlotanteObligatorio.php";
6
require_once __DIR__ . "/lib/validaEntidadObligatoria.php";
7
require_once __DIR__ . "/lib/devuelveCreated.php";
8
require_once __DIR__ . "/Bd.php";
9
require_once __DIR__ . "/productoBusca.php";
10
require_once __DIR__ . "/ventaEnCapturaBusca.php";
11
12
13
$prodId = recibeEnteroObligatorio("id");
14
$cantidad = recibeFlotanteObligatorio("cantidad");
15
16
$bd = Bd::pdo();
17
18
$producto = productoBusca($bd, $prodId);
19
$producto = validaEntidadObligatoria("Producto",  $producto);
20
21
$venta = ventaEnCapturaBusca($bd);
22
$venta = validaEntidadObligatoria("Venta en captura",  $venta);
23
24
$stmt = $bd->prepare(
25
 "INSERT INTO DET_VENTA (
26
    VENT_ID, PROD_ID, DTV_CANTIDAD, DTV_PRECIO
27
   ) values (
28
    :VENT_ID, :PROD_ID, :DTV_CANTIDAD, :DTV_PRECIO
29
   )"
30
);
31
$stmt->execute([
32
 ":VENT_ID" => $venta["VENT_ID"],
33
 ":PROD_ID" => $prodId,
34
 ":DTV_CANTIDAD" => $cantidad,
35
 ":DTV_PRECIO" => $producto["PROD_PRECIO"],
36
]);
37
38
$encodeProdId = urlencode($prodId);
39
devuelveCreated("/php/vista-modifica.php?id=$encodeProdId", [
40
 "prodId" => ["value" => $prodId],
41
 "prodNombre" => ["value" => $producto["PROD_NOMBRE"]],
42
 "precio" => ["value" => "$" . number_format($producto["PROD_PRECIO"], 2)],
43
 "cantidad" => ["valueAsNumber" => $cantidad],
44
]);
45

C. php / det-venta-elimina.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/recibeEnteroObligatorio.php";
5
require_once __DIR__ . "/lib/devuelveNoContent.php";
6
require_once __DIR__ . "/Bd.php";
7
require_once __DIR__ . "/ventaEnCapturaBusca.php";
8
9
$prodId = recibeEnteroObligatorio("prodId");
10
11
$bd = Bd::pdo();
12
13
$venta = ventaEnCapturaBusca($bd);
14
if ($venta !== false) {
15
 $stmt = $bd->prepare(
16
  "DELETE FROM DET_VENTA WHERE VENT_ID = :VENT_ID AND PROD_ID = :PROD_ID"
17
 );
18
 $stmt->execute([":VENT_ID" => $venta["VENT_ID"], ":PROD_ID" => $prodId]);
19
}
20
devuelveNoContent();
21

D. php / det-venta-modifica.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/recibeEnteroObligatorio.php";
5
require_once __DIR__ . "/lib/recibeFlotanteObligatorio.php";
6
require_once __DIR__ . "/lib/validaEntidadObligatoria.php";
7
require_once __DIR__ . "/lib/devuelveJson.php";
8
require_once __DIR__ . "/Bd.php";
9
require_once __DIR__ . "/productoBusca.php";
10
require_once __DIR__ . "/ventaEnCapturaBusca.php";
11
12
$prodId = recibeEnteroObligatorio("prodId");
13
$cantidad = recibeFlotanteObligatorio("cantidad");
14
15
$bd = Bd::pdo();
16
17
$producto = productoBusca($bd, $prodId);
18
$producto = validaEntidadObligatoria("Producto",  $producto);
19
20
$venta = ventaEnCapturaBusca($bd);
21
$venta = validaEntidadObligatoria("Venta en captura",  $venta);
22
23
$stmt = $bd->prepare(
24
 "UPDATE DET_VENTA
25
   SET
26
    DTV_CANTIDAD = :DTV_CANTIDAD,
27
    DTV_PRECIO = :DTV_PRECIO
28
   WHERE
29
    VENT_ID = :VENT_ID
30
    AND PROD_ID = :PROD_ID"
31
);
32
$stmt->execute([
33
 ":DTV_CANTIDAD" => $cantidad,
34
 ":DTV_PRECIO" => $producto["PROD_PRECIO"],
35
 ":VENT_ID" => $venta["VENT_ID"],
36
 ":PROD_ID" => $prodId
37
]);
38
39
devuelveJson([
40
 "prodId" => ["value" => $prodId],
41
 "prodNombre" => ["value" => $producto["PROD_NOMBRE"]],
42
 "precio" => ["value" => "$" . number_format($producto["PROD_PRECIO"], 2)],
43
 "cantidad" => ["valueAsNumber" => $cantidad],
44
]);
45

E. php / detVentaConsulta.php

1
<?php
2
3
function detVentaConsulta(\PDO $bd, int $ventaId)
4
{
5
 $stmt = $bd->prepare(
6
  "SELECT
7
    DV.PROD_ID,
8
    P.PROD_NOMBRE,
9
    P.PROD_EXISTENCIAS,
10
    P.PROD_PRECIO,
11
    DV.DTV_CANTIDAD,
12
    DV.DTV_PRECIO
13
   FROM DET_VENTA DV, PRODUCTO P
14
   WHERE
15
    DV.PROD_ID = P.PROD_ID
16
    AND DV.VENT_ID = :VENT_ID
17
   ORDER BY P.PROD_NOMBRE"
18
 );
19
 $stmt->execute([":VENT_ID" => $ventaId]);
20
 $lista = $stmt->fetchAll(PDO::FETCH_ASSOC);
21
 return $lista;
22
}
23

F. php / productoBusca.php

1
<?php
2
3
function productoBusca(\PDO $bd, int $prodId)
4
{
5
$stmt = $bd->prepare("SELECT * FROM PRODUCTO WHERE PROD_ID = :PROD_ID");
6
$stmt->execute([":PROD_ID" => $prodId]);
7
$modelo = $stmt->fetch(PDO::FETCH_ASSOC);
8
 return $modelo;
9
}
10

G. php / srvcompras.db

1
SQLite format 3@  .�*
O�W
��
{�,�'tableDET_VENTADET_VENTACREATE TABLE DET_VENTA (
2
      VENT_ID INTEGER NOT NULL,
3
      PROD_ID INTEGER NOT NULL,
4
      DTV_CANTIDAD REAL NOT NULL,
5
      DTV_PRECIO REAL NOT NULL,
6
      CONSTRAINT DTV_PK
7
       PRIMARY KEY (VENT_ID, PROD_ID),
8
      CONSTRAINT DTV_VENT_FK
9
       FOREIGN KEY (VENT_ID) REFERENCES VENTA(VENT_ID),
10
      CONSTRAINT DTV_PROD_FK
11
       FOREIGN KEY (PROD_ID) REFERENCES PRODUCTO(PROD_ID)
12
      )1Eindexsqlite_autoindex_DET_VENTA_1DET_VENTA�m�-tablePRODUCTOPRODUCTOCREATE TABLE PRODUCTO (
13
      PROD_ID INTEGER,
14
      PROD_NOMBRE TEXT NOT NULL,
15
      PROD_EXISTENCIAS REAL NOT NULL,
16
      PROD_PRECIO REAL NOT NULL,
17
      CONSTRAINT PROD_PK
18
       PRIMARY KEY(PROD_ID),
19
      CONSTRAINT PROD_NOM_UNQ
20
       UNIQUE(PROD_NOMBRE),
21
      CONSTRAINT PROD_NOM_NV
22
       CHECK(LENGTH(PROD_NOMBRE) > 0)
23
     )/Cindexsqlite_autoindex_PRODUCTO_1PRODUCTO�&�+tableVENTAVENTACREATE TABLE VENTA (
24
      VENT_ID INTEGER,
25
      VENT_EN_CAPTURA INTEGER NOT NULL,
26
      CONSTRAINT VENT_PK
27
       PRIMARY KEY(VENT_ID)
28
     )
���	
����#Hamburguesa(Hot dog$Sandwich0
29
����#HamburguesaHot dog	Sandwich
���			
30
���				

H. php / venta-en-captura-procesa.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/devuelveCreated.php";
5
require_once __DIR__ . "/lib/validaEntidadObligatoria.php";
6
require_once __DIR__ . "/Bd.php";
7
require_once __DIR__ . "/ventaEnCapturaBusca.php";
8
require_once __DIR__ . "/detVentaConsulta.php";
9
require_once __DIR__ . "/ventaEnCapturaAgrega.php";
10
11
$bd = Bd::pdo();
12
$bd->beginTransaction();
13
14
$venta = ventaEnCapturaBusca($bd);
15
$venta = validaEntidadObligatoria("Venta en captura",  $venta);
16
17
$detalles = detVentaConsulta($bd, $venta["VENT_ID"]);
18
19
// Actualiza las existencias de los productos vendidos.
20
$update = $bd->prepare(
21
 "UPDATE PRODUCTO
22
   SET PROD_EXISTENCIAS = :PROD_EXISTENCIAS
23
   WHERE PROD_ID = :PROD_ID"
24
);
25
foreach ($detalles as $detVenta) {
26
 $update->execute([
27
  ":PROD_ID" => $detVenta["PROD_ID"],
28
  ":PROD_EXISTENCIAS" =>
29
  $detVenta["PROD_EXISTENCIAS"] - $detVenta["DTV_CANTIDAD"]
30
 ]);
31
}
32
33
$update = $bd->prepare(
34
 "UPDATE VENTA
35
   SET VENT_EN_CAPTURA = 0
36
   WHERE VENT_ID = :VENT_ID"
37
);
38
$update->execute([":VENT_ID" => $venta["VENT_ID"]]);
39
40
ventaEnCapturaAgrega($bd);
41
$folio = $bd->lastInsertId();
42
43
$bd->commit();
44
45
devuelveCreated("/srv/venta-en-captura.php", [
46
 "folio" => ["value" => $folio],
47
 "detalles" => ["innerHTML" => ""]
48
]);
49

I. php / ventaEnCapturaAgrega.php

1
<?php
2
3
function ventaEnCapturaAgrega(\PDO $bd)
4
{
5
 $bd->exec("INSERT INTO VENTA (VENT_EN_CAPTURA) VALUES (1)");
6
}
7

J. php / ventaEnCapturaBusca.php

1
<?php
2
3
function ventaEnCapturaBusca(\PDO $bd)
4
{
5
 $stmt = $bd->query("SELECT * FROM VENTA WHERE VENT_EN_CAPTURA = 1");
6
 $venta = $stmt->fetch(PDO::FETCH_ASSOC);
7
 return $venta;
8
}
9

K. php / vista-agrega.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/recibeEnteroObligatorio.php";
5
require_once __DIR__ . "/lib/validaEntidadObligatoria.php";
6
require_once __DIR__ . "/lib/devuelveJson.php";
7
require_once __DIR__ . "/Bd.php";
8
require_once __DIR__ . "/productoBusca.php";
9
10
$id = recibeEnteroObligatorio("id");
11
12
$producto = productoBusca(Bd::pdo(), $id);
13
$producto = validaEntidadObligatoria("Producto",  $producto);
14
15
devuelveJson([
16
 "id" => ["value" => $id],
17
 "producto" => ["value" => $producto["PROD_NOMBRE"]],
18
 "precio" => ["value" => "$" . number_format($producto["PROD_PRECIO"], 2)],
19
]);
20

L. php / vista-carrito.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/devuelveJson.php";
5
require_once __DIR__ . "/lib/validaEntidadObligatoria.php";
6
require_once __DIR__ . "/Bd.php";
7
require_once __DIR__ . "/ventaEnCapturaBusca.php";
8
require_once __DIR__ . "/detVentaConsulta.php";
9
require_once __DIR__ . "/Bd.php";
10
11
$bd = Bd::pdo();
12
13
$venta = ventaEnCapturaBusca($bd);
14
$venta = validaEntidadObligatoria("Venta en captura",  $venta);
15
16
$detalles = detVentaConsulta($bd, $venta["VENT_ID"]);
17
18
$renderDetalles = "";
19
foreach ($detalles as $detVenta) {
20
 $encodeProdId = urlencode($detVenta["PROD_ID"]);
21
 $prodId = htmlentities($encodeProdId);
22
 $prodNombre = htmlentities($detVenta["PROD_NOMBRE"]);
23
 $precio = htmlentities("$" . number_format($detVenta["PROD_PRECIO"], 2));
24
 $cantidad = htmlentities(number_format($detVenta["DTV_CANTIDAD"], 2));
25
 $renderDetalles .=
26
  "<dt>$prodNombre</dt>
27
    <dd>
28
     <a href= 'modifica.html?prodId=$prodId'>Modificar o eliminar</a>
29
    </dd>
30
    <dd>
31
     <dl>
32
      <dt>Cantidad</dt>
33
      <dd>$cantidad</dd>
34
      <dt>Precio</dt>
35
      <dd>$precio</dd>
36
     </dl>
37
    </dd>";
38
}
39
40
devuelveJson([
41
 "folio" => ["value" => $venta["VENT_ID"]],
42
 "detalles" => ["innerHTML" => $renderDetalles]
43
]);
44

M. php / vista-index.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/devuelveJson.php";
5
require_once __DIR__ . "/Bd.php";
6
7
$bd = Bd::pdo();
8
$stmt = $bd->query("SELECT * FROM PRODUCTO ORDER BY PROD_NOMBRE");
9
$lista = $stmt->fetchAll(PDO::FETCH_ASSOC);
10
11
$render = "";
12
foreach ($lista as $modelo) {
13
 $encodeId = urlencode($modelo["PROD_ID"]);
14
 $id = htmlentities($encodeId);
15
 $nombre = htmlentities($modelo["PROD_NOMBRE"]);
16
 $precio = htmlentities("$" . number_format($modelo["PROD_PRECIO"], 2));
17
 $existencias = htmlentities(number_format($modelo["PROD_EXISTENCIAS"], 2));
18
 $render .=
19
  "<dt>$nombre</dt>
20
    <dd>
21
     <a href='agrega.html?id=$id'>Agregar al carrito</a>
22
    </dd>
23
    <dd>
24
     <dl>
25
      <dt>Precio</dt>
26
      <dd>$precio</dd>
27
      <dt>Existencias</dt>
28
      <dd>$existencias</dd>
29
     </dl>
30
    </dd>";
31
}
32
devuelveJson(["lista" => ["innerHTML" => $render]]);
33

N. php / vista-modifica.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/recibeEnteroObligatorio.php";
5
require_once __DIR__ . "/lib/validaEntidadObligatoria.php";
6
require_once __DIR__ . "/lib/devuelveJson.php";
7
require_once __DIR__ . "/Bd.php";
8
require_once __DIR__ . "/productoBusca.php";
9
require_once __DIR__ . "/ventaEnCapturaBusca.php";
10
11
$prodId = recibeEnteroObligatorio("prodId");
12
13
$bd = Bd::pdo();
14
15
$venta = ventaEnCapturaBusca($bd);
16
$venta = validaEntidadObligatoria("Venta en captura",  $venta);
17
18
$producto = productoBusca($bd, $prodId);
19
$producto = validaEntidadObligatoria("Producto",  $producto);
20
21
$stmt = $bd->prepare(
22
 "SELECT * FROM DET_VENTA WHERE VENT_ID = :VENT_ID AND PROD_ID = :PROD_ID"
23
);
24
$stmt->execute([":VENT_ID" => $venta["VENT_ID"], ":PROD_ID" => $prodId]);
25
$detVenta = $stmt->fetch(PDO::FETCH_ASSOC);
26
$detVenta = validaEntidadObligatoria("Detalle de venta",  $detVenta);
27
28
devuelveJson([
29
 "prodId" => ["value" => $prodId],
30
 "prodNombre" => ["value" => $producto["PROD_NOMBRE"]],
31
 "precio" => ["value" => "$" . number_format($detVenta["DTV_PRECIO"], 2)],
32
 "cantidad" => ["valueAsNumber" => $detVenta["DTV_CANTIDAD"]],
33
]);
34

O. Carpeta « php / lib »

1. php / lib / BAD_REQUEST.php

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

2. php / lib / devuelveCreated.php

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

3. php / lib / devuelveJson.php

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

4. php / lib / devuelveNoContent.php

1
<?php
2
3
function devuelveNoContent()
4
{
5
 http_response_code(204);
6
}
7

5. php / lib / devuelveResultadoNoJson.php

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

6. php / lib / INTERNAL_SERVER_ERROR.php

1
<?php
2
3
const INTERNAL_SERVER_ERROR = 500;

7. php / lib / manejaErrores.php

1
<?php
2
3
require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
require_once __DIR__ . "/ProblemDetailsException.php";
5
6
// Hace que se lance una excepción automáticamente cuando se genere un error.
7
set_error_handler(function ($severity, $message, $file, $line) {
8
 throw new ErrorException($message, 0, $severity, $file, $line);
9
});
10
11
// Código cuando una excepción no es atrapada.
12
set_exception_handler(function (Throwable $excepcion) {
13
 if ($excepcion instanceof ProblemDetailsException) {
14
  devuelveProblemDetails($excepcion->problemDetails);
15
 } else {
16
  devuelveProblemDetails([
17
   "status" => INTERNAL_SERVER_ERROR,
18
   "title" => "Error interno del servidor",
19
   "detail" => $excepcion->getMessage(),
20
   "type" => "/errors/errorinterno.html",
21
  ]);
22
 }
23
 exit();
24
});
25
26
function devuelveProblemDetails(array $array)
27
{
28
 $json = json_encode($array);
29
 if ($json === false) {
30
  devuelveResultadoNoJson();
31
 } else {
32
  http_response_code(isset($array["status"]) ? $array["status"] : 500);
33
  header("Content-Type: application/problem+json; charset=utf-8");
34
  echo $json;
35
 }
36
}
37

8. php / lib / NOT_FOUND.php

1
<?php
2
3
const NOT_FOUND = 404;
4

9. php / lib / ProblemDetailsException.php

1
<?php
2
3
require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
5
/**
6
 * Detalle de los errores devueltos por un servicio.
7
 */
8
class ProblemDetailsException extends Exception
9
{
10
11
 public array $problemDetails;
12
13
 public function __construct(
14
  array $problemDetails,
15
 ) {
16
  
17
  parent::__construct(
18
   isset($problemDetails["detail"])
19
    ? $problemDetails["detail"]
20
    : (isset($problemDetails["title"])
21
     ? $problemDetails["title"]
22
     : "Error"),
23
   $problemDetails["status"]
24
    ? $problemDetails["status"]
25
    : INTERNAL_SERVER_ERROR
26
  );
27
28
  $this->problemDetails = $problemDetails;
29
 }
30
}
31

10. php / lib / recibeArray.php

1
<?php
2
3
/**
4
 * Devuelve los valores asociados a un
5
 * parámetro multivaluado; por ejemplo, un
6
 * grupo de checkbox, recibido en el servidor
7
 * por medio de GET, POST o cookie.
8
 * 
9
 * Si no se recibe el parámetro, devuelve [].
10
 * 
11
 * Si el valor recibido no es un arreglo, lo
12
 * coloca dentro de uno.
13
 */
14
function recibeArray(string $parametro)
15
{
16
 if (isset($_REQUEST[$parametro])) {
17
  $valor = $_REQUEST[$parametro];
18
  return is_array($valor)
19
   ? $valor
20
   : [$valor];
21
 } else {
22
  return [];
23
 }
24
}
25

11. php / lib / recibeEntero.php

1
<?php
2
3
require_once __DIR__ . "/recibeTexto.php";
4
5
/**
6
 * Devuelve el valor entero de un parámetro recibido en el
7
 * servidor por medio de GET, POST o cookie.
8
 * 
9
 * Si el parámetro no se recibe, devuelve false
10
 *
11
 * Si se recibe una cadena vacía, se devuelve null.
12
 * 
13
 * Si parámetro no se puede convertir a entero, se genera
14
 * un error.
15
 */
16
function recibeEntero(string $parametro): false|null|int
17
{
18
 $valor = recibeTexto($parametro);
19
 if ($valor === false) {
20
  return false;
21
 } else {
22
  $valor = trim($valor);
23
  if ($valor === "") {
24
   return null;
25
  } else {
26
   return (int) $valor;
27
  }
28
 }
29
}
30

12. php / lib / recibeEnteroObligatorio.php

1
<?php
2
3
require_once __DIR__ . "/BAD_REQUEST.php";
4
require_once __DIR__ . "/recibeEntero.php";
5
require_once __DIR__ . "/ProblemDetailsException.php";
6
7
function recibeEnteroObligatorio(string $parametro)
8
{
9
 $entero = recibeEntero($parametro);
10
11
 if ($entero === false)
12
  throw new ProblemDetailsException([
13
   "status" => BAD_REQUEST,
14
   "title" => "Falta el valor $parametro.",
15
   "type" => "/errors/faltavalor.html",
16
   "detail" => "La solicitud no tiene el valor de $parametro."
17
  ]);
18
19
 if ($entero === null)
20
  throw new ProblemDetailsException([
21
   "status" => BAD_REQUEST,
22
   "title" => "Campo $parametro en blanco.",
23
   "type" => "/errors/campoenteroenblanco.html",
24
   "detail" => "Pon un número entero en el campo $parametro."
25
  ]);
26
27
 return $entero;
28
}
29

13. php / lib / recibeFlotante.php

1
<?php
2
3
require_once __DIR__ . "/recibeTexto.php";
4
5
/**
6
 * Devuelve el valor decimal de un parámetro (que
7
 * puede tener fracciones) enviado al servidor por
8
 * medio de GET, POST o cookie.
9
 * 
10
 * Si el parámetro no se recibe, devuekve false
11
 * 
12
 * Si se recibe una cadena vacía, se devuelve null.
13
 * 
14
 * Si parámetro no se puede convertir a decimal, se genera
15
 * un error.
16
 */
17
function recibeFlotante(string $parametro): false|null|float
18
{
19
 $valor = recibeTexto($parametro);
20
 if ($valor === false) {
21
  return false;
22
 } else {
23
  $valor = trim($valor);
24
  if ($valor === "") {
25
   return null;
26
  } else {
27
   return (float) $valor;
28
  }
29
 }
30
}
31

14. php / lib / recibeFlotanteObligatorio.php

1
<?php
2
3
require_once __DIR__ . "/BAD_REQUEST.php";
4
require_once __DIR__ . "/recibeFlotante.php";
5
require_once __DIR__ . "/ProblemDetailsException.php";
6
7
function recibeFlotanteObligatorio(string $parametro)
8
{
9
 $flotante = recibeFlotante($parametro);
10
11
 if ($flotante === false)
12
  throw new ProblemDetailsException([
13
   "status" => BAD_REQUEST,
14
   "title" => "Falta el valor $parametro.",
15
   "type" => "/error/faltavalor.html",
16
   "detail" => "La solicitud no tiene el valor de $parametro."
17
  ]);
18
19
 if ($flotante === null)
20
  throw new ProblemDetailsException([
21
   "status" => BAD_REQUEST,
22
   "title" => "Campo $parametro en blanco.",
23
   "type" => "/error/camponumericoenblanco.html",
24
   "detail" => "Pon un número en el campo $parametro."
25
  ]);
26
27
 return $flotante;
28
}
29

15. php / lib / recibeTexto.php

1
<?php
2
3
/**
4
 * Devuelve 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
 */
9
function recibeTexto(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

16. php / lib / recibeTextoObligatorio.php

1
<?php
2
3
require_once __DIR__ . "/BAD_REQUEST.php";
4
require_once __DIR__ . "/recibeTexto.php";
5
require_once __DIR__ . "/ProblemDetailsException.php";
6
7
function recibeTextoObligatorio(string $parametro)
8
{
9
 $texto = recibeTexto($parametro);
10
11
 if ($texto === false)
12
  throw new ProblemDetailsException([
13
   "status" => BAD_REQUEST,
14
   "title" => "Falta el valor $parametro.",
15
   "type" => "/errors/faltavalor.html",
16
   "detail" => "La solicitud no tiene el valor de $parametro."
17
  ]);
18
19
 $trimTexto = trim($texto);
20
21
 if ($trimTexto === "")
22
  throw new ProblemDetailsException([
23
   "status" => BAD_REQUEST,
24
   "title" => "Campo $parametro en blanco.",
25
   "type" => "/errors/campoenblanco.html",
26
   "detail" => "Pon texto en el campo $parametro."
27
  ]);
28
29
 return $trimTexto;
30
}
31

17. php / lib / validaEntidadObligatoria.php

1
<?php
2
3
require_once __DIR__ . "/NOT_FOUND.php";
4
require_once __DIR__ . "/ProblemDetailsException.php";
5
6
function validaEntidadObligatoria(string $nombre,  $entidad)
7
{
8
9
 if ($entidad === false)
10
  throw new ProblemDetailsException([
11
  "status" => NOT_FOUND,
12
  "title" => "Registro de $nombre no encontrado.",
13
  "type" => "/errors/entidadnoencontrada.html",
14
  "detail" => "No se encontró ningún registro de $nombre con el id solicitado.",
15
  ]);
16
17
 return $entidad;
18
}
19

M. Carpeta « js »

Versión para imprimir.

A. Carpeta « js / lib »

1. js / lib / accionElimina.js

1
import { consume } from "./consume.js"
2
import { enviaFormRecibeJson } from "./enviaFormRecibeJson.js"
3
4
/**
5
 * @param {string} url
6
 * @param {HTMLFormElement} formulario
7
 * @param {string} mensaje
8
 * @param {string} nuevaVista
9
 */
10
export async function accionElimina(url, formulario, mensaje, nuevaVista) {
11
 if (confirm(mensaje)) {
12
  await consume(enviaFormRecibeJson(url, formulario))
13
  location.href = nuevaVista
14
 }
15
}

2. js / lib / consume.js

1
import { ProblemDetailsError } from "./ProblemDetailsError.js"
2
3
/**
4
 * Espera a que la promesa de un fetch termine. Si
5
 * hay error, lanza una excepción.
6
 * 
7
 * @param {Promise<Response> } servicio
8
 */
9
export async function consume(servicio) {
10
 const respuesta = await servicio
11
 if (respuesta.ok) {
12
  return respuesta
13
 } else {
14
  const contentType = respuesta.headers.get("Content-Type")
15
  if (
16
   contentType !== null && contentType.startsWith("application/problem+json")
17
  )
18
   throw new ProblemDetailsError(await respuesta.json())
19
  else
20
   throw new Error(respuesta.statusText)
21
 }
22
}

3. js / lib / descargaVista.js

1
import { consume } from "./consume.js"
2
import { muestraObjeto } from "./muestraObjeto.js"
3
import { recibeJson } from "./recibeJson.js"
4
5
/**
6
 * @param {string} url
7
 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
8
 *  | "CONNECT" | "HEAD" } metodoHttp
9
 */
10
export async function descargaVista(url, metodoHttp = "GET") {
11
 const respuesta = await consume(recibeJson(url, metodoHttp))
12
 const json = await respuesta.json()
13
 muestraObjeto(document, json)
14
 return json
15
}

4. js / lib / enviaFormRecibeJson.js

1
/**
2
 * Envía los datos de un formolario a la url usando la codificación
3
 * multipart/form-data.
4
 * @param {string} url
5
 * @param {HTMLFormElement} formulario
6
 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
7
 *  | "CONNECT" | "HEAD" } metodoHttp
8
 */
9
export function enviaFormRecibeJson(url, formulario, metodoHttp = "POST") {
10
 return fetch(
11
  url,
12
  {
13
   method: metodoHttp,
14
   headers: { "Accept": "application/json, application/problem+json" },
15
   body: new FormData(formulario)
16
  }
17
 )
18
}

5. js / lib / manejaErrores.js

1
import { muestraError } from "./muestraError.js"
2
3
/**
4
 * Intercepta Response.prototype.json para capturar errores de parseo
5
 * y asegurar que se reporten correctamente en navegadores Chromium.
6
 */
7
{
8
 const originalJson = Response.prototype.json
9
10
 Response.prototype.json = function () {
11
  // Llamamos al método original usando el contexto (this) de la respuesta
12
  return originalJson.apply(this, arguments)
13
   .catch((/** @type {any} */ error) => {
14
    // Corrige un error de Chrome que evita el manejo correcto de errores.
15
    throw new Error(error)
16
   })
17
 }
18
}
19
20
window.onerror = function (
21
  /** @type {string} */ _message,
22
  /** @type {string} */ _url,
23
  /** @type {number} */ _line,
24
  /** @type {number} */ _column,
25
  /** @type {Error} */ errorObject
26
) {
27
 muestraError(errorObject)
28
 return true
29
}
30
31
window.addEventListener('unhandledrejection', event => {
32
 muestraError(event.reason)
33
 event.preventDefault()
34
})
35

6. js / lib / muestraError.js

1
import { ProblemDetailsError } from "./ProblemDetailsError.js"
2
3
/**
4
 * Muestra los datos de una Error en la consola y en un cuadro de alerta.
5
 * @param { ProblemDetailsError | Error | null } error descripción del error.
6
 */
7
export function muestraError(error) {
8
9
 if (error === null) {
10
11
  console.error("Error")
12
  alert("Error")
13
14
 } else if (error instanceof ProblemDetailsError) {
15
16
  const problemDetails = error.problemDetails
17
18
  let mensaje =
19
   typeof problemDetails["title"] === "string" ? problemDetails["title"] : ""
20
  if (typeof problemDetails["detail"] === "string") {
21
   if (mensaje !== "") {
22
    mensaje += "\n\n"
23
   }
24
   mensaje += problemDetails["detail"]
25
  }
26
  if (mensaje === "") {
27
   mensaje = "Error"
28
  }
29
  console.error(error, problemDetails)
30
  alert(mensaje)
31
32
 } else {
33
34
  console.error(error)
35
  alert(error.message)
36
37
 }
38
39
}

7. js / lib / muestraObjeto.js

1
/**
2
 * @param {Document | HTMLElement | ShadowRoot} raizHtml
3
 * @param { any } objeto
4
 */
5
export function muestraObjeto(raizHtml, objeto) {
6
 for (const [nombre, definiciones] of Object.entries(objeto)) {
7
  if (Array.isArray(definiciones)) {
8
   muestraArray(raizHtml, nombre, definiciones)
9
  } else if (definiciones !== undefined && definiciones !== null) {
10
   muestraElemento(raizHtml, nombre, definiciones)
11
  }
12
 }
13
}
14
15
/**
16
 * @param { string } nombre
17
 */
18
export function selectorDeNombre(nombre) {
19
 return `[id="${nombre}"],[name="${nombre}"],[data-name="${nombre}"]`
20
}
21
22
/**
23
 * @param { Document | HTMLElement | ShadowRoot } raizHtml
24
 * @param { string } propiedad
25
 * @param {any[]} valores
26
 */
27
function muestraArray(raizHtml, propiedad, valores) {
28
 const conjunto = new Set(valores)
29
 const elementos = raizHtml.querySelectorAll(selectorDeNombre(propiedad))
30
 if (elementos.length === 1 && elementos[0] instanceof HTMLSelectElement) {
31
  muestraOptions(elementos[0], conjunto)
32
 } else {
33
  muestraInputs(elementos, conjunto)
34
 }
35
36
}
37
38
/**
39
 * @param {HTMLSelectElement} select
40
 * @param {Set<any>} conjunto
41
 */
42
function muestraOptions(select, conjunto) {
43
 for (let i = 0, options = select.options, len = options.length; i < len; i++) {
44
  const option = options[i]
45
  option.selected = conjunto.has(option.value)
46
 }
47
}
48
49
/**
50
 * @param {NodeListOf<Element>} elementos
51
 * @param {Set<any>} conjunto
52
 */
53
function muestraInputs(elementos, conjunto) {
54
 for (let i = 0, len = elementos.length; i < len; i++) {
55
  const elemento = elementos[i]
56
  if (elemento instanceof HTMLInputElement) {
57
   elemento.checked = conjunto.has(elemento.value)
58
  }
59
 }
60
}
61
62
const data_ = "data-"
63
const dataLength = data_.length
64
65
/**
66
 * @param {Document | HTMLElement | ShadowRoot} raizHtml
67
 * @param {string} nombre
68
 * @param {{ [s: string]: any; } } definiciones
69
 */
70
function muestraElemento(raizHtml, nombre, definiciones) {
71
 const elemento = raizHtml.querySelector(selectorDeNombre(nombre))
72
 if (elemento !== null) {
73
  for (const [propiedad, valor] of Object.entries(definiciones)) {
74
   if (propiedad in elemento) {
75
    elemento[propiedad] = valor
76
   } else if (
77
    propiedad.length > dataLength
78
    && propiedad.startsWith(data_)
79
    && elemento instanceof HTMLElement
80
   ) {
81
    elemento.dataset[propiedad.substring(dataLength)] = valor
82
   }
83
  }
84
 }
85
}

8. js / lib / ProblemDetailsError.js

1
export class ProblemDetailsError extends Error {
2
3
 /**
4
  * Detalle de los errores devueltos por un servicio.
5
  * Crea una instancia de ProblemDetailsError.
6
  * @param {object} problemDetails Objeto con la descripcipon del error.
7
  */
8
 constructor(problemDetails) {
9
10
  super(typeof problemDetails["detail"] === "string"
11
   ? problemDetails["detail"]
12
   : (typeof problemDetails["title"] === "string"
13
    ? problemDetails["title"]
14
    : "Error"))
15
16
  this.problemDetails = problemDetails
17
18
 }
19
20
}

9. js / lib / recibeJson.js

1
2
/**
3
 * @param {string} url
4
 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
5
 *  | "CONNECT" | "HEAD" } metodoHttp
6
 */
7
export async function recibeJson(url, metodoHttp = "GET") {
8
 return fetch(
9
  url,
10
  {
11
   method: metodoHttp,
12
   headers: { "Accept": "application/json, application/problem+json" }
13
  }
14
 )
15
}

10. js / lib / submitAccion.js

1
import { consume } from "./consume.js"
2
import { enviaFormRecibeJson } from "./enviaFormRecibeJson.js"
3
4
/**
5
 * @param {Event} event
6
 * @param {string} url
7
 * @param {HTMLFormElement} formulario
8
 * @param {string} nuevaVista
9
 */
10
export async function submitAccion(event, url, formulario, nuevaVista) {
11
 event.preventDefault()
12
 await consume(enviaFormRecibeJson(url, formulario))
13
 location.href = nuevaVista
14
}

N. Carpeta « errors »

Versión para imprimir.

A. errors / campoenblanco.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>Campo en blanco</title>
10
11
</head>
12
13
<body>
14
15
 <h1>Campo en blanco</h1>
16
17
 <p>Pon texto en el campo obligatorio que está en blanco.</p>
18
19
</body>
20
21
</html>

B. errors / campoenteroenblanco.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>Campo entero en blanco</title>
10
11
</head>
12
13
<body>
14
15
 <h1>Campo entero en blanco</h1>
16
17
 <p>
18
  Pon un número entero en el campo que pide un valor entero obligatorio y está
19
  en blanco.
20
 </p>
21
22
</body>
23
24
</html>

C. errors / camponumericoenblanco.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>Campo numérico en blanco</title>
10
11
</head>
12
13
<body>
14
15
 <h1>Campo numérico en blanco</h1>
16
17
 <p>
18
  Pon un número en el campo que pide un valor numérico obligatorio y está
19
  en blanco.
20
 </p>
21
22
</body>
23
24
</html>

D. errors / entidadnoencontrada.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>Registro no encontrado</title>
10
11
</head>
12
13
<body>
14
15
 <h1>Registro no encontrado</h1>
16
17
 <p>No se encontró ningún registro con el id solicitado.</p>
18
19
</body>
20
21
</html>

E. errors / 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>

F. errors / faltavalor.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 un valor</title>
10
11
</head>
12
13
<body>
14
15
 <h1>Falta un valor</h1>
16
17
 <p>La solicitud no tiene un valor obligatorio.</p>
18
19
</body>
20
21
</html>

G. errors / 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>

O. 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
  ]
18
}

P. Resumen