| 1 | import { exportaAHtml } from "../lib/js/exportaAHtml.js" | 
| 2 | import { sincroniza } from "./sincroniza.js" | 
| 3 | |
| 4 | /** | 
| 5 | * Cada 20 segundos (2000 milisegundos) después de la última | 
| 6 | * sincronización, los datos se envían al servidor para volver a | 
| 7 | * sincronizarse con los datos del servidor. | 
| 8 | */ | 
| 9 | const MILISEGUNDOS_PARA_VOLVER_A_SINCRONIZAR = 20000 | 
| 10 | |
| 11 | /** | 
| 12 | * @param {HTMLUListElement} lista | 
| 13 | */ | 
| 14 | export function esperaUnPocoYSincroniza(lista) { | 
| 15 | setTimeout(() => sincroniza(lista), MILISEGUNDOS_PARA_VOLVER_A_SINCRONIZAR) | 
| 16 | } | 
| 17 | |
| 18 | exportaAHtml(esperaUnPocoYSincroniza) | 
| 1 | "use strict" // usa JavaScript en modo estricto. | 
| 2 | |
| 3 | const nombreDeServiceWorker = "sw.js" | 
| 4 | |
| 5 | try { | 
| 6 | navigator.serviceWorker.register(nombreDeServiceWorker) | 
| 7 | .then(registro => { | 
| 8 | console.log(nombreDeServiceWorker, "registrado.") | 
| 9 | console.log(registro) | 
| 10 | }) | 
| 11 | .catch(error => console.log(error)) | 
| 12 | } catch (error) { | 
| 13 | console.log(error) | 
| 14 | } | 
| 1 | import { exportaAHtml } from "../lib/js/exportaAHtml.js" | 
| 2 | import { htmlentities } from "../lib/js/htmlentities.js" | 
| 3 | |
| 4 | /** | 
| 5 | * @param {HTMLUListElement} lista | 
| 6 | * @param {import("./modelo/PASATIEMPO.js").PASATIEMPO[]} pasatiempos | 
| 7 | */ | 
| 8 | export function renderiza(lista, pasatiempos) { | 
| 9 | let render = "" | 
| 10 | for (const modelo of pasatiempos) { | 
| 11 | if (modelo.PAS_ID === undefined) | 
| 12 | throw new Error(`Falta PAS_ID de ${modelo.PAS_NOMBRE}.`) | 
| 13 | const nombre = htmlentities(modelo.PAS_NOMBRE) | 
| 14 | const searchParams = new URLSearchParams([["id", modelo.PAS_ID]]) | 
| 15 | const params = htmlentities(searchParams.toString()) | 
| 16 | render += /* html */ | 
| 17 | `<li> | 
| 18 | <p><a href="modifica.html?${params}">${nombre}</a></p> | 
| 19 | </li>` | 
| 20 | } | 
| 21 | lista.innerHTML = render | 
| 22 | } | 
| 23 | |
| 24 | exportaAHtml(renderiza) | 
| 1 | import { enviaJson } from "../lib/js/enviaJson.js" | 
| 2 | import { exportaAHtml } from "../lib/js/exportaAHtml.js" | 
| 3 | import { muestraError } from "../lib/js/muestraError.js" | 
| 4 | import { pasatiempoConsultaTodos } from "./bd/pasatiempoConsultaTodos.js" | 
| 5 | import { pasatiemposReemplaza } from "./bd/pasatiemposReemplaza.js" | 
| 6 | import { esperaUnPocoYSincroniza } from "./esperaUnPocoYSincroniza.js" | 
| 7 | import { validaPasatiempos } from "./modelo/validaPasatiempos.js" | 
| 8 | import { renderiza } from "./renderiza.js" | 
| 9 | |
| 10 | /** | 
| 11 | * @param {HTMLUListElement} lista | 
| 12 | */ | 
| 13 | export async function sincroniza(lista) { | 
| 14 | try { | 
| 15 | if (navigator.onLine) { | 
| 16 | const todos = await pasatiempoConsultaTodos() | 
| 17 | const respuesta = await enviaJson("srv/sincroniza.php", todos) | 
| 18 | const pasatiempos = validaPasatiempos(respuesta.body) | 
| 19 | await pasatiemposReemplaza(pasatiempos) | 
| 20 | renderiza(lista, pasatiempos) | 
| 21 | } | 
| 22 | } catch (error) { | 
| 23 | muestraError(error) | 
| 24 | } | 
| 25 | esperaUnPocoYSincroniza(lista) | 
| 26 | |
| 27 | } | 
| 28 | |
| 29 | exportaAHtml(sincroniza) | 
| 1 | export const ALMACEN_PASATIEMPO = "PASATIEMPO" | 
| 2 | export const PAS_ID = "PAS_ID" | 
| 3 | export const INDICE_NOMBRE = "INDICE_NOMBRE" | 
| 4 | export const PAS_NOMBRE = "PAS_NOMBRE" | 
| 5 | const BD_NOMBRE = "sincronizacion" | 
| 6 | const BD_VERSION = 1 | 
| 7 | |
| 8 | /** @type { Promise<IDBDatabase> } */ | 
| 9 | export const Bd = new Promise((resolve, reject) => { | 
| 10 | |
| 11 | /* Se solicita abrir la base de datos, indicando nombre y | 
| 12 | * número de versión. */ | 
| 13 | const solicitud = indexedDB.open(BD_NOMBRE, BD_VERSION) | 
| 14 | |
| 15 | // Si se presenta un error, rechaza la promesa. | 
| 16 | solicitud.onerror = () => reject(solicitud.error) | 
| 17 | |
| 18 | // Si se abre con éxito, devuelve una conexión a la base de datos. | 
| 19 | solicitud.onsuccess = () => resolve(solicitud.result) | 
| 20 | |
| 21 | // Si es necesario, se inicia una transacción para cambio de versión. | 
| 22 | solicitud.onupgradeneeded = () => { | 
| 23 | |
| 24 | const bd = solicitud.result | 
| 25 | |
| 26 | // Como hay cambio de versión, borra el almacén si es que existe. | 
| 27 | if (bd.objectStoreNames.contains(ALMACEN_PASATIEMPO)) { | 
| 28 | bd.deleteObjectStore(ALMACEN_PASATIEMPO) | 
| 29 | } | 
| 30 | |
| 31 | // Crea el almacén "PASATIEMPO" con el campo llave "PAS_ID". | 
| 32 | const almacenPasatiempo = | 
| 33 | bd.createObjectStore(ALMACEN_PASATIEMPO, { keyPath: PAS_ID }) | 
| 34 | |
| 35 | // Crea un índice ordenado por el campo "PAS_NOMBRE" que no acepta duplicados. | 
| 36 | almacenPasatiempo.createIndex(INDICE_NOMBRE, "PAS_NOMBRE") | 
| 37 | } | 
| 38 | |
| 39 | }) | 
| 1 | import { bdEjecuta } from "../../lib/js/bdEjecuta.js" | 
| 2 | import { creaIdCliente } from "../../lib/js/creaIdCliente.js" | 
| 3 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" | 
| 4 | import { validaNombre } from "../modelo/validaNombre.js" | 
| 5 | import { exportaAHtml } from "../../lib/js/exportaAHtml.js" | 
| 6 | |
| 7 | /** | 
| 8 | * @param {import("../modelo/PASATIEMPO.js").PASATIEMPO} modelo | 
| 9 | */ | 
| 10 | export async function pasatiempoAgrega(modelo) { | 
| 11 | validaNombre(modelo.PAS_NOMBRE) | 
| 12 | modelo.PAS_MODIFICACION = Date.now() | 
| 13 | modelo.PAS_ELIMINADO = 0 | 
| 14 | // Genera id único en internet. | 
| 15 | modelo.PAS_ID = creaIdCliente(Date.now().toString()) | 
| 16 | return bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => { | 
| 17 | const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO) | 
| 18 | almacenPasatiempo.add(modelo) | 
| 19 | }) | 
| 20 | } | 
| 21 | |
| 22 | exportaAHtml(pasatiempoAgrega) | 
| 1 | import { bdConsulta } from "../../lib/js/bdConsulta.js" | 
| 2 | import { exportaAHtml } from "../../lib/js/exportaAHtml.js" | 
| 3 | import { validaPasatiempo } from "../modelo/validaPasatiempo.js" | 
| 4 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" | 
| 5 | |
| 6 | /** | 
| 7 | * @param {string} id | 
| 8 | */ | 
| 9 | export async function pasatiempoBusca(id) { | 
| 10 | |
| 11 | return bdConsulta(Bd, [ALMACEN_PASATIEMPO], | 
| 12 | /** | 
| 13 | * @param {(resultado: import("../modelo/PASATIEMPO.js").PASATIEMPO|undefined) | 
| 14 | * => any} resolve | 
| 15 | */ | 
| 16 | (transaccion, resolve) => { | 
| 17 | |
| 18 | /* Pide el primer objeto de ALMACEN_PASATIEMPO que tenga como llave | 
| 19 | * primaria el valor del parámetro id. */ | 
| 20 | const consulta = transaccion.objectStore(ALMACEN_PASATIEMPO).get(id) | 
| 21 | |
| 22 | // onsuccess se invoca solo una vez, devolviendo el objeto solicitado. | 
| 23 | consulta.onsuccess = () => { | 
| 24 | /* Se recupera el objeto solicitado usando | 
| 25 | * consulta.result | 
| 26 | * Si el objeto no se encuentra se recupera undefined. */ | 
| 27 | const objeto = consulta.result | 
| 28 | if (objeto !== undefined) { | 
| 29 | const modelo = validaPasatiempo(objeto) | 
| 30 | if (modelo.PAS_ELIMINADO === 0) { | 
| 31 | resolve(modelo) | 
| 32 | return | 
| 33 | } | 
| 34 | } | 
| 35 | resolve(undefined) | 
| 36 | |
| 37 | } | 
| 38 | |
| 39 | }) | 
| 40 | |
| 41 | } | 
| 42 | |
| 43 | exportaAHtml(pasatiempoBusca) | 
| 1 | import { bdConsulta } from "../../lib/js/bdConsulta.js" | 
| 2 | import { exportaAHtml } from "../../lib/js/exportaAHtml.js" | 
| 3 | import { validaPasatiempo } from "../modelo/validaPasatiempo.js" | 
| 4 | import { ALMACEN_PASATIEMPO, Bd, INDICE_NOMBRE } from "./Bd.js" | 
| 5 | |
| 6 | export async function pasatiempoConsultaNoEliminados() { | 
| 7 | |
| 8 | return bdConsulta(Bd, [ALMACEN_PASATIEMPO], | 
| 9 | /** | 
| 10 | * @param {(resultado: import("../modelo/PASATIEMPO.js").PASATIEMPO[])=>void | 
| 11 | * } resolve | 
| 12 | */ | 
| 13 | (transaccion, resolve) => { | 
| 14 | |
| 15 | const resultado = [] | 
| 16 | |
| 17 | const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO) | 
| 18 | |
| 19 | // Usa el índice INDICE_NOMBRE para recuperar los datos ordenados. | 
| 20 | const indiceNombre = almacenPasatiempo.index(INDICE_NOMBRE) | 
| 21 | |
| 22 | // Pide un cursor para recorrer cada objeto que devuelve la consulta. | 
| 23 | const consulta = indiceNombre.openCursor() | 
| 24 | |
| 25 | /* onsuccess se invoca por cada uno de los objetos de la consulta y una vez | 
| 26 | * cuando se acaban dichos objetos. */ | 
| 27 | consulta.onsuccess = () => { | 
| 28 | /* El cursor correspondiente al objeto se recupera usando | 
| 29 | * consulta.result */ | 
| 30 | const cursor = consulta.result | 
| 31 | if (cursor === null) { | 
| 32 | /* Si el cursor vale null, ya no hay más objetos que procesar; por lo | 
| 33 | * mismo, se devuelve el resultado con los pasatiempos recuperados, usando | 
| 34 | * resolve(resultado). */ | 
| 35 | resolve(resultado) | 
| 36 | } else { | 
| 37 | /* Si el cursor no vale null y hay más objetos, el siguiente se obtiene con | 
| 38 | * cursor.value */ | 
| 39 | const modelo = validaPasatiempo(cursor.value) | 
| 40 | if (modelo.PAS_ELIMINADO === 0) { | 
| 41 | resultado.push(modelo) | 
| 42 | } | 
| 43 | /* Busca el siguiente objeto de la consulta, que se recupera la siguiente | 
| 44 | * vez que se invoque la función onsuccess. */ | 
| 45 | cursor.continue() | 
| 46 | } | 
| 47 | } | 
| 48 | |
| 49 | }) | 
| 50 | |
| 51 | } | 
| 52 | |
| 53 | exportaAHtml(pasatiempoConsultaNoEliminados) | 
| 1 | import { bdConsulta } from "../../lib/js/bdConsulta.js" | 
| 2 | import { validaPasatiempo } from "../modelo/validaPasatiempo.js" | 
| 3 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" | 
| 4 | |
| 5 | /** | 
| 6 | * Lista todos los objetos, incluyendo los que tienen borrado lógico. | 
| 7 | */ | 
| 8 | export async function pasatiempoConsultaTodos() { | 
| 9 | |
| 10 | return bdConsulta(Bd, [ALMACEN_PASATIEMPO], | 
| 11 | /** | 
| 12 | * @param {(resultado: import("../modelo/PASATIEMPO.js").PASATIEMPO[])=>void | 
| 13 | * } resolve | 
| 14 | */ | 
| 15 | (transaccion, resolve) => { | 
| 16 | |
| 17 | const resultado = [] | 
| 18 | |
| 19 | // Pide un cursor para recorrer cada objeto que devuelve la consulta. | 
| 20 | const consulta = transaccion.objectStore(ALMACEN_PASATIEMPO).openCursor() | 
| 21 | |
| 22 | /* onsuccess se invoca por cada uno de los objetos de la consulta y una vez | 
| 23 | * cuando se acaban dichos objetos. */ | 
| 24 | consulta.onsuccess = () => { | 
| 25 | /* El cursor correspondiente al objeto se recupera usando | 
| 26 | * consulta.result */ | 
| 27 | const cursor = consulta.result | 
| 28 | if (cursor === null) { | 
| 29 | /* Si el cursor vale null, ya no hay más objetos que procesar; por lo | 
| 30 | * mismo, se devuelve el resultado con los pasatiempos recuperados, usando | 
| 31 | * resolve(resultado). */ | 
| 32 | resolve(resultado) | 
| 33 | } else { | 
| 34 | /* Si el cursor no vale null y hay más objetos, el siguiente se obtiene con | 
| 35 | * cursor.value*/ | 
| 36 | resultado.push(validaPasatiempo(cursor.value)) | 
| 37 | /* Busca el siguiente objeto de la consulta, que se recupera la siguiente | 
| 38 | * vez que se invoque la función onsuccess. */ | 
| 39 | cursor.continue() | 
| 40 | } | 
| 41 | } | 
| 42 | |
| 43 | }) | 
| 44 | |
| 45 | } | 
| 1 | import { bdEjecuta } from "../../lib/js/bdEjecuta.js" | 
| 2 | import { exportaAHtml } from "../../lib/js/exportaAHtml.js" | 
| 3 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" | 
| 4 | import { pasatiempoBusca } from "./pasatiempoBusca.js" | 
| 5 | |
| 6 | /** | 
| 7 | * @param { string } id | 
| 8 | */ | 
| 9 | export async function pasatiempoElimina(id) { | 
| 10 | const modelo = await pasatiempoBusca(id) | 
| 11 | if (modelo !== undefined) { | 
| 12 | modelo.PAS_MODIFICACION = Date.now() | 
| 13 | modelo.PAS_ELIMINADO = 1 | 
| 14 | return bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => { | 
| 15 | const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO) | 
| 16 | almacenPasatiempo.put(modelo) | 
| 17 | }) | 
| 18 | } | 
| 19 | } | 
| 20 | |
| 21 | exportaAHtml(pasatiempoElimina) | 
| 1 | import { bdEjecuta } from "../../lib/js/bdEjecuta.js" | 
| 2 | import { exportaAHtml } from "../../lib/js/exportaAHtml.js" | 
| 3 | import { validaId } from "../modelo/validaId.js" | 
| 4 | import { validaNombre } from "../modelo/validaNombre.js" | 
| 5 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" | 
| 6 | import { pasatiempoBusca } from "./pasatiempoBusca.js" | 
| 7 | |
| 8 | /** | 
| 9 | * @param { import("../modelo/PASATIEMPO.js").PASATIEMPO } modelo | 
| 10 | */ | 
| 11 | export async function pasatiempoModifica(modelo) { | 
| 12 | validaNombre(modelo.PAS_NOMBRE) | 
| 13 | if (modelo.PAS_ID === undefined) | 
| 14 | throw new Error(`Falta PAS_ID de ${modelo.PAS_NOMBRE}.`) | 
| 15 | validaId(modelo.PAS_ID) | 
| 16 | const anterior = await pasatiempoBusca(modelo.PAS_ID) | 
| 17 | if (anterior !== undefined) { | 
| 18 | modelo.PAS_MODIFICACION = Date.now() | 
| 19 | modelo.PAS_ELIMINADO = 0 | 
| 20 | return bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => { | 
| 21 | const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO) | 
| 22 | almacenPasatiempo.put(modelo) | 
| 23 | }) | 
| 24 | } | 
| 25 | } | 
| 26 | |
| 27 | exportaAHtml(pasatiempoModifica) | 
| 1 | import { bdEjecuta } from "../../lib/js/bdEjecuta.js" | 
| 2 | import { ALMACEN_PASATIEMPO, Bd } from "./Bd.js" | 
| 3 | |
| 4 | /** | 
| 5 | * Borra el contenido del almacén PASATIEMPO y guarda nuevospasatiempos. | 
| 6 | * @param {import("../modelo/PASATIEMPO.js").PASATIEMPO[]} nuevospasatiempos | 
| 7 | */ | 
| 8 | export async function pasatiemposReemplaza(nuevospasatiempos) { | 
| 9 | return bdEjecuta(Bd, [ALMACEN_PASATIEMPO], transaccion => { | 
| 10 | const almacenPasatiempo = transaccion.objectStore(ALMACEN_PASATIEMPO) | 
| 11 | almacenPasatiempo.clear() | 
| 12 | for (const objeto of nuevospasatiempos) { | 
| 13 | almacenPasatiempo.add(objeto) | 
| 14 | } | 
| 15 | }) | 
| 16 | } | 
| 1 | /** | 
| 2 | * @typedef {Object} PASATIEMPO | 
| 3 | * @property {string} [PAS_ID] | 
| 4 | * @property {string} PAS_NOMBRE | 
| 5 | * @property {number} [PAS_MODIFICACION] | 
| 6 | * @property {number} [PAS_ELIMINADO] | 
| 7 | */ | 
| 1 | /** | 
| 2 | * @param {string} id | 
| 3 | */ | 
| 4 | export function validaId(id) { | 
| 5 | if (id === "") | 
| 6 | throw new Error("Falta el id.") | 
| 7 | } | 
| 1 | /** | 
| 2 | * @param {string} nombre | 
| 3 | */ | 
| 4 | export function validaNombre(nombre) { | 
| 5 | if (nombre === "") | 
| 6 | throw new Error("Falta el nombre.") | 
| 7 | } | 
| 1 | /** | 
| 2 | * @param { any } objeto | 
| 3 | * @returns {import("./PASATIEMPO.js").PASATIEMPO} | 
| 4 | */ | 
| 5 | export function validaPasatiempo(objeto) { | 
| 6 | |
| 7 | if (typeof objeto.PAS_ID !== "string") | 
| 8 | throw new Error("El id debe ser texto.") | 
| 9 | |
| 10 | if (typeof objeto.PAS_NOMBRE !== "string") | 
| 11 | throw new Error("El nombre debe ser texto.") | 
| 12 | |
| 13 | if (typeof objeto.PAS_MODIFICACION !== "number") | 
| 14 | throw new Error("El campo modificacion debe ser número.") | 
| 15 | |
| 16 | if (typeof objeto.PAS_ELIMINADO !== "number") | 
| 17 | throw new Error("El campo eliminado debe ser número.") | 
| 18 | |
| 19 | return objeto | 
| 20 | |
| 21 | } | 
| 1 | import { validaPasatiempo } from "./validaPasatiempo.js" | 
| 2 | |
| 3 | /** | 
| 4 | * @param { any } objetos | 
| 5 | * @returns {import("./PASATIEMPO.js").PASATIEMPO[]} | 
| 6 | */ | 
| 7 | export function validaPasatiempos(objetos) { | 
| 8 | if (!Array.isArray(objetos)) | 
| 9 | throw new Error("no se recibió un arreglo.") | 
| 10 | /** | 
| 11 | * @type {import("./PASATIEMPO.js").PASATIEMPO[]} | 
| 12 | */ | 
| 13 | const arreglo = [] | 
| 14 | for (const objeto of objetos) { | 
| 15 | arreglo.push(validaPasatiempo(objeto)) | 
| 16 | } | 
| 17 | return arreglo | 
| 18 | } |