diff --git a/assets/static/appico/message.png b/assets/static/appico/message.png
new file mode 100644
index 0000000..763b3f7
Binary files /dev/null and b/assets/static/appico/message.png differ
diff --git a/src/index.html b/src/index.html
index 5df1051..1d0f6c9 100644
--- a/src/index.html
+++ b/src/index.html
@@ -90,6 +90,7 @@
+
diff --git a/src/page/mensajes.js b/src/page/mensajes.js
new file mode 100644
index 0000000..3715e84
--- /dev/null
+++ b/src/page/mensajes.js
@@ -0,0 +1,280 @@
+PERMS['mensajes'] = 'Mensajes';
+PERMS['mensajes:edit'] = '> Editar';
+PAGES.mensajes = {
+ navcss: 'btn5',
+ icon: 'static/appico/message.png',
+ AccessControl: true,
+ // AccessControlRole is not needed.
+ Title: 'Mensajes',
+ edit: function (mid) {
+ if (!checkRole('mensajes:edit')) {
+ setUrlHash('mensajes');
+ return;
+ }
+ var nameh1 = safeuuid();
+ var field_asunto = safeuuid();
+ var field_contenido = safeuuid();
+ var field_autor = safeuuid();
+ var field_files = safeuuid();
+ var attachments_list = safeuuid();
+ var btn_guardar = safeuuid();
+ var btn_borrar = safeuuid();
+ var div_actions = safeuuid();
+ container.innerHTML = html`
+
Mensaje
+
+ `;
+ DB.get('mensajes', mid).then((data) => {
+ function load_data(data, ENC = '') {
+ document.getElementById(nameh1).innerText = mid;
+ document.getElementById(field_asunto).value = data['Asunto'] || '';
+ document.getElementById(field_contenido).value = data['Contenido'] || '';
+ document.getElementById(field_autor).value = data['Autor'] || SUB_LOGGED_IN_DETAILS["Nombre"] || '';
+
+ // Mostrar adjuntos existentes (si los hay).
+ // No confiar en `data._attachments` porque `DB.get` devuelve solo `doc.data`.
+ const attachContainer = document.getElementById(attachments_list);
+ attachContainer.innerHTML = '';
+ // Usar API de DB para listar attachments (no acceder a internals desde la UI)
+ DB.listAttachments('mensajes', mid)
+ .then((list) => {
+ if (!list || !Array.isArray(list)) return;
+ list.forEach((att) => {
+ addAttachmentRow(att.name, att.dataUrl);
+ });
+ })
+ .catch((e) => {
+ console.warn('listAttachments error', e);
+ });
+ }
+ if (typeof data == 'string') {
+ TS_decrypt(data, SECRET, (data) => {
+ load_data(data, '%E');
+ });
+ } else {
+ load_data(data || {});
+ }
+ });
+ // gestión de archivos seleccionados antes de guardar
+ const attachmentsToUpload = [];
+ function addAttachmentRow(name, url) {
+ const attachContainer = document.getElementById(attachments_list);
+ const idRow = safeuuid();
+ const isImage = url && url.indexOf('data:image') === 0;
+ const preview = isImage
+ ? `
`
+ : `${name}`;
+ const html = `
+
+
${preview}${name}
+
+
`;
+ attachContainer.insertAdjacentHTML('beforeend', html);
+ attachContainer.querySelectorAll(`button[data-name="${name}"]`).forEach((btn) => {
+ btn.onclick = () => {
+ if (!confirm('¿Borrar este adjunto?')) return;
+ // Usar API pública en DB para borrar metadata del attachment
+ DB.deleteAttachment('mensajes', mid, name)
+ .then((ok) => {
+ if (ok) {
+ document.getElementById(idRow).remove();
+ toastr.error('Adjunto borrado');
+ } else {
+ toastr.error('No se pudo borrar el adjunto');
+ }
+ })
+ .catch((e) => {
+ console.warn('deleteAttachment error', e);
+ toastr.error('Error borrando adjunto');
+ });
+ };
+ });
+ }
+
+ document.getElementById(field_files).addEventListener('change', function (e) {
+ const files = Array.from(e.target.files || []);
+ files.forEach((file) => {
+ const reader = new FileReader();
+ reader.onload = function (ev) {
+ const dataUrl = ev.target.result;
+ attachmentsToUpload.push({
+ name: file.name,
+ data: dataUrl,
+ type: file.type || 'application/octet-stream',
+ });
+ // mostrar preview temporal
+ addAttachmentRow(file.name, dataUrl);
+ };
+ reader.readAsDataURL(file);
+ });
+ // limpiar input para permitir re-subidas del mismo archivo
+ e.target.value = '';
+ });
+ document.getElementById(btn_guardar).onclick = () => {
+ // Disable button to prevent double-clicking
+ var guardarBtn = document.getElementById(btn_guardar);
+ if (guardarBtn.disabled) return;
+
+ guardarBtn.disabled = true;
+ guardarBtn.style.opacity = '0.5';
+
+ var data = {
+ Autor: document.getElementById(field_autor).value,
+ Contenido: document.getElementById(field_contenido).value,
+ Asunto: document.getElementById(field_asunto).value,
+ };
+ document.getElementById('actionStatus').style.display = 'block';
+ DB.put('mensajes', mid, data)
+ .then(() => {
+ // subir attachments si los hay
+ const uploadPromises = [];
+ attachmentsToUpload.forEach((att) => {
+ if (DB.putAttachment) {
+ uploadPromises.push(
+ DB.putAttachment('mensajes', mid, att.name, att.data, att.type).catch((e) => {
+ console.warn('putAttachment error', e);
+ })
+ );
+ }
+ });
+ Promise.all(uploadPromises)
+ .then(() => {
+ // limpiar lista temporal y recargar attachments
+ attachmentsToUpload.length = 0;
+ try {
+ // recargar lista actual sin salir
+ const pouchId = 'mensajes:' + mid;
+ if (DB && DB._internal && DB._internal.local) {
+ DB._internal.local
+ .get(pouchId, { attachments: true })
+ .then((doc) => {
+ const attachContainer = document.getElementById(attachments_list);
+ attachContainer.innerHTML = '';
+ if (doc && doc._attachments) {
+ Object.keys(doc._attachments).forEach((name) => {
+ try {
+ const att = doc._attachments[name];
+ if (att && att.data) {
+ const durl =
+ 'data:' +
+ (att.content_type || 'application/octet-stream') +
+ ';base64,' +
+ att.data;
+ addAttachmentRow(name, durl);
+ return;
+ }
+ } catch (e) {}
+ DB.getAttachment('mensajes', mid, name)
+ .then((durl) => {
+ addAttachmentRow(name, durl);
+ })
+ .catch(() => {});
+ });
+ }
+ })
+ .catch(() => {
+ /* ignore reload errors */
+ });
+ }
+ } catch (e) {}
+ toastr.success('Guardado!');
+ setTimeout(() => {
+ document.getElementById('actionStatus').style.display = 'none';
+ setUrlHash('mensajes');
+ }, SAVE_WAIT);
+ })
+ .catch((e) => {
+ console.warn('Attachment upload error', e);
+ document.getElementById('actionStatus').style.display = 'none';
+ guardarBtn.disabled = false;
+ guardarBtn.style.opacity = '1';
+ toastr.error('Error al guardar los adjuntos');
+ });
+ })
+ .catch((e) => {
+ console.warn('DB.put error', e);
+ document.getElementById('actionStatus').style.display = 'none';
+ guardarBtn.disabled = false;
+ guardarBtn.style.opacity = '1';
+ toastr.error('Error al guardar el mensaje');
+ });
+ };
+ document.getElementById(btn_borrar).onclick = () => {
+ if (confirm('¿Quieres borrar este mensaje?') == true) {
+ DB.del('mensajes', mid).then(() => {
+ toastr.error('Borrado!');
+ setTimeout(() => {
+ setUrlHash('mensajes');
+ }, SAVE_WAIT);
+ });
+ }
+ };
+ },
+ index: function () {
+ if (!checkRole('mensajes')) {
+ setUrlHash('index');
+ return;
+ }
+ const tablebody = safeuuid();
+ var btn_new = safeuuid();
+ container.innerHTML = html`
+ Mensajes
+
+
+ `;
+ TS_IndexElement(
+ 'mensajes',
+ [
+ {
+ key: 'Autor',
+ type: 'raw',
+ default: '',
+ label: 'Origen',
+ },
+ {
+ key: 'Asunto',
+ type: 'raw',
+ default: '',
+ label: 'Asunto',
+ },
+ ],
+ 'mensajes',
+ document.querySelector('#cont')
+ );
+ if (!checkRole('mensajes:edit')) {
+ document.getElementById(btn_new).style.display = 'none';
+ } else {
+ document.getElementById(btn_new).onclick = () => {
+ setUrlHash('mensajes,' + safeuuid(''));
+ };
+ }
+ },
+};