5. PWA básica

Versión para imprimir.

A. Introduccion

B. Hazlo funcionar (con videos)

  1. Prueba e instala, de preferencia con Chrome, el sitio https://gilpgpwa.github.io/.

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

  3. Crea una cuenta de email con el nombre de tu sitio, por ejemplo, miapp@google.com

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

  5. Crea un repositorio nuevo. En el nombre del repositorio debes poner el nombre de tu cuenta seguido por .github.io; por ejemplo miapp.github.io

  6. Importa el proyecto de GitHub a Visual Studio Code

  7. Edita los archivos que desees.

  8. El archivo sw.js tiene una lista de los archivos que se instalan. El archivo instruccionesListadoSw.txt te indica como generarla usando Visual Studio Code.

  9. Crea los íconos del proyecto con https://www.photopea.com/.

  10. Crea los íconos enmascarables con https://maskable.app/ a partir del archivo «icono2048.png».

  11. Coloca el archivo favicon.ico en la raíz del proyecto.

  12. Coloca los otros íconos en la carpeta img y asegúrate de que estén declarados en el archivo site.webmanifest.

  13. El archivo sw.js tiene una lista de los archivos que se instalan. El archivo instruccionesListadoSw.txt te indica como generarla usando Visual Studio Code.

  14. Cada vez que modifiques los archivos, debes modificar el valor de VERSION en el archivo sw.js para poder ver los cambios en el navegador.

  15. Prueba tu PWA.

  16. Publica tu PWA.

  17. Instala y usa tu PWA en Windows. Aunque en este video se recomienda usar Edge, al momento de actualizar el contenido, la opción más recomendada es Chrome para que te muestre las descripciones y las capturas de pantalla.

  18. Instala y usa tu PWA en Android. Al momento de actualizar las notas, tal vez no te aparezca el botón para instalar y tengas que seleccionar la acción de agregar a la pantalla principal que aparece en el menú de extensión de Chrome.

  19. Instala y usa tu PWA en iOS (iPhone y iPad).

C. Hazlo funcionar (texto)

  1. Prueba e instala, de preferencia con Chrome, el sitio https://gilpgpwa.github.io/.

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

  3. Crea tu proyecto en GitHub pages:

    1. Crea una cuenta de email con el nombre de tu sitio, por ejemplo, miapp@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 miapp.

    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 *. Debes usar el nombre de tu cuenta seguido por .github.io; por ejemplo miapp.github.io

      • Mantén la selección Public.

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

    5. Entra al repositorio y selecciona ⚙️ Settings, luego selecciona 📁 Pages y en la sección Branches selecciona la carpeta donde se ubicará la carpeta. De preferencia selecciona / (root) para que coloques la página en la raíz del proyecto.Si haces cambios, clic en Save.

    6. En la misma pestaña de ⚙️ Settings selecciona ◯ Actions, luego selecciona General y en Actions permissions selecciona el rediobotón Allow xxxx, and select non-xxxx, actions and reusable workflows y abajo la casilla de verificación Allow actions created by GitHub y cliquea el botón Save.

  4. Importa el proyecto de GitHub a Visual Studio Code:

    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.

  5. Edita los archivos que desees.

  6. Crea los íconos del proyecto con https://www.photopea.com/.

  7. Crea los íconos enmascarables con https://maskable.app/ a partir del archivo «icono2048.png».

  8. Coloca el archivo favicon.ico en la raíz del proyecto.

  9. Coloca los otros íconos en la carpeta img y asegúrate de que estén declarados en el archivo site.webmanifest.

  10. El archivo sw.js tiene una lista de los archivos que se instalan. El archivo instruccionesListadoSw.txt te indica como generarla usando Visual Studio Code.

  11. Cada vez que modifiques los archivos, debes modificar el valor de VERSION en el archivo sw.js para poder ver los cambios en el navegador.

  12. Haz clic derecho en index.html, selecciona Open with Live Server y se abre el navegador para que puedas probar localmente el ejemplo.

  13. Haz al menos una captura de la ventana de tu aplicación con orientación vertical y otra con orientación horizontal, todas ellas de 320 px como mínimo y 3,840 px como máximo.

  14. Coloca las capturas en la carpeta img y asegúrate de que estén declaradas en el archivo site.webmanifest.

  15. Cuando desarrolles, es incómodo modificar la versión cada que realizas cambios; en vez de ello desinstala la app:

    1. Abre las herramientas de depuración haciendo clic derecho en la página y selecciona Inspeccionar (o Inspect si aparece en inglés).

    2. En la Pestaña Aplicación (o Application en inglés) selecciona Almacenamoento (o Storage en inglés). Cliquea Borrar datos del sitio.

    3. Recarga la app, 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.

    4. Tanbién puedes usar la combinación de teclas Ctrl+Mayúsculas+r para forzar que se actualice temporalmente el navegador en caso de que no se vean los cambios.

    5. En la Pestaña Aplicación (o Application en inglés) selecciona Archivo de manifiesto (o Manifest file en inglés). Esta herramienta analiza la estructura del archivo de manifiesto y te indica si hay un error.

    6. En la Pestaña Aplicación (o Application en inglés) selecciona Almacenamiento en caché (o Cache storage en inglés). Aquí puedes revisar si el caché de la aplicación se llenó correctamente. En caso de que esté vacío, es que hubo algún error durante la carga y la app se ejecuta más lenta.

  16. Cuando usas GitHub pages, antes de subir los archivos, no debes modificar el valor de VERSION en el archivo sw.js.

  17. 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
  18. Si usas GitHub pages:

    1. Entra a la página de tu repositorio y abajo a la derecha, selecciona el enlace github-pages.

    2. Se muestran los despliegues realizados. Recarga la página hasta que apareca el mensaje de tu último push con una palomita dentro de un círculo verde.

    3. A partir de este momento, espera al menos 11 minutos para modificar el valor de VERSION en el archivo sw.js y volver a subir el proyecto.

  19. Instala y usa tu PWA en Windows. Aunque en este video se recomienda usar Edge, al momento de actualizar el contenido, la opción más recomendada es Chrome para que te muestre las descripciones y las capturas de pantalla.

  20. Instala y usa tu PWA en Android. Al momento de actualizar las notas, tal vez no te aparezca el botón para instalar y tengas que seleccionar la acción de agregar a la pantalla principal que aparece en el menú de extensión de Chrome.

  21. Instala y usa tu PWA en iOS (iPhone y iPad).

D. Archivos

Haz clic en los triángulos para expandir las carpetas

E. index.html

1
<!DOCTYPE html>
2
<html lang="es">
3
4
<head>
5
6
 <meta charset="UTF-8">
7
8
 <title>PWA Básica</title>
9
10
 <!-- Resumen para los motores de búsqueda. -->
11
 <meta name="description" content="Ejemplo de PWA">
12
13
 <script type="module" src="js/lib/registraServiceWorker.js"></script>
14
15
 <meta name="viewport" content="width=device-width">
16
17
 <!-- Color de la barra de navegación de Chrome en dispositivos móviles. -->
18
 <meta name="theme-color" content="#cbc693">
19
20
 <!-- Ícono para la página web, que normalmente se pone en la raíz del sitio.
21
  Puede ser diferente para cada página. -->
22
 <link rel="icon" sizes="32x32" href="favicon.ico">
23
24
 <!-- Configuración de la PWA para Chrome, Edge y Safari.
25
  Debe ponerse en todas las páginas. -->
26
 <link rel="manifest" href="site.webmanifest">
27
28
 <link rel="stylesheet" href="css/estilos.css">
29
30
</head>
31
32
<body>
33
34
 <h1>PWA Básica</h1>
35
36
 <p>Hola mundo.</p>
37
38
</body>
39
40
</html>

F. Carpeta « css »

Versión para imprimir.

A. css / estilos.css

1
html {
2
 color-scheme: light dark;
3
 font-family: sans-serif;
4
}

G. Carpeta « js »

Versión para imprimir.

A. Carpeta « js / lib »

1. js / lib / registraServiceWorker.js

1
const nombreDeServiceWorker = "sw.js"
2
3
try {
4
 navigator.serviceWorker.register(nombreDeServiceWorker)
5
  .then(registro => {
6
   console.log(nombreDeServiceWorker, "registrado.")
7
   console.log(registro)
8
  })
9
  .catch(error => console.log(error))
10
} catch (error) {
11
 console.log(error)
12
}

H. site.webmanifest

  • Este archivo sirve para configurar los instaladores de la aplicación.

Explicación de las propiedades

short_name

Nombre corto. Normalmente se despliega en dispositivos móviles. Máximo 20 caracteres.

name

Nombre largo. Normalmente se despliega en computadoras de escritorio. Máximo 30 caracteres.

id

Identificador del archivo de instalación. Normalmente es la ruta del archivo inicial de la app.

start_url

Ruta del archivo inicial de la app.

display

Forma de mostrar la app. El término standalone significa que no se muestra la barra de navegación del navegador web.

theme_color

Color de la barra de estado (en dispositivos móviles) o de título (en computadoras de escritorio) de la app.

background_color

Color de fondo de la pantalla desplah en dispositivos móviles.

description

Describe el propósito de la aplicación. Aparece en el cuadro de diálogo que muestra el navegador al instalar la app.

screenshots

Listado de máximo 8 capturas de pantalla. Aparecen en el cuadro de diálogo que muestra el navegador al instalar la app. Debes incluir al menos una con "form_factor": "wide" y otra con "form_factor": "narrow".

icons

Listado de íconos en distintas resoluciones para los instaladores de la app. Se selecciona el que se vea mejor según las característocas del dispositivo.

src

Url de la imagen dentro de la app.

sizes

Dimensiones en pixeles de la imagen, anchoxalto.

type

Tipo mime de la imagen.

purpose

Forma en que se usa la imagen.

maskable

La imagen puede recortarse de forma segura para tomar distintas formas, como círculos, gotas, cuadrados con esquinas redondeadas, etc. Normalmente se usa para dispositivos móviles.

any

No se puede asegurar nada sobre la imagen. Normalmente se usa para dispositivos de escritorio.

Normalmente debe proporcionarse un juego de íconos con purpose any y otro juego de íconos con purpose maskable.

form_factor

Orientación de una screenshot.

wide

La screenshot tiene una orientación horizontal. Normalmente la creenshot se usa para dispositivos de escritorio.

narrow

La screenshot tiene una orientación vertical. Normalmente la creenshot se usa para dispositivos móviles.

Debes incluir al menos una screenshot con "form_factor": "wide" y otra con "form_factor": "narrow".

label

Descripción de una screenshot. Aparece en el cuadro de diálogo que muestra el navegador al instalar la app.

1
{
2
 "short_name": "PWA",
3
 "name": "Ejemplo de PWA",
4
 "id": "/index.html",
5
 "start_url": "/index.html",
6
 "display": "standalone",
7
 "theme_color": "#cbc693",
8
 "background_color": "#ffffff",
9
 "description": "Ejemplos básico de PWA.",
10
 "screenshots": [
11
  {
12
   "src": "/img/screenshot_horizontal.png",
13
   "sizes": "1507x777",
14
   "type": "image/png",
15
   "form_factor": "wide",
16
   "label": "PWA Básica"
17
  },
18
  {
19
   "src": "/img/screenshot_vertical.png",
20
   "sizes": "591x980",
21
   "type": "image/png",
22
   "form_factor": "narrow",
23
   "label": "PWA Básica (2)"
24
  }
25
 ],
26
 "icons": [
27
  {
28
   "src": "/img/maskable_icon_x48.png",
29
   "sizes": "48x48",
30
   "type": "image/png",
31
   "purpose": "any"
32
  },
33
  {
34
   "src": "/img/maskable_icon_x72.png",
35
   "sizes": "72x72",
36
   "type": "image/png",
37
   "purpose": "any"
38
  },
39
  {
40
   "src": "/img/maskable_icon_x96.png",
41
   "sizes": "96x96",
42
   "type": "image/png",
43
   "purpose": "any"
44
  },
45
  {
46
   "src": "/img/maskable_icon_x128.png",
47
   "sizes": "128x128",
48
   "type": "image/png",
49
   "purpose": "any"
50
  },
51
  {
52
   "src": "/img/maskable_icon_x192.png",
53
   "sizes": "192x192",
54
   "type": "image/png",
55
   "purpose": "any"
56
  },
57
  {
58
   "src": "/img/maskable_icon_x384.png",
59
   "sizes": "384x384",
60
   "type": "image/png",
61
   "purpose": "any"
62
  },
63
  {
64
   "src": "/img/maskable_icon_x512.png",
65
   "sizes": "512x512",
66
   "type": "image/png",
67
   "purpose": "any"
68
  },
69
  {
70
   "src": "/img/maskable_icon.png",
71
   "sizes": "2730x2730",
72
   "type": "image/png",
73
   "purpose": "any"
74
  },
75
  {
76
   "src": "/img/icono2048.png",
77
   "sizes": "2048x2048",
78
   "type": "image/png",
79
   "purpose": "any"
80
  },
81
  {
82
   "src": "/img/maskable_icon_x48.png",
83
   "sizes": "48x48",
84
   "type": "image/png",
85
   "purpose": "maskable"
86
  },
87
  {
88
   "src": "/img/maskable_icon_x72.png",
89
   "sizes": "72x72",
90
   "type": "image/png",
91
   "purpose": "maskable"
92
  },
93
  {
94
   "src": "/img/maskable_icon_x96.png",
95
   "sizes": "96x96",
96
   "type": "image/png",
97
   "purpose": "maskable"
98
  },
99
  {
100
   "src": "/img/maskable_icon_x128.png",
101
   "sizes": "128x128",
102
   "type": "image/png",
103
   "purpose": "maskable"
104
  },
105
  {
106
   "src": "/img/maskable_icon_x192.png",
107
   "sizes": "192x192",
108
   "type": "image/png",
109
   "purpose": "maskable"
110
  },
111
  {
112
   "src": "/img/maskable_icon_x384.png",
113
   "sizes": "384x384",
114
   "type": "image/png",
115
   "purpose": "maskable"
116
  },
117
  {
118
   "src": "/img/maskable_icon_x512.png",
119
   "sizes": "512x512",
120
   "type": "image/png",
121
   "purpose": "maskable"
122
  },
123
  {
124
   "src": "/img/maskable_icon.png",
125
   "sizes": "2730x2730",
126
   "type": "image/png",
127
   "purpose": "maskable"
128
  }
129
 ]
130
}

I. instruccionesListadoSw.txt

1
Generar el listado de archivos del sw.js desde Visual Studio Code.
2
1. Abrir una terminal desde el menú 
3
    Terminal > New Terminal
4
5
2. Desde la terminal introducir la orden:
6
    Get-ChildItem -path . -Recurse | Select Directory,Name | Out-File archivos.txt
7
8
3. Abrir el archivo generado, que se llama
9
    archivos.txt
10
   y sobre este, realizar los pasos que siguen:
11
12
4. Quita del archivo archivos.txt:
13
    * el encabezado,
14
    * todas las carpetas,
15
    * todos los archivos dentro de .vscode como:
16
       * el archivo .vscode/settings.json,
17
       * el archivo .vscode/launch.json,
18
    * el archivo .htaccess,
19
    * el archivo archivos.txt,
20
    * este archivo (instruccionesListadoSw.txt),
21
    * el archivo jsconfig.json,
22
    * el archivo sw.js,
23
    * el archivo de la base de datos, que termina en ".db" y
24
      está en la carpeta php,
25
    * todos los archivos de php y
26
    * las líneas en blanco del final
27
28
5. Cambia los \ por / desde Visual Studio Code con las siguientes
29
   combinaciones de teclas:
30
31
    Ctrl+H En el diálogo que aparece introduce lo siguiente:
32
    Find:\
33
    Replace:/
34
35
    Clic en el icono Reemplaza todo o Replace All y luego teclea ESC
36
 
37
6. Coloca las comillas y coma del final de cada línea desde Visual
38
   Studio Code con las siguientes combinaciones de teclas:
39
40
    Ctrl+H En el diálogo que aparece, selecciona el botón
41
            ".*"
42
           e introduce lo siguiente:
43
    Find:\s*$
44
    Replace:",
45
46
    Clic en el icono Reemplaza todo o Replace All y luego teclea ESC
47
48
7. Marca la carpeta inicial, presiona la combinación de teclas:
49
50
    Shift+Ctrl+L
51
52
    borra la selección, teclea " y luego ESC
53
54
8. Cambia las secuencias de espacios por / con las siguientes
55
   combinaciones de teclas:
56
57
    Ctrl+H En el diálogo que aparece, selecciona el botón
58
            ".*"
59
           e introduce lo siguiente:
60
    Find:\s+
61
    Replace:/
62
63
    Clic en el icono Reemplaza todo o Replace All y luego teclea ESC
64
65
9. Cambia las "/ por " con las siguientes combinaciones de teclas:
66
67
    Ctrl+H En el diálogo que aparece, quita la selección del botón
68
            ".*"
69
           e introduce lo siguiente:
70
    Find:"/
71
    Replace:"
72
73
    Clic en el icono Reemplaza todo o Replace All y luego teclea ESC
74
75
10. Copia el texto al archivo
76
     sw.js
77
    en el contenido del arreglo llamado ARCHIVOS, pero recuerda
78
    mantener el último elemento, que dice:
79
     "/"

J. archivos.txt

1
"favicon.ico",
2
"index.html",
3
"jsconfig.json",
4
"site.webmanifest",
5
"css/estilos.css",
6
"img/icono2048.png",
7
"img/maskable_icon.png",
8
"img/maskable_icon_x128.png",
9
"img/maskable_icon_x192.png",
10
"img/maskable_icon_x384.png",
11
"img/maskable_icon_x48.png",
12
"img/maskable_icon_x512.png",
13
"img/maskable_icon_x72.png",
14
"img/maskable_icon_x96.png",
15
"img/screenshot_horizontal.png",
16
"img/screenshot_vertical.png",
17
"js/lib/registraServiceWorker.js",

K. sw.js

1
/* Este archivo debe estar colocado en la carpeta raíz del sitio.
2
 * 
3
 * Cualquier cambio en el contenido de este archivo hace que el service
4
 * worker se reinstale. */
5
6
/**
7
 * Cambia el número de la versión cuando cambia el contenido de los
8
 * archivos.
9
 * 
10
 * El número a la izquierda del punto (.), en este caso <q>1</q>, se
11
 * conoce como número mayor y se cambia cuando se realizan
12
 * modificaciones grandes o importantes.
13
 * 
14
 * El número a la derecha del punto (.), en este caso <q>00</q>, se
15
 * conoce como número menor y se cambia cuando se realizan
16
 * modificaciones menores.
17
 */
18
const VERSION = "4.01"
19
20
/** Nombre del archivo de cache. */
21
const CACHE = "ejemploPWA"
22
23
/**
24
 * Archivos requeridos para que la aplicación funcione fuera de
25
 * línea.
26
 */
27
const ARCHIVOS = [
28
 "favicon.ico",
29
 "index.html",
30
 "jsconfig.json",
31
 "site.webmanifest",
32
 "css/estilos.css",
33
 "img/icono2048.png",
34
 "img/maskable_icon.png",
35
 "img/maskable_icon_x128.png",
36
 "img/maskable_icon_x192.png",
37
 "img/maskable_icon_x384.png",
38
 "img/maskable_icon_x48.png",
39
 "img/maskable_icon_x512.png",
40
 "img/maskable_icon_x72.png",
41
 "img/maskable_icon_x96.png",
42
 "img/screenshot_horizontal.png",
43
 "img/screenshot_vertical.png",
44
 "js/lib/registraServiceWorker.js",
45
 "/"
46
]
47
48
// Verifica si el código corre dentro de un service worker.
49
if (self instanceof ServiceWorkerGlobalScope) {
50
 // Evento al empezar a instalar el servide worker,
51
 self.addEventListener("install",
52
  (/** @type {ExtendableEvent} */ evt) => {
53
   console.log("El service worker se está instalando.")
54
   evt.waitUntil(llenaElCache())
55
  })
56
57
 // Evento al solicitar información a la red.
58
 self.addEventListener("fetch", (/** @type {FetchEvent} */ evt) => {
59
  if (evt.request.method === "GET") {
60
   evt.respondWith(buscaLaRespuestaEnElCache(evt))
61
  }
62
 })
63
64
 // Evento cuando el service worker se vuelve activo.
65
 self.addEventListener("activate",
66
  () => console.log("El service worker está activo."))
67
}
68
69
async function llenaElCache() {
70
 console.log("Intentando cargar caché:", CACHE)
71
 // Borra todos los cachés.
72
 const keys = await caches.keys()
73
 for (const key of keys) {
74
  await caches.delete(key)
75
 }
76
 // Abre el caché de este service worker.
77
 const cache = await caches.open(CACHE)
78
 // Carga el listado de ARCHIVOS.
79
 await cache.addAll(ARCHIVOS)
80
 console.log("Cache cargado:", CACHE)
81
 console.log("Versión:", VERSION)
82
}
83
84
/** @param {FetchEvent} evt */
85
async function buscaLaRespuestaEnElCache(evt) {
86
 // Abre el caché.
87
 const cache = await caches.open(CACHE)
88
 const request = evt.request
89
 /* Busca la respuesta a la solicitud en el contenido del caché, sin
90
  * tomar en cuenta la parte después del símbolo "?" en la URL. */
91
 const response = await cache.match(request, { ignoreSearch: true })
92
 if (response === undefined) {
93
  /* Si no la encuentra, empieza a descargar de la red y devuelve
94
   * la promesa. */
95
  return fetch(request)
96
 } else {
97
  // Si la encuentra, devuelve la respuesta encontrada en el caché.
98
  return response
99
 }
100
}

L. favicon.ico

favicon.ico

M. Carpeta « img »

Versión para imprimir.

A. img / icono2048.png

icono2048.png

B. img / maskable_icon.png

maskable_icon.png

C. img / maskable_icon_x128.png

maskable_icon_x128.png

D. img / maskable_icon_x192.png

maskable_icon_x192.png

E. img / maskable_icon_x384.png

maskable_icon_x384.png

F. img / maskable_icon_x48.png

maskable_icon_x48.png

G. img / maskable_icon_x512.png

maskable_icon_x512.png

H. img / maskable_icon_x72.png

maskable_icon_x72.png

I. img / maskable_icon_x96.png

maskable_icon_x96.png

J. img / screenshot_horizontal.png

screenshot_horizontal.png

K. img / screenshot_vertical.png

screenshot_vertical.png

N. .htaccess

1
AddType application/manifest+json .webmanifest
2
3
ExpiresActive On
4
5
Header set Cache-Control "max-age=1, must-revalidate"
6

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