@@ -79,6 +80,7 @@
+
diff --git a/intenciones.html b/frontend/intenciones.html
old mode 100755
new mode 100644
similarity index 100%
rename from intenciones.html
rename to frontend/intenciones.html
diff --git a/js/api-config.js b/frontend/js/api-config.js
similarity index 95%
rename from js/api-config.js
rename to frontend/js/api-config.js
index 9a29b0a..2335f57 100644
--- a/js/api-config.js
+++ b/frontend/js/api-config.js
@@ -5,7 +5,7 @@ const API_BASE = (
location.hostname === "127.0.0.1" ||
location.hostname === "" // file:// abierto directamente
) ? "http://localhost:8080"
- : "https://recursos-catolicos.es:8080";
+ : "/api";
/**
* Realiza una llamada autenticada a la API.
diff --git a/frontend/js/auth.js b/frontend/js/auth.js
new file mode 100644
index 0000000..7a730ed
--- /dev/null
+++ b/frontend/js/auth.js
@@ -0,0 +1,297 @@
+// ================================
+// UTILIDADES DE AUTENTICACIÓN
+// ================================
+
+/** Devuelve el token JWT o null si no hay sesión. */
+function getToken() {
+ return localStorage.getItem("token");
+}
+
+/** Devuelve el objeto usuario guardado en sesión, o null. */
+function getUsuario() {
+ const u = localStorage.getItem("usuario");
+ return u ? JSON.parse(u) : null;
+}
+
+/**
+ * Verifica que el usuario esté autenticado.
+ * Si no lo está, redirige a login.html y devuelve null.
+ */
+function verificarAuth() {
+ if (!getToken()) {
+ window.location.href = "login.html";
+ return null;
+ }
+ return getUsuario();
+}
+
+/** Cierra la sesión y recarga la página actual (o va a index si es protegida). */
+function cerrarSesion() {
+ localStorage.removeItem("token");
+ localStorage.removeItem("usuario");
+ const paginasProtegidas = ["intenciones.html", "diario-oracion.html"];
+ const actual = location.pathname.split("/").pop();
+ if (paginasProtegidas.includes(actual)) {
+ window.location.href = "index.html";
+ } else {
+ location.reload();
+ }
+}
+
+/**
+ * Muestra el nombre del usuario y el botón de cerrar sesión en el header.
+ * Sin sesión: muestra botones que abren el modal de auth.
+ */
+function mostrarSesionEnHeader() {
+ const usuario = getUsuario();
+ const contenedor = document.getElementById("header-sesion");
+ if (!contenedor) return;
+
+ if (usuario) {
+ contenedor.innerHTML = `
+
+
+ `;
+ } else {
+ contenedor.innerHTML = `
+
+
+ `;
+ }
+}
+
+
+// ================================
+// MODAL DE AUTENTICACIÓN
+// ================================
+
+/** Resuelve la base de la API aunque api-config.js no esté cargado en la página. */
+function _apiBase() {
+ if (typeof API_BASE !== "undefined") return API_BASE;
+ return (location.hostname === "localhost" ||
+ location.hostname === "127.0.0.1" ||
+ location.hostname === "")
+ ? "http://localhost:8080"
+ : "https://recursos-catolicos.es:8080";
+}
+
+/** Crea e inserta el modal en el DOM la primera vez que se abre. */
+function _inyectarModalAuth() {
+ if (document.getElementById("modal-auth")) return;
+
+ const el = document.createElement("div");
+ el.id = "modal-auth";
+ el.className = "modal-auth-overlay";
+ el.setAttribute("role", "dialog");
+ el.setAttribute("aria-modal", "true");
+ el.setAttribute("aria-label", "Iniciar sesión o registrarse");
+ el.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
Bienvenido de nuevo 🙏
+
+
+
+
+
¿Olvidaste tu contraseña? Más opciones
+
+
+
+
+
Crea tu espacio personal ✝
+
+
+
+
+
+
¿Quieres más opciones? Registro completo
+
+
+ `;
+ document.body.appendChild(el);
+
+ // — Cerrar —
+ document.getElementById("modal-auth-cerrar").addEventListener("click", cerrarModalAuth);
+ el.addEventListener("click", e => { if (e.target === el) cerrarModalAuth(); });
+ document.addEventListener("keydown", _modalKeyHandler);
+
+ // — Pestañas —
+ el.querySelectorAll(".modal-auth-tab").forEach(tab => {
+ tab.addEventListener("click", () => {
+ el.querySelectorAll(".modal-auth-tab").forEach(t => t.classList.remove("activa"));
+ tab.classList.add("activa");
+ document.getElementById("modal-panel-login").style.display = tab.dataset.tab === "login" ? "block" : "none";
+ document.getElementById("modal-panel-registro").style.display = tab.dataset.tab === "registro" ? "block" : "none";
+ _focoModal(tab.dataset.tab);
+ });
+ });
+
+ // — Enter para enviar —
+ ["modal-email", "modal-password"].forEach(id =>
+ document.getElementById(id).addEventListener("keydown", e => { if (e.key === "Enter") _loginModal(); })
+ );
+ ["modal-nombre", "modal-email-reg", "modal-password-reg"].forEach(id =>
+ document.getElementById(id).addEventListener("keydown", e => { if (e.key === "Enter") _registroModal(); })
+ );
+
+ document.getElementById("modal-btn-login").addEventListener("click", _loginModal);
+ document.getElementById("modal-btn-registro").addEventListener("click", _registroModal);
+}
+
+function _modalKeyHandler(e) {
+ if (e.key === "Escape") cerrarModalAuth();
+}
+
+function _focoModal(tab) {
+ setTimeout(() => {
+ const id = tab === "login" ? "modal-email" : "modal-nombre";
+ document.getElementById(id)?.focus();
+ }, 80);
+}
+
+/** Abre el modal en la pestaña indicada ('login' | 'registro'). */
+function abrirModalAuth(tab = "login") {
+ _inyectarModalAuth();
+ const modal = document.getElementById("modal-auth");
+
+ // Limpiar mensajes y campos al abrir
+ ["modal-login-msg", "modal-registro-msg"].forEach(id => {
+ const el = document.getElementById(id);
+ if (el) { el.textContent = ""; el.className = "modal-auth-msg"; }
+ });
+
+ modal.querySelectorAll(".modal-auth-tab").forEach(t => t.classList.remove("activa"));
+ modal.querySelector(`[data-tab="${tab}"]`).classList.add("activa");
+ document.getElementById("modal-panel-login").style.display = tab === "login" ? "block" : "none";
+ document.getElementById("modal-panel-registro").style.display = tab === "registro" ? "block" : "none";
+
+ modal.classList.add("activo");
+ document.body.classList.add("modal-abierto");
+ _focoModal(tab);
+}
+
+/** Cierra el modal. */
+function cerrarModalAuth() {
+ const modal = document.getElementById("modal-auth");
+ if (!modal) return;
+ modal.classList.remove("activo");
+ document.body.classList.remove("modal-abierto");
+}
+
+/** Lógica de inicio de sesión desde el modal. */
+async function _loginModal() {
+ const email = document.getElementById("modal-email").value.trim();
+ const password = document.getElementById("modal-password").value.trim();
+ const msg = document.getElementById("modal-login-msg");
+ const btn = document.getElementById("modal-btn-login");
+
+ msg.textContent = "";
+ msg.className = "modal-auth-msg";
+
+ if (!email || !password) {
+ msg.textContent = "Completa todos los campos.";
+ msg.classList.add("error");
+ return;
+ }
+
+ btn.disabled = true;
+ btn.textContent = "Entrando…";
+
+ try {
+ const res = await fetch(`${_apiBase()}/auth/login`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email, password })
+ });
+
+ if (res.ok) {
+ const data = await res.json();
+ localStorage.setItem("token", data.token);
+ localStorage.setItem("usuario", JSON.stringify(data.usuario));
+ msg.textContent = `¡Bienvenido, ${data.usuario.nombre}! ✝`;
+ msg.classList.add("success");
+ setTimeout(() => location.reload(), 900);
+ } else {
+ msg.textContent = "Email o contraseña incorrectos.";
+ msg.classList.add("error");
+ btn.disabled = false;
+ btn.textContent = "Entrar";
+ }
+ } catch (e) {
+ msg.textContent = "No se pudo conectar con el servidor.";
+ msg.classList.add("error");
+ btn.disabled = false;
+ btn.textContent = "Entrar";
+ }
+}
+
+/** Lógica de registro desde el modal (individual; registro completo en register.html). */
+async function _registroModal() {
+ const nombre = document.getElementById("modal-nombre").value.trim();
+ const email = document.getElementById("modal-email-reg").value.trim();
+ const password = document.getElementById("modal-password-reg").value.trim();
+ const msg = document.getElementById("modal-registro-msg");
+ const btn = document.getElementById("modal-btn-registro");
+
+ msg.textContent = "";
+ msg.className = "modal-auth-msg";
+
+ if (!nombre || !email || !password) {
+ msg.textContent = "Completa todos los campos.";
+ msg.classList.add("error");
+ return;
+ }
+ if (password.length < 8) {
+ msg.textContent = "La contraseña debe tener al menos 8 caracteres.";
+ msg.classList.add("error");
+ return;
+ }
+
+ btn.disabled = true;
+ btn.textContent = "Registrando…";
+
+ try {
+ const res = await fetch(`${_apiBase()}/auth/register`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ nombre, email, password, tipoUsuario: "individual" })
+ });
+
+ if (res.ok) {
+ msg.textContent = "¡Cuenta creada! Iniciando sesión…";
+ msg.classList.add("success");
+
+ // Auto-login tras el registro
+ const loginRes = await fetch(`${_apiBase()}/auth/login`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email, password })
+ });
+ if (loginRes.ok) {
+ const data = await loginRes.json();
+ localStorage.setItem("token", data.token);
+ localStorage.setItem("usuario", JSON.stringify(data.usuario));
+ }
+ setTimeout(() => location.reload(), 900);
+ } else {
+ const error = await res.text();
+ msg.textContent = error || "No se pudo crear la cuenta.";
+ msg.classList.add("error");
+ btn.disabled = false;
+ btn.textContent = "Registrarme";
+ }
+ } catch (e) {
+ msg.textContent = "No se pudo conectar con el servidor.";
+ msg.classList.add("error");
+ btn.disabled = false;
+ btn.textContent = "Registrarme";
+ }
+}
diff --git a/js/biblioteca.js b/frontend/js/biblioteca.js
old mode 100755
new mode 100644
similarity index 100%
rename from js/biblioteca.js
rename to frontend/js/biblioteca.js
diff --git a/js/codigo.js b/frontend/js/codigo.js
old mode 100755
new mode 100644
similarity index 85%
rename from js/codigo.js
rename to frontend/js/codigo.js
index 2dae0ad..d32fcce
--- a/js/codigo.js
+++ b/frontend/js/codigo.js
@@ -8,6 +8,7 @@ document.addEventListener("DOMContentLoaded", () => {
visualizarRosario();
recordatorioDifuntos();
cargarIntencionesIndex();
+ personalizarSeccionDiario();
});
@@ -309,3 +310,51 @@ async function cargarIntencionesIndex() {
.map(i => `
${i.texto}`)
.join("");
}
+
+
+
+/* ============================================
+ PERSONALIZACIÓN SECCIÓN DIARIO EN PORTADA
+============================================ */
+
+function personalizarSeccionDiario() {
+ const previewElem = document.getElementById('diario-preview-index');
+ const textoElem = document.getElementById('texto-diario-index');
+ const botonElem = document.getElementById('boton-diario-index');
+ if (!previewElem) return;
+
+ const usuario = typeof getUsuario === 'function' ? getUsuario() : null;
+ if (!usuario) return; // sin sesión: se muestra el texto genérico
+
+ // Ajustar texto intro con el nombre
+ if (textoElem) {
+ textoElem.textContent = `Tu espacio personal, ${usuario.nombre}.`;
+ }
+
+ // Buscar entrada de hoy en localStorage
+ const hoy = (() => {
+ const d = new Date();
+ const offset = d.getTimezoneOffset() * 60000;
+ return new Date(d - offset).toISOString().split('T')[0];
+ })();
+
+ let entradas = {};
+ try {
+ const data = localStorage.getItem(`diario_${usuario.id}`);
+ if (data) entradas = JSON.parse(data);
+ } catch (e) { /* sin entradas */ }
+
+ const entradaHoy = entradas[hoy] || null;
+
+ if (entradaHoy && entradaHoy.texto) {
+ const icono = { paz: '🕊️', gratitud: '🙏', lucha: '😔', gozo: '✨', silencio: '🌿' }[entradaHoy.estado] || '🕯';
+ const preview = entradaHoy.texto.substring(0, 120) + (entradaHoy.texto.length > 120 ? '…' : '');
+ previewElem.innerHTML = `
+
+ ${icono} ${entradaHoy.titulo || 'Mi oración de hoy'}
+ ${preview}
+
+ `;
+ if (botonElem) botonElem.textContent = 'Continuar escribiendo';
+ }
+}
diff --git a/frontend/js/diario.js b/frontend/js/diario.js
new file mode 100644
index 0000000..505feb7
--- /dev/null
+++ b/frontend/js/diario.js
@@ -0,0 +1,310 @@
+// ================================
+// DIARIO DE ORACIÓN
+// Persistencia en API (con fallback a localStorage si no hay conexión).
+// Requiere: api-config.js, auth.js
+// ================================
+
+const ESTADOS_DIARIO = {
+ paz: { icono: '🕊️', label: 'Paz' },
+ gratitud: { icono: '🙏', label: 'Gratitud' },
+ lucha: { icono: '😔', label: 'Lucha' },
+ gozo: { icono: '✨', label: 'Gozo' },
+ silencio: { icono: '🌿', label: 'Silencio' }
+};
+
+let _usuario = null;
+let _fechaSeleccionada = null;
+
+// Cache en memoria para evitar peticiones redundantes durante la sesión.
+const _cache = {};
+
+// ── INICIALIZACIÓN ──────────────────────────────────────────
+
+document.addEventListener("DOMContentLoaded", () => {
+ _usuario = verificarAuth();
+ if (!_usuario) return;
+
+ const hoy = new Date();
+ _fechaSeleccionada = toFechaISO(hoy);
+
+ document.getElementById('saludo-usuario').textContent = saludoPersonal(_usuario.nombre, hoy);
+ document.getElementById('fecha-entrada').value = _fechaSeleccionada;
+
+ cargarEntrada(_fechaSeleccionada);
+ cargarListaEntradas();
+
+ document.getElementById('btn-guardar-entrada').addEventListener('click', guardarEntrada);
+ document.getElementById('btn-borrar-entrada').addEventListener('click', borrarEntrada);
+ document.getElementById('btn-anterior').addEventListener('click', () => navegarFecha(-1));
+ document.getElementById('btn-siguiente').addEventListener('click', () => navegarFecha(1));
+
+ document.getElementById('fecha-entrada').addEventListener('change', e => {
+ _fechaSeleccionada = e.target.value;
+ cargarEntrada(_fechaSeleccionada);
+ });
+
+ document.querySelectorAll('.btn-estado').forEach(btn => {
+ btn.addEventListener('click', () => seleccionarEstado(btn.dataset.estado));
+ });
+});
+
+// ── SALUDO ──────────────────────────────────────────────────
+
+function saludoPersonal(nombre, fecha) {
+ const h = fecha.getHours();
+ const franja = h < 13 ? 'Buenos días' : h < 20 ? 'Buenas tardes' : 'Buenas noches';
+ return `${franja}, ${nombre}. Un momento de silencio contigo.`;
+}
+
+// ── API ─────────────────────────────────────────────────────
+
+async function _apiGetEntrada(fecha) {
+ try {
+ const res = await apiCall(`/diario/${fecha}`);
+ if (!res) return null;
+ if (res.status === 404) return null;
+ if (!res.ok) throw new Error('Error al cargar entrada');
+ return await res.json();
+ } catch (e) {
+ return _localGetEntrada(fecha);
+ }
+}
+
+async function _apiGetTodas() {
+ try {
+ const res = await apiCall('/diario');
+ if (!res || !res.ok) throw new Error('Error al cargar entradas');
+ return await res.json();
+ } catch (e) {
+ return _localGetTodas();
+ }
+}
+
+async function _apiGuardar(fecha, titulo, texto, estado) {
+ try {
+ const res = await apiCall('/diario', {
+ method: 'POST',
+ body: JSON.stringify({ fecha, titulo, texto, estado })
+ });
+ if (!res || !res.ok) throw new Error('Error al guardar');
+ const dto = await res.json();
+ _localSetEntrada(fecha, { id: dto.id, fecha, titulo, texto, estado });
+ return dto;
+ } catch (e) {
+ _localSetEntrada(fecha, { fecha, titulo, texto, estado });
+ return { fecha, titulo, texto, estado };
+ }
+}
+
+async function _apiEliminar(fecha) {
+ const entrada = _cache[fecha] || _localGetEntrada(fecha);
+ if (entrada?.id) {
+ try {
+ await apiCall(`/diario/${entrada.id}`, { method: 'DELETE' });
+ } catch (e) { /* sin conexión: eliminar solo local */ }
+ }
+ _localEliminarEntrada(fecha);
+ delete _cache[fecha];
+}
+
+// ── LOCALSTORAGE (fallback / caché offline) ─────────────────
+
+function _lsKey() { return `diario_${_usuario.id}`; }
+
+function _localGetTodas() {
+ try {
+ const data = localStorage.getItem(_lsKey());
+ const obj = data ? JSON.parse(data) : {};
+ return Object.values(obj);
+ } catch (e) { return []; }
+}
+
+function _localGetEntrada(fecha) {
+ try {
+ const data = localStorage.getItem(_lsKey());
+ const obj = data ? JSON.parse(data) : {};
+ return obj[fecha] || null;
+ } catch (e) { return null; }
+}
+
+function _localSetEntrada(fecha, entrada) {
+ try {
+ const data = localStorage.getItem(_lsKey());
+ const obj = data ? JSON.parse(data) : {};
+ obj[fecha] = entrada;
+ localStorage.setItem(_lsKey(), JSON.stringify(obj));
+ } catch (e) { /* sin espacio */ }
+}
+
+function _localEliminarEntrada(fecha) {
+ try {
+ const data = localStorage.getItem(_lsKey());
+ const obj = data ? JSON.parse(data) : {};
+ delete obj[fecha];
+ localStorage.setItem(_lsKey(), JSON.stringify(obj));
+ } catch (e) { /* noop */ }
+}
+
+// ── CARGAR ENTRADA ──────────────────────────────────────────
+
+async function cargarEntrada(fecha) {
+ actualizarFechaDisplay(fecha);
+
+ document.getElementById('titulo-entrada').value = '';
+ document.getElementById('texto-entrada').value = '';
+ document.querySelectorAll('.btn-estado').forEach(b => b.classList.remove('activo'));
+ document.getElementById('btn-borrar-entrada').style.display = 'none';
+
+ const entrada = await _apiGetEntrada(fecha);
+ _cache[fecha] = entrada;
+
+ if (entrada) {
+ document.getElementById('titulo-entrada').value = entrada.titulo || '';
+ document.getElementById('texto-entrada').value = entrada.texto || '';
+ if (entrada.estado) {
+ const btn = document.querySelector(`.btn-estado[data-estado="${entrada.estado}"]`);
+ if (btn) btn.classList.add('activo');
+ }
+ document.getElementById('btn-borrar-entrada').style.display = 'inline-block';
+ }
+}
+
+// ── GUARDAR ─────────────────────────────────────────────────
+
+async function guardarEntrada() {
+ const titulo = document.getElementById('titulo-entrada').value.trim();
+ const texto = document.getElementById('texto-entrada').value.trim();
+ const estado = getEstadoSeleccionado();
+ const btn = document.getElementById('btn-guardar-entrada');
+
+ if (!texto) {
+ mostrarMensajeDiario('Escribe algo antes de guardar 🙏', 'error');
+ return;
+ }
+
+ btn.disabled = true;
+ btn.textContent = 'Guardando…';
+
+ const dto = await _apiGuardar(_fechaSeleccionada, titulo, texto, estado);
+ _cache[_fechaSeleccionada] = dto;
+
+ btn.disabled = false;
+ btn.textContent = 'Guardar ✝';
+ document.getElementById('btn-borrar-entrada').style.display = 'inline-block';
+
+ cargarListaEntradas();
+ mostrarMensajeDiario('Entrada guardada ✝', 'success');
+}
+
+// ── BORRAR ───────────────────────────────────────────────────
+
+async function borrarEntrada() {
+ if (!confirm('¿Eliminar esta entrada del diario?')) return;
+
+ await _apiEliminar(_fechaSeleccionada);
+
+ document.getElementById('titulo-entrada').value = '';
+ document.getElementById('texto-entrada').value = '';
+ document.querySelectorAll('.btn-estado').forEach(b => b.classList.remove('activo'));
+ document.getElementById('btn-borrar-entrada').style.display = 'none';
+
+ cargarListaEntradas();
+ mostrarMensajeDiario('Entrada eliminada', 'info');
+}
+
+// ── ESTADOS DE ÁNIMO ────────────────────────────────────────
+
+function seleccionarEstado(estado) {
+ const btn = document.querySelector(`.btn-estado[data-estado="${estado}"]`);
+ if (!btn) return;
+ const yaActivo = btn.classList.contains('activo');
+ document.querySelectorAll('.btn-estado').forEach(b => b.classList.remove('activo'));
+ if (!yaActivo) btn.classList.add('activo');
+}
+
+function getEstadoSeleccionado() {
+ const activo = document.querySelector('.btn-estado.activo');
+ return activo ? activo.dataset.estado : null;
+}
+
+// ── NAVEGACIÓN ───────────────────────────────────────────────
+
+function navegarFecha(delta) {
+ const d = new Date(_fechaSeleccionada + 'T12:00:00');
+ d.setDate(d.getDate() + delta);
+ _fechaSeleccionada = toFechaISO(d);
+ document.getElementById('fecha-entrada').value = _fechaSeleccionada;
+ cargarEntrada(_fechaSeleccionada);
+}
+
+function actualizarFechaDisplay(fecha) {
+ const d = new Date(fecha + 'T12:00:00');
+ const opts = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
+ document.getElementById('display-fecha').textContent = d.toLocaleDateString('es-ES', opts);
+ const hoy = toFechaISO(new Date());
+ document.getElementById('btn-siguiente').disabled = fecha >= hoy;
+}
+
+// ── LISTA DE ENTRADAS ───────────────────────────────────────
+
+async function cargarListaEntradas() {
+ const lista = document.getElementById('lista-entradas');
+ const sinElem = document.getElementById('sin-entradas');
+
+ const entradas = await _apiGetTodas();
+
+ const lista_sorted = entradas
+ .filter(e => e && e.fecha)
+ .sort((a, b) => b.fecha.localeCompare(a.fecha));
+
+ if (lista_sorted.length === 0) {
+ lista.innerHTML = '';
+ sinElem.style.display = 'block';
+ return;
+ }
+
+ sinElem.style.display = 'none';
+ const hoy = toFechaISO(new Date());
+
+ lista.innerHTML = lista_sorted.map(e => {
+ const fecha = e.fecha;
+ const d = new Date(fecha + 'T12:00:00');
+ const fechaStr = d.toLocaleDateString('es-ES', { weekday: 'short', day: 'numeric', month: 'long' });
+ const icono = ESTADOS_DIARIO[e.estado]?.icono || '🕯';
+ const preview = (e.texto || '').substring(0, 80) + ((e.texto || '').length > 80 ? '…' : '');
+ const esHoy = fecha === hoy;
+
+ return `
+
+ ${icono}
+
+ ${fechaStr}${esHoy ? ' (hoy)' : ''}
+ ${e.titulo ? `${e.titulo}` : ''}
+ ${preview}
+
+
+ `;
+ }).join('');
+}
+
+function seleccionarFechaLista(fecha) {
+ _fechaSeleccionada = fecha;
+ document.getElementById('fecha-entrada').value = fecha;
+ cargarEntrada(fecha);
+ document.getElementById('editor-diario').scrollIntoView({ behavior: 'smooth' });
+}
+
+// ── UTILIDADES ───────────────────────────────────────────────
+
+function toFechaISO(date) {
+ const offset = date.getTimezoneOffset() * 60000;
+ return new Date(date - offset).toISOString().split('T')[0];
+}
+
+function mostrarMensajeDiario(texto, tipo) {
+ const msg = document.getElementById('mensaje-diario');
+ msg.textContent = texto;
+ msg.className = `mensaje-diario ${tipo}`;
+ msg.style.display = 'block';
+ setTimeout(() => { msg.style.display = 'none'; }, 3000);
+}
\ No newline at end of file
diff --git a/js/header.js b/frontend/js/header.js
old mode 100755
new mode 100644
similarity index 100%
rename from js/header.js
rename to frontend/js/header.js
diff --git a/js/intenciones.js b/frontend/js/intenciones.js
old mode 100755
new mode 100644
similarity index 83%
rename from js/intenciones.js
rename to frontend/js/intenciones.js
index 666d00f..d5051dc
--- a/js/intenciones.js
+++ b/frontend/js/intenciones.js
@@ -205,25 +205,54 @@ function crearHexagono(intencion) {
// ── DIFUNTOS ─────────────────────────────────────────────────
+// Caché en memoria para evitar peticiones redundantes
+let _difuntosCache = null;
+
+async function _apiGetDifuntos() {
+ try {
+ const res = await apiCall('/difuntos/personales');
+ if (!res || !res.ok) throw new Error('Error al cargar difuntos');
+ const data = await res.json();
+ _difuntosCache = data;
+ // Sincronizar localStorage como caché offline
+ localStorage.setItem(keyDifuntos(), JSON.stringify(data));
+ return data;
+ } catch (e) {
+ // Fallback a localStorage si no hay conexión
+ return JSON.parse(localStorage.getItem(keyDifuntos()) || '[]');
+ }
+}
+
// La clave incluye el id del usuario para que cada cuenta tenga sus propios difuntos
function keyDifuntos() {
- return `difuntos_personales_${usuario ? usuario.id : "anonimo"}`;
+ return `difuntos_personales_${usuario ? usuario.id : 'anonimo'}`;
}
-function obtenerDifuntos() {
- return JSON.parse(localStorage.getItem(keyDifuntos()) || "[]");
-}
-
-function agregarDifunto() {
+async function agregarDifunto() {
const nombre = document.getElementById("difunto-nombre").value.trim();
if (!nombre) return;
const nacimiento = document.getElementById("difunto-nacimiento").value || null;
const defuncion = document.getElementById("difunto-defuncion").value || null;
- const difuntos = obtenerDifuntos();
- difuntos.push({ id: Date.now().toString(), nombre, nacimiento, defuncion });
- localStorage.setItem(keyDifuntos(), JSON.stringify(difuntos));
+ try {
+ const res = await apiCall('/difuntos/personales', {
+ method: 'POST',
+ body: JSON.stringify({ nombre, nacimiento, defuncion })
+ });
+ if (res && res.ok) {
+ _difuntosCache = null; // invalidar caché
+ } else {
+ // Guardar local si falla
+ const difuntos = JSON.parse(localStorage.getItem(keyDifuntos()) || '[]');
+ difuntos.push({ id: Date.now().toString(), nombre, nacimiento, defuncion });
+ localStorage.setItem(keyDifuntos(), JSON.stringify(difuntos));
+ }
+ } catch (e) {
+ const difuntos = JSON.parse(localStorage.getItem(keyDifuntos()) || '[]');
+ difuntos.push({ id: Date.now().toString(), nombre, nacimiento, defuncion });
+ localStorage.setItem(keyDifuntos(), JSON.stringify(difuntos));
+ }
document.getElementById("difunto-nombre").value = "";
document.getElementById("difunto-nacimiento").value = "";
@@ -232,15 +261,26 @@ function agregarDifunto() {
cargarDifuntos();
}
-function eliminarDifunto(id) {
- const difuntos = obtenerDifuntos().filter(d => d.id !== id);
+async function eliminarDifunto(id) {
+ try {
+ const res = await apiCall(`/difuntos/personales/${id}`, { method: 'DELETE' });
+ if (res && (res.ok || res.status === 204)) {
+ _difuntosCache = null;
+ // Sincronizar localStorage
+ const local = JSON.parse(localStorage.getItem(keyDifuntos()) || '[]');
+ localStorage.setItem(keyDifuntos(), JSON.stringify(local.filter(d => String(d.id) !== String(id))));
+ return;
+ }
+ } catch (e) { /* sin conexión: eliminar solo local */ }
+ // Fallback: solo localStorage
+ const difuntos = JSON.parse(localStorage.getItem(keyDifuntos()) || '[]').filter(d => String(d.id) !== String(id));
localStorage.setItem(keyDifuntos(), JSON.stringify(difuntos));
}
-function cargarDifuntos() {
+async function cargarDifuntos() {
const lista = document.getElementById("lista-difuntos");
const sinDifuntos = document.getElementById("sin-difuntos");
- const difuntos = obtenerDifuntos();
+ const difuntos = _difuntosCache || await _apiGetDifuntos();
lista.innerHTML = "";
@@ -275,8 +315,8 @@ function cargarDifuntos() {
});
lista.querySelectorAll(".btn-eliminar-difunto").forEach(btn => {
- btn.addEventListener("click", () => {
- eliminarDifunto(btn.dataset.id);
+ btn.addEventListener("click", async () => {
+ await eliminarDifunto(btn.dataset.id);
cargarDifuntos();
});
});
diff --git a/js/login.js b/frontend/js/login.js
old mode 100755
new mode 100644
similarity index 100%
rename from js/login.js
rename to frontend/js/login.js
diff --git a/js/register.js b/frontend/js/register.js
old mode 100755
new mode 100644
similarity index 100%
rename from js/register.js
rename to frontend/js/register.js
diff --git a/js/rosario.js b/frontend/js/rosario.js
old mode 100755
new mode 100644
similarity index 100%
rename from js/rosario.js
rename to frontend/js/rosario.js
diff --git a/js/rosario20260118.js b/frontend/js/rosario20260118.js
old mode 100755
new mode 100644
similarity index 100%
rename from js/rosario20260118.js
rename to frontend/js/rosario20260118.js
diff --git a/login.html b/frontend/login.html
old mode 100755
new mode 100644
similarity index 100%
rename from login.html
rename to frontend/login.html
diff --git a/frontend/nginx.conf b/frontend/nginx.conf
new file mode 100644
index 0000000..fc4e19e
--- /dev/null
+++ b/frontend/nginx.conf
@@ -0,0 +1,21 @@
+server {
+ listen 80;
+ server_name _;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # Proxy llamadas a la API hacia el backend Spring Boot
+ location /api/ {
+ proxy_pass http://recursos-catolicos-api:8080/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ # Servir ficheros estáticos
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+}
diff --git a/oraciones-basicas.html b/frontend/oraciones-basicas.html
old mode 100755
new mode 100644
similarity index 99%
rename from oraciones-basicas.html
rename to frontend/oraciones-basicas.html
index c90bc63..197c3c0
--- a/oraciones-basicas.html
+++ b/frontend/oraciones-basicas.html
@@ -69,6 +69,7 @@
+
diff --git a/register.html b/frontend/register.html
old mode 100755
new mode 100644
similarity index 100%
rename from register.html
rename to frontend/register.html
diff --git a/rosario.html b/frontend/rosario.html
old mode 100755
new mode 100644
similarity index 95%
rename from rosario.html
rename to frontend/rosario.html
index 2a9f6fc..617e7c9
--- a/rosario.html
+++ b/frontend/rosario.html
@@ -20,6 +20,7 @@
+
diff --git a/js/auth.js b/js/auth.js
deleted file mode 100644
index 359a8df..0000000
--- a/js/auth.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// ================================
-// UTILIDADES DE AUTENTICACIÓN
-// ================================
-
-/** Devuelve el token JWT o null si no hay sesión. */
-function getToken() {
- return localStorage.getItem("token");
-}
-
-/** Devuelve el objeto usuario guardado en sesión, o null. */
-function getUsuario() {
- const u = localStorage.getItem("usuario");
- return u ? JSON.parse(u) : null;
-}
-
-/**
- * Verifica que el usuario esté autenticado.
- * Si no lo está, redirige a login.html y devuelve null.
- */
-function verificarAuth() {
- if (!getToken()) {
- window.location.href = "login.html";
- return null;
- }
- return getUsuario();
-}
-
-/** Cierra la sesión eliminando los datos locales y redirige al login. */
-function cerrarSesion() {
- localStorage.removeItem("token");
- localStorage.removeItem("usuario");
- window.location.href = "login.html";
-}
-
-/**
- * Muestra el nombre del usuario y el botón de cerrar sesión en el header.
- * Llama a esta función después de cargar el header.
- */
-function mostrarSesionEnHeader() {
- const usuario = getUsuario();
- const contenedor = document.getElementById("header-sesion");
- if (!contenedor) return;
-
- if (usuario) {
- contenedor.innerHTML = `
-
- Esta es una página de prueba.
- Espero que disfrutes aprendiendo HTML.
-
-
\ No newline at end of file