This commit is contained in:
naielv
2025-12-25 00:45:14 +01:00
parent 13a4367c92
commit 648854190e
15 changed files with 549 additions and 252 deletions

175
decrypt.js Normal file
View File

@@ -0,0 +1,175 @@
function TS_decrypt(input, secret, callback, table, id) {
// Accept objects or plaintext strings. Also support legacy RSA{...} AES-encrypted entries.
var __ts_sync = true;
if (typeof input !== "string") {
try {
callback(input, false);
} catch (e) {
console.error(e);
}
return __ts_sync;
}
// Legacy encrypted format: RSA{...}
if (
input.startsWith("RSA{") &&
input.endsWith("}") &&
typeof CryptoJS !== "undefined"
) {
try {
var data = input.slice(4, -1);
var words = CryptoJS.AES.decrypt(data, secret);
var decryptedUtf8 = null;
try {
decryptedUtf8 = words.toString(CryptoJS.enc.Utf8);
} catch (utfErr) {
// Malformed UTF-8 — try Latin1 fallback
try {
decryptedUtf8 = words.toString(CryptoJS.enc.Latin1);
} catch (latinErr) {
console.warn(
"TS_decrypt: failed to decode decrypted bytes",
utfErr,
latinErr
);
try {
callback(input, false);
} catch (ee) {}
return;
}
}
var parsed = null;
try {
parsed = JSON.parse(decryptedUtf8);
} catch (pe) {
// If JSON parsing fails, the decrypted string may be raw Latin1 bytes.
// Try to convert Latin1 byte string -> UTF-8 and parse again.
try {
if (
typeof TextDecoder !== "undefined" &&
typeof decryptedUtf8 === "string"
) {
var bytes = new Uint8Array(decryptedUtf8.length);
for (var _i = 0; _i < decryptedUtf8.length; _i++)
bytes[_i] = decryptedUtf8.charCodeAt(_i) & 0xff;
var converted = new TextDecoder("utf-8").decode(bytes);
try {
parsed = JSON.parse(converted);
decryptedUtf8 = converted;
} catch (e2) {
parsed = decryptedUtf8;
}
} else {
parsed = decryptedUtf8;
}
} catch (convErr) {
parsed = decryptedUtf8;
}
}
try {
callback(parsed, true);
} catch (e) {
console.error(e);
}
// Migrate to plaintext in DB if table/id provided
if (table && id && window.DB && DB.put && typeof parsed !== "string") {
DB.put(table, id, parsed).catch(() => {});
}
return;
} catch (e) {
console.error("TS_decrypt: invalid encrypted payload", e);
try {
callback(input, false);
} catch (ee) {}
return;
}
}
if (input.startsWith("SEA{") && input.endsWith("}")) {
__ts_sync = false;
SEA.decrypt(input, secret, (decrypted) => {
try {
callback(decrypted, true);
} catch (e) {
console.error(e);
}
});
return __ts_sync;
}
// Try to parse JSON strings and migrate to object
try {
var parsed = JSON.parse(input);
try {
callback(parsed, false);
} catch (e) {
console.error(e);
}
if (table && id && window.DB && DB.put) {
DB.put(table, id, parsed).catch(() => {});
}
} catch (e) {
// Not JSON, return raw string
try {
callback(input, false);
} catch (err) {
console.error(err);
}
}
return __ts_sync;
}
function recursiveTSDecrypt(input, secret = "") {
// Skip null values (do not show on decrypted output)
if (input === null) return null;
if (typeof input === "string") {
let result = null;
let resolver = null;
const promise = new Promise((resolve) => {
resolver = resolve;
});
const sync = TS_decrypt(input, secret, (decrypted) => {
result = decrypted;
if (resolver) resolver(decrypted);
});
if (sync === false) return promise;
return result;
} else if (Array.isArray(input)) {
const mapped = input.map((item) => recursiveTSDecrypt(item, secret));
if (mapped.some((v) => v && typeof v.then === "function")) {
return Promise.all(mapped).then((values) => values.filter((v) => v !== null && typeof v !== 'undefined'));
}
return mapped.filter((v) => v !== null && typeof v !== 'undefined');
} else if (typeof input === "object" && input !== null) {
const keys = Object.keys(input);
const mapped = keys.map((k) => recursiveTSDecrypt(input[k], secret));
if (mapped.some((v) => v && typeof v.then === "function")) {
return Promise.all(mapped).then((values) => {
const out = {};
for (let i = 0; i < keys.length; i++) {
const val = values[i];
if (val !== null && typeof val !== 'undefined') out[keys[i]] = val;
}
return out;
});
} else {
const out = {};
for (let i = 0; i < keys.length; i++) {
const val = mapped[i];
if (val !== null && typeof val !== 'undefined') out[keys[i]] = val;
}
return out;
}
} else {
return input;
}
}
gun.get(TABLE).load((DATA) => {
var plain2 = recursiveTSDecrypt(DATA, SECRET);
plain2.then(function (result) {
download(
`Export TeleSec ${GROUPID} Decrypted.json.txt`,
JSON.stringify(result)
);
});
});

View File

@@ -9,10 +9,12 @@ function tableScroll(query) {
var container = document.getElementById("container"); var container = document.getElementById("container");
function LinkAccount(LinkAccount_secret, refresh = false) { function LinkAccount(LinkAccount_secret, refresh = false) {
// Store group id for backward compatibility and keep secret // Group identifier (no encryption). Secret usage removed.
localStorage.setItem("TELESEC_AUTO", "YES"); if (LinkAccount_secret) {
SECRET = LinkAccount_secret.toUpperCase(); SECRET = String(LinkAccount_secret).toUpperCase();
localStorage.setItem("TELESEC_secret", SECRET); } else {
SECRET = "";
}
if (refresh == true) { if (refresh == true) {
location.reload(); location.reload();
@@ -30,17 +32,6 @@ function LinkAccount(LinkAccount_secret, refresh = false) {
console.warn('DB.init failed or not available yet', e); console.warn('DB.init failed or not available yet', e);
} }
} }
if (localStorage.getItem("TELESEC_AUTO") == "YES") {
LinkAccount(
localStorage.getItem("TELESEC_secret")
);
}
if (urlParams.get("login") != null) {
LinkAccount(
urlParams.get("enc_password"),
);
//location.search = "";
}
function open_page(params) { function open_page(params) {
// Clear stored event listeners and timers // Clear stored event listeners and timers
@@ -153,11 +144,11 @@ function fixGunLocalStorage() {
} }
// Heartbeat: store a small "last seen" doc locally and replicate to remote when available // Heartbeat: store a small "last seen" doc locally and replicate to remote when available
setInterval(() => { // setInterval(() => {
if (typeof DB !== 'undefined') { // if (typeof DB !== 'undefined') {
DB.put('heartbeat', getDBName() || 'heartbeat', 'heartbeat-' + CurrentISOTime()); // DB.put('heartbeat', getDBName() || 'heartbeat', 'heartbeat-' + CurrentISOTime());
} // }
}, 5000); // }, 5000);
function betterSorter(a, b) { function betterSorter(a, b) {

View File

@@ -313,6 +313,15 @@ function addCategory_Personas(
btn.append(br2); btn.append(br2);
var img = document.createElement("img"); var img = document.createElement("img");
img.src = value.Foto || "static/ico/user_generic.png"; img.src = value.Foto || "static/ico/user_generic.png";
// Prefer attachment 'foto' for this persona
try {
const personaKey = key;
if (personaKey) {
DB.getAttachment('personas', personaKey, 'foto').then((durl) => {
if (durl) img.src = durl;
}).catch(() => {});
}
} catch (e) {}
img.style.height = "60px"; img.style.height = "60px";
img.style.padding = "5px"; img.style.padding = "5px";
img.style.backgroundColor = "white"; img.style.backgroundColor = "white";
@@ -330,7 +339,16 @@ function addCategory_Personas(
defaultval = key; defaultval = key;
span_0.innerText = ""; span_0.innerText = "";
var img_5 = document.createElement("img"); var img_5 = document.createElement("img");
img_5.src = value.Foto; img_5.src = value.Foto || "static/ico/user_generic.png";
// Prefer attachment 'foto' when available
try {
const personaKey2 = key;
if (personaKey2) {
DB.getAttachment('personas', personaKey2, 'foto').then((durl) => {
if (durl) img_5.src = durl;
}).catch(() => {});
}
} catch (e) {}
img_5.style.height = "30px"; img_5.style.height = "30px";
span_0.append(img_5, value.Nombre); span_0.append(img_5, value.Nombre);
change_cb(defaultval); change_cb(defaultval);
@@ -537,29 +555,62 @@ const SC_actions = {
], ],
}; };
// Listado precargado de personas: // Listado precargado de personas:
function TS_decrypt(input, secret, callback) { function TS_decrypt(input, secret, callback, table, id) {
// Only support AES-based encrypted payloads in the format: RSA{<ciphertext>} or plain objects/strings // Accept objects or plaintext strings. Also support legacy RSA{...} AES-encrypted entries.
if (typeof input != "string") { if (typeof input !== "string") {
callback(input); try { callback(input, false); } catch (e) { console.error(e); }
} else if (input.startsWith("RSA{") && input.endsWith("}")) { return;
var data = input.slice(4, -1);
var decrypted = CryptoJS.AES.decrypt(data, secret).toString(CryptoJS.enc.Utf8);
try {
callback(JSON.parse(decrypted));
} catch (e) {
console.error('TS_decrypt: invalid JSON', e);
callback(null);
}
} else {
// plain string (settings, etc.)
callback(input);
} }
}
// Legacy encrypted format: RSA{...}
if (input.startsWith("RSA{") && input.endsWith("}") && typeof CryptoJS !== 'undefined') {
try {
var data = input.slice(4, -1);
var words = CryptoJS.AES.decrypt(data, secret);
var decryptedUtf8 = null;
try {
decryptedUtf8 = words.toString(CryptoJS.enc.Utf8);
} catch (utfErr) {
// Malformed UTF-8 — try Latin1 fallback
try {
decryptedUtf8 = words.toString(CryptoJS.enc.Latin1);
} catch (latinErr) {
console.warn('TS_decrypt: failed to decode decrypted bytes', utfErr, latinErr);
try { callback(input, false); } catch (ee) { }
return;
}
}
var parsed = null;
try { parsed = JSON.parse(decryptedUtf8); } catch (pe) { parsed = decryptedUtf8; }
try { callback(parsed, true); } catch (e) { console.error(e); }
// Migrate to plaintext in DB if table/id provided
if (table && id && window.DB && DB.put && typeof parsed !== 'string') {
DB.put(table, id, parsed).catch(() => {});
}
return;
} catch (e) {
console.error('TS_decrypt: invalid encrypted payload', e);
try { callback(input, false); } catch (ee) { }
return;
}
}
// Try to parse JSON strings and migrate to object
try {
var parsed = JSON.parse(input);
try { callback(parsed, false); } catch (e) { console.error(e); }
if (table && id && window.DB && DB.put) {
DB.put(table, id, parsed).catch(() => {});
}
} catch (e) {
// Not JSON, return raw string
try { callback(input, false); } catch (err) { console.error(err); }
}
}
function TS_encrypt(input, secret, callback, mode = "RSA") { function TS_encrypt(input, secret, callback, mode = "RSA") {
// Use AES symmetric encryption (RSA{} envelope for backwards compatibility) // Encryption removed: store plaintext objects directly
var encrypted = CryptoJS.AES.encrypt(JSON.stringify(input), secret).toString(); try { callback(input); } catch (e) { console.error(e); }
callback("RSA{" + encrypted + "}"); }
}
// Populate SC_Personas from DB (PouchDB) // Populate SC_Personas from DB (PouchDB)
DB.map('personas', (data, key) => { DB.map('personas', (data, key) => {
function add_row(data, key) { function add_row(data, key) {
@@ -571,9 +622,9 @@ DB.map('personas', (data, key) => {
} }
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data, key); add_row(data, key);
}); }, 'personas', key);
} else { } else {
add_row(data, key); add_row(data, key);
} }
@@ -903,21 +954,19 @@ function TS_IndexElement(
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
data.Estado = state; data.Estado = state;
TS_encrypt(data, SECRET, (encrypted) => { if (typeof ref === 'string') {
if (typeof ref === 'string') { DB.put(ref, data._key, data).then(() => {
DB.put(ref, data._key, encrypted).then(() => { toastr.success("Guardado!");
toastr.success("Guardado!"); }).catch((e) => { console.warn('DB.put error', e); });
}); } else {
} else { try {
try { // legacy
// legacy ref.get(data._key).put(data);
ref.get(data._key).put(encrypted); toastr.success("Guardado!");
toastr.success("Guardado!"); } catch (e) {
} catch (e) { console.warn('Could not save item', e);
console.warn('Could not save item', e);
}
} }
}); }
return false; return false;
}; };
return button; return button;
@@ -992,6 +1041,17 @@ function TS_IndexElement(
infoSpan.style.color = "black"; infoSpan.style.color = "black";
const img = document.createElement("img"); const img = document.createElement("img");
img.src = persona.Foto || "static/ico/user_generic.png"; img.src = persona.Foto || "static/ico/user_generic.png";
// Prefer attachment 'foto' stored in PouchDB if available
try {
const personaId = key.self === true ? (data._key || data._id || data.id) : data[key.key];
if (personaId) {
DB.getAttachment('personas', personaId, 'foto').then((durl) => {
if (durl) img.src = durl;
}).catch(() => {});
}
} catch (e) {
// ignore
}
img.height = 70; img.height = 70;
infoSpan.appendChild(img); infoSpan.appendChild(img);
infoSpan.appendChild(document.createElement("br")); infoSpan.appendChild(document.createElement("br"));
@@ -1033,9 +1093,9 @@ function TS_IndexElement(
debounce(debounce_load, render, 300, [rows]); debounce(debounce_load, render, 300, [rows]);
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data, key); add_row(data, key);
}); }, ref, key);
} else { } else {
add_row(data, key); add_row(data, key);
} }
@@ -1054,9 +1114,9 @@ function TS_IndexElement(
debounce(debounce_load, render, 300, [rows]); debounce(debounce_load, render, 300, [rows]);
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data, key); add_row(data, key);
}); }, undefined, undefined);
} else { } else {
add_row(data, key); add_row(data, key);
} }
@@ -1127,7 +1187,7 @@ function SetPages() {
document.getElementById("appendApps2").append(a); document.getElementById("appendApps2").append(a);
} }
var Booted = false; var Booted = false;
var TimeoutBoot = 6; var TimeoutBoot = 3; // in loops of 750ms
var BootLoops = 0; var BootLoops = 0;
function getPeers() { function getPeers() {
@@ -1220,12 +1280,10 @@ var BootIntervalID = setInterval(() => {
} }
if (!data) { if (!data) {
const persona = { Nombre: 'Admin (bypass)', Roles: 'ADMIN,' }; const persona = { Nombre: 'Admin (bypass)', Roles: 'ADMIN,' };
TS_encrypt(persona, SECRET, (encrypted) => { DB.put('personas', bypassId, persona).then(() => finish(persona, bypassId)).catch((e) => { console.warn('AC_BYPASS create error', e); open_page('login'); });
DB.put('personas', bypassId, encrypted).then(() => finish(persona, bypassId)).catch((e) => { console.warn('AC_BYPASS create error', e); open_page('login'); });
});
} else { } else {
if (typeof data === 'string') { if (typeof data === 'string') {
TS_decrypt(data, SECRET, (pdata) => finish(pdata, bypassId)); TS_decrypt(data, SECRET, (pdata) => finish(pdata, bypassId), 'personas', bypassId);
} else { } else {
finish(data, bypassId); finish(data, bypassId);
} }

View File

@@ -10,6 +10,20 @@ var DB = (function () {
let changes = null; let changes = null;
let callbacks = {}; // table -> [cb] let callbacks = {}; // table -> [cb]
function ensureLocal() {
if (local) return;
try {
const localName = localStorage.getItem('TELESEC_COUCH_DBNAME') || 'telesec';
local = new PouchDB(localName);
if (changes) {
try { changes.cancel(); } catch (e) {}
changes = local.changes({ live: true, since: 'now', include_docs: true }).on('change', onChange);
}
} catch (e) {
console.warn('ensureLocal error', e);
}
}
function makeId(table, id) { function makeId(table, id) {
return table + ':' + id; return table + ':' + id;
} }
@@ -47,6 +61,7 @@ var DB = (function () {
} }
function replicateToRemote() { function replicateToRemote() {
ensureLocal();
if (!local || !remote) return; if (!local || !remote) return;
PouchDB.replicate(local, remote, { live: true, retry: true }).on('error', function (err) { PouchDB.replicate(local, remote, { live: true, retry: true }).on('error', function (err) {
console.warn('Replication error', err); console.warn('Replication error', err);
@@ -67,6 +82,7 @@ var DB = (function () {
} }
async function put(table, id, data) { async function put(table, id, data) {
ensureLocal();
const _id = makeId(table, id); const _id = makeId(table, id);
try { try {
const existing = await local.get(_id).catch(() => null); const existing = await local.get(_id).catch(() => null);
@@ -89,6 +105,7 @@ var DB = (function () {
} }
async function get(table, id) { async function get(table, id) {
ensureLocal();
const _id = makeId(table, id); const _id = makeId(table, id);
try { try {
const doc = await local.get(_id); const doc = await local.get(_id);
@@ -103,6 +120,7 @@ var DB = (function () {
} }
async function list(table) { async function list(table) {
ensureLocal();
try { try {
const res = await local.allDocs({ include_docs: true, startkey: table + ':', endkey: table + ':\uffff' }); const res = await local.allDocs({ include_docs: true, startkey: table + ':', endkey: table + ':\uffff' });
return res.rows.map(r => { return res.rows.map(r => {
@@ -112,7 +130,62 @@ var DB = (function () {
} catch (e) { return []; } } catch (e) { return []; }
} }
// Convert data URL to Blob
function dataURLtoBlob(dataurl) {
const arr = dataurl.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
async function putAttachment(table, id, name, dataUrlOrBlob, contentType) {
ensureLocal();
const _id = makeId(table, id);
try {
let doc = await local.get(_id).catch(() => null);
if (!doc) {
// create a minimal doc so attachments can be put
await local.put({ _id: _id, table: table, ts: new Date().toISOString(), data: {} });
doc = await local.get(_id);
}
let blob = dataUrlOrBlob;
if (typeof dataUrlOrBlob === 'string' && dataUrlOrBlob.indexOf('data:') === 0) {
blob = dataURLtoBlob(dataUrlOrBlob);
}
const type = contentType || (blob && blob.type) || 'application/octet-stream';
await local.putAttachment(_id, name, doc._rev, blob, type);
return true;
} catch (e) {
console.error('putAttachment error', e);
return false;
}
}
async function getAttachment(table, id, name) {
ensureLocal();
const _id = makeId(table, id);
try {
const blob = await local.getAttachment(_id, name);
if (!blob) return null;
// convert blob to data URL
return await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e) { resolve(e.target.result); };
reader.onerror = function (e) { reject(e); };
reader.readAsDataURL(blob);
});
} catch (e) {
return null;
}
}
function map(table, cb) { function map(table, cb) {
ensureLocal();
callbacks[table] = callbacks[table] || []; callbacks[table] = callbacks[table] || [];
callbacks[table].push(cb); callbacks[table].push(cb);
// initial load // initial load
@@ -131,8 +204,27 @@ var DB = (function () {
list, list,
map, map,
replicateToRemote, replicateToRemote,
putAttachment,
getAttachment,
_internal: { local } _internal: { local }
}; };
})(); })();
window.DB = DB; window.DB = DB;
// Auto-initialize DB on startup using saved settings (non-blocking)
(function autoInitDB() {
try {
const remoteServer = localStorage.getItem('TELESEC_COUCH_URL') || '';
const username = localStorage.getItem('TELESEC_COUCH_USER') || '';
const password = localStorage.getItem('TELESEC_COUCH_PASS') || '';
const dbname = localStorage.getItem('TELESEC_COUCH_DBNAME') || undefined;
const secret = localStorage.getItem('TELESEC_secret') || '';
// Call init but don't await; DB functions are safe-guarded with ensureLocal()
DB.init({ secret, remoteServer, username, password, dbname }).catch((e) => {
console.warn('DB.autoInit error', e);
});
} catch (e) {
console.warn('DB.autoInit unexpected error', e);
}
})();

View File

@@ -73,9 +73,9 @@ PAGES.aulas = {
); );
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data || {}); add_row(data || {});
}); }, 'comedor', CurrentISODate());
} else { } else {
add_row(data || {}); add_row(data || {});
} }
@@ -93,9 +93,9 @@ PAGES.aulas = {
); );
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data || {}); add_row(data || {});
}); }, 'notas', 'tareas');
} else { } else {
add_row(data || {}); add_row(data || {});
} }
@@ -113,9 +113,9 @@ PAGES.aulas = {
); );
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data || {}); add_row(data || {});
}); }, 'aulas_informes', 'diario-' + CurrentISODate());
} else { } else {
add_row(data || {}); add_row(data || {});
} }
@@ -191,9 +191,9 @@ PAGES.aulas = {
document.getElementById(field_autor).value = data["Solicitante"] || SUB_LOGGED_IN_ID || ""; document.getElementById(field_autor).value = data["Solicitante"] || SUB_LOGGED_IN_ID || "";
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
load_data(data, "%E"); load_data(data, "%E");
}); }, 'aulas_solicitudes', mid);
} else { } else {
load_data(data || {}); load_data(data || {});
} }
@@ -204,16 +204,14 @@ PAGES.aulas = {
Contenido: document.getElementById(field_contenido).value, Contenido: document.getElementById(field_contenido).value,
Asunto: document.getElementById(field_asunto).value, Asunto: document.getElementById(field_asunto).value,
}; };
var enc = TS_encrypt(data, SECRET, (encrypted) => { document.getElementById("actionStatus").style.display = "block";
document.getElementById("actionStatus").style.display = "block"; DB.put('aulas_solicitudes', mid, data).then(() => {
DB.put('aulas_solicitudes', mid, encrypted).then(() => { toastr.success("Guardado!");
toastr.success("Guardado!"); setTimeout(() => {
setTimeout(() => { document.getElementById("actionStatus").style.display = "none";
document.getElementById("actionStatus").style.display = "none"; setUrlHash("aulas,solicitudes");
setUrlHash("aulas,solicitudes"); }, SAVE_WAIT);
}, SAVE_WAIT); }).catch((e) => { console.warn('DB.put error', e); });
});
});
}; };
document.getElementById(btn_borrar).onclick = () => { document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar esta solicitud?") == true) { if (confirm("¿Quieres borrar esta solicitud?") == true) {
@@ -338,16 +336,14 @@ PAGES.aulas = {
Asunto: document.getElementById(field_asunto).value, Asunto: document.getElementById(field_asunto).value,
Fecha: document.getElementById(field_fecha).value || CurrentISODate(), Fecha: document.getElementById(field_fecha).value || CurrentISODate(),
}; };
var enc = TS_encrypt(data, SECRET, (encrypted) => { document.getElementById("actionStatus").style.display = "block";
document.getElementById("actionStatus").style.display = "block"; DB.put('aulas_informes', mid, data).then(() => {
DB.put('aulas_informes', mid, encrypted).then(() => { toastr.success("Guardado!");
toastr.success("Guardado!"); setTimeout(() => {
setTimeout(() => { document.getElementById("actionStatus").style.display = "none";
document.getElementById("actionStatus").style.display = "none"; setUrlHash("aulas,informes");
setUrlHash("aulas,informes"); }, SAVE_WAIT);
}, SAVE_WAIT); }).catch((e) => { console.warn('DB.put error', e); });
});
});
}; };
document.getElementById(btn_borrar).onclick = () => { document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar este informe?") == true) { if (confirm("¿Quieres borrar este informe?") == true) {

View File

@@ -114,9 +114,9 @@ PAGES.avisos = {
); );
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
load_data(data, "%E"); load_data(data, "%E");
}); }, 'notificaciones', mid);
} else { } else {
load_data(data || {}); load_data(data || {});
} }
@@ -141,16 +141,14 @@ PAGES.avisos = {
.getElementById(field_estado) .getElementById(field_estado)
.value.replace("%%", "por_leer"), .value.replace("%%", "por_leer"),
}; };
var enc = TS_encrypt(data, SECRET, (encrypted) => { document.getElementById("actionStatus").style.display = "block";
document.getElementById("actionStatus").style.display = "block"; DB.put('notificaciones', mid, data).then(() => {
DB.put('notificaciones', mid, encrypted).then(() => { toastr.success("Guardado!");
toastr.success("Guardado!"); setTimeout(() => {
setTimeout(() => { document.getElementById("actionStatus").style.display = "none";
document.getElementById("actionStatus").style.display = "none"; setUrlHash("avisos");
setUrlHash("avisos"); }, SAVE_WAIT);
}, SAVE_WAIT); }).catch((e) => { console.warn('DB.put error', e); });
});
});
}; };
document.getElementById(btn_borrar).onclick = () => { document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar esta notificación?") == true) { if (confirm("¿Quieres borrar esta notificación?") == true) {

View File

@@ -36,9 +36,9 @@ PAGES.comedor = {
data["Platos"] || ""; data["Platos"] || "";
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
load_data(data, "%E"); load_data(data, "%E");
}); }, 'comedor', mid);
} else { } else {
load_data(data || {}); load_data(data || {});
} }
@@ -55,16 +55,14 @@ PAGES.comedor = {
DB.del('comedor', mid); DB.del('comedor', mid);
} }
var enc = TS_encrypt(data, SECRET, (encrypted) => { document.getElementById("actionStatus").style.display = "block";
document.getElementById("actionStatus").style.display = "block"; DB.put('comedor', newDate, data).then(() => {
DB.put('comedor', newDate, encrypted).then(() => { toastr.success("Guardado!");
toastr.success("Guardado!"); setTimeout(() => {
setTimeout(() => { document.getElementById("actionStatus").style.display = "none";
document.getElementById("actionStatus").style.display = "none"; setUrlHash("comedor");
setUrlHash("comedor"); }, SAVE_WAIT);
}, SAVE_WAIT); }).catch((e) => { console.warn('DB.put error', e); });
});
});
}; };
document.getElementById(btn_borrar).onclick = () => { document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar esta entrada?") == true) { if (confirm("¿Quieres borrar esta entrada?") == true) {

View File

@@ -75,9 +75,9 @@ PAGES.dataman = {
const value = entry.data; const value = entry.data;
if (value != null) { if (value != null) {
if (typeof value == 'string') { if (typeof value == 'string') {
TS_decrypt(value, SECRET, (data) => { TS_decrypt(value, SECRET, (data, wasEncrypted) => {
output.materiales[key] = data; output.materiales[key] = data;
}); }, 'materiales', key);
} else { } else {
output.materiales[key] = value; output.materiales[key] = value;
} }
@@ -89,9 +89,9 @@ PAGES.dataman = {
const value = entry.data; const value = entry.data;
if (value != null) { if (value != null) {
if (typeof value == 'string') { if (typeof value == 'string') {
TS_decrypt(value, SECRET, (data) => { TS_decrypt(value, SECRET, (data, wasEncrypted) => {
output.personas[key] = data; output.personas[key] = data;
}); }, 'personas', key);
} else { } else {
output.personas[key] = value; output.personas[key] = value;
} }
@@ -168,12 +168,18 @@ PAGES.dataman = {
var sel = document.getElementById(select_type).value; var sel = document.getElementById(select_type).value;
if (sel == "%telesec") { if (sel == "%telesec") {
// legacy import, store entire payload as-is // legacy import, store entire payload as-is
DB.put('%telesec', 'export_' + Date.now(), JSON.parse(val)); // for each top-level key, store their items in DB
var parsed = JSON.parse(val);
Object.entries(parsed).forEach((section) => {
const sectionName = section[0];
const sectionData = section[1];
Object.entries(sectionData).forEach((entry) => {
DB.put(sectionName, entry[0], entry[1]).catch((e) => { console.warn('DB.put error', e); });
});
});
} else { } else {
Object.entries(JSON.parse(val)["data"]).forEach((entry) => { Object.entries(JSON.parse(val)["data"]).forEach((entry) => {
var enc = TS_encrypt(entry[1], SECRET, (encrypted) => { DB.put(sel, entry[0], entry[1]).catch((e) => { console.warn('DB.put error', e); });
DB.put(sel, entry[0], encrypted);
});
}); });
} }
setTimeout(() => { setTimeout(() => {

View File

@@ -101,10 +101,9 @@ PAGES.login = {
document.getElementById(btn_bypass_create).onclick = () => { document.getElementById(btn_bypass_create).onclick = () => {
var name = prompt("Nombre de la persona (ej: Admin):"); var name = prompt("Nombre de la persona (ej: Admin):");
if (!name) return; if (!name) return;
var id = 'bypass-' + Date.now(); var id = 'bypass-' + Date.now();
var persona = { Nombre: name, Roles: 'ADMIN,' }; var persona = { Nombre: name, Roles: 'ADMIN,' };
TS_encrypt(persona, SECRET, (encrypted) => { DB.put('personas', id, persona).then(() => {
DB.put('personas', id, encrypted).then(() => {
toastr.success('Persona creada: ' + id); toastr.success('Persona creada: ' + id);
localStorage.setItem('TELESEC_BYPASS_ID', id); localStorage.setItem('TELESEC_BYPASS_ID', id);
SUB_LOGGED_IN_ID = id; SUB_LOGGED_IN_ID = id;
@@ -115,7 +114,6 @@ PAGES.login = {
}).catch((e) => { }).catch((e) => {
toastr.error('Error creando persona: ' + (e && e.message ? e.message : e)); toastr.error('Error creando persona: ' + (e && e.message ? e.message : e));
}); });
});
}; };
} }
} }

View File

@@ -74,9 +74,9 @@ PAGES.materiales = {
document.getElementById(field_notas).value = data["Notas"] || ""; document.getElementById(field_notas).value = data["Notas"] || "";
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
load_data(data, "%E"); load_data(data, "%E");
}); }, 'materiales', mid);
} else { } else {
load_data(data || {}); load_data(data || {});
} }
@@ -91,16 +91,14 @@ PAGES.materiales = {
Revision: document.getElementById(field_revision).value, Revision: document.getElementById(field_revision).value,
Notas: document.getElementById(field_notas).value, Notas: document.getElementById(field_notas).value,
}; };
var enc = TS_encrypt(data, SECRET, (encrypted) => { document.getElementById("actionStatus").style.display = "block";
document.getElementById("actionStatus").style.display = "block"; DB.put('materiales', mid, data).then(() => {
DB.put('materiales', mid, encrypted).then(() => { toastr.success("Guardado!");
toastr.success("Guardado!"); setTimeout(() => {
setTimeout(() => { document.getElementById("actionStatus").style.display = "none";
document.getElementById("actionStatus").style.display = "none"; setUrlHash("materiales");
setUrlHash("materiales"); }, SAVE_WAIT);
}, SAVE_WAIT); }).catch((e) => { console.warn('DB.put error', e); });
});
});
}; };
document.getElementById(btn_borrar).onclick = () => { document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar este material?") == true) { if (confirm("¿Quieres borrar este material?") == true) {
@@ -184,11 +182,11 @@ PAGES.materiales = {
} }
if (typeof data === "string") { if (typeof data === "string") {
TS_decrypt(data, SECRET, (dec) => { TS_decrypt(data, SECRET, (dec, wasEncrypted) => {
if (dec && typeof dec === "object") { if (dec && typeof dec === "object") {
addUbicacion(dec); addUbicacion(dec);
} }
}); }, 'materiales', key);
} else { } else {
addUbicacion(data); addUbicacion(data);
} }

View File

@@ -79,16 +79,14 @@ PAGES.notas = {
Contenido: document.getElementById(field_contenido).value, Contenido: document.getElementById(field_contenido).value,
Asunto: document.getElementById(field_asunto).value, Asunto: document.getElementById(field_asunto).value,
}; };
var enc = TS_encrypt(data, SECRET, (encrypted) => { document.getElementById("actionStatus").style.display = "block";
document.getElementById("actionStatus").style.display = "block"; DB.put('notas', mid, data).then(() => {
DB.put('notas', mid, encrypted).then(() => { toastr.success("Guardado!");
toastr.success("Guardado!"); setTimeout(() => {
setTimeout(() => { document.getElementById("actionStatus").style.display = "none";
document.getElementById("actionStatus").style.display = "none"; setUrlHash("notas");
setUrlHash("notas"); }, SAVE_WAIT);
}, SAVE_WAIT); }).catch((e) => { console.warn('DB.put error', e); });
});
});
}; };
document.getElementById(btn_borrar).onclick = () => { document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar esta nota?") == true) { if (confirm("¿Quieres borrar esta nota?") == true) {

View File

@@ -476,37 +476,32 @@ PAGES.pagos = {
} }
persona.Monedero_Balance = fixfloat(newBalance); persona.Monedero_Balance = fixfloat(newBalance);
DB.put('personas', personaId, persona).then(() => {
TS_encrypt(persona, SECRET, (encrypted) => { if (callback) callback();
DB.put('personas', personaId, encrypted).then(() => { }).catch((e) => { console.warn('DB.put error', e); if (callback) callback(); });
if (callback) callback();
});
});
} }
function saveTransaction(ticketId, data) { function saveTransaction(ticketId, data) {
TS_encrypt(data, SECRET, (encrypted) => { document.getElementById("actionStatus").style.display = "block";
document.getElementById("actionStatus").style.display = "block"; DB.put('pagos', ticketId, data).then(() => {
DB.put('pagos', ticketId, encrypted).then(() => { // If this is from SuperCafé, update the order
// If this is from SuperCafé, update the order if (data.Origen === "SuperCafé" && data.OrigenID) {
if (data.Origen === "SuperCafé" && data.OrigenID) { handleSuperCafePayment(data);
handleSuperCafePayment(data); }
}
// Check for promotional bonus on Ingreso transactions (Efectivo only) // Check for promotional bonus on Ingreso transactions (Efectivo only)
if (data.Tipo === "Ingreso" && data.Metodo === "Efectivo") { if (data.Tipo === "Ingreso" && data.Metodo === "Efectivo") {
var bonusAmount = calculatePromoBonus(data.Monto); var bonusAmount = calculatePromoBonus(data.Monto);
if (bonusAmount > 0) { if (bonusAmount > 0) {
createPromoBonusTransaction(data.Persona, bonusAmount, data.Monto); createPromoBonusTransaction(data.Persona, bonusAmount, data.Monto);
}
} }
}
toastr.success("¡Transacción completada!"); toastr.success("¡Transacción completada!");
setTimeout(() => { setTimeout(() => {
document.getElementById("actionStatus").style.display = "none"; document.getElementById("actionStatus").style.display = "none";
setUrlHash("pagos," + ticketId); setUrlHash("pagos," + ticketId);
}, SAVE_WAIT); }, SAVE_WAIT);
});
}); });
} }
@@ -555,16 +550,11 @@ PAGES.pagos = {
var currentBalance = parseFloat(persona.Monedero_Balance || 0); var currentBalance = parseFloat(persona.Monedero_Balance || 0);
var newBalance = currentBalance + bonusAmount; var newBalance = currentBalance + bonusAmount;
persona.Monedero_Balance = fixfloat(newBalance); persona.Monedero_Balance = fixfloat(newBalance);
DB.put('personas', personaId, persona).catch((e) => { console.warn('DB.put error', e); });
TS_encrypt(persona, SECRET, (encrypted) => {
DB.put('personas', personaId, encrypted);
});
} }
// Save bonus transaction // Save bonus transaction
TS_encrypt(bonusData, SECRET, (encrypted) => { DB.put('pagos', bonusTicketId, bonusData).catch((e) => { console.warn('DB.put error', e); });
DB.put('pagos', bonusTicketId, encrypted);
});
toastr.success( toastr.success(
"🎉 ¡Promo Bono aplicado! +" + bonusAmount.toFixed(2) + "€ extra" "🎉 ¡Promo Bono aplicado! +" + bonusAmount.toFixed(2) + "€ extra"
@@ -580,9 +570,7 @@ PAGES.pagos = {
var persona = SC_Personas[transactionData.Persona]; var persona = SC_Personas[transactionData.Persona];
if (!persona) return; if (!persona) return;
TS_encrypt(persona, SECRET, (encrypted) => { DB.put('personas', transactionData.Persona, persona).catch((e) => { console.warn('DB.put error', e); });
DB.put('personas', transactionData.Persona, encrypted);
});
} }
// Pre-fill if data provided // Pre-fill if data provided
@@ -847,12 +835,9 @@ PAGES.pagos = {
} }
persona.Monedero_Balance = fixfloat(newBalance); persona.Monedero_Balance = fixfloat(newBalance);
DB.put('personas', personaId, persona).then(() => {
TS_encrypt(persona, SECRET, (encrypted) => { if (callback) callback();
DB.put('personas', personaId, encrypted).then(() => { }).catch((e) => { console.warn('DB.put error', e); if (callback) callback(); });
if (callback) callback();
});
});
} }
function deleteTransaction(transactionKey) { function deleteTransaction(transactionKey) {
@@ -1383,16 +1368,14 @@ PAGES.pagos = {
delete updatedData.PersonaDestino; delete updatedData.PersonaDestino;
} }
TS_encrypt(updatedData, SECRET, (encrypted) => { document.getElementById("actionStatus").style.display = "block";
document.getElementById("actionStatus").style.display = "block"; DB.put('pagos', transactionId, updatedData).then(() => {
DB.put('pagos', transactionId, encrypted).then(() => { toastr.success("¡Transacción actualizada!");
toastr.success("¡Transacción actualizada!"); setTimeout(() => {
setTimeout(() => { document.getElementById("actionStatus").style.display = "none";
document.getElementById("actionStatus").style.display = "none"; setUrlHash("pagos," + transactionId);
setUrlHash("pagos," + transactionId); }, SAVE_WAIT);
}, SAVE_WAIT); }).catch((e) => { console.warn('DB.put error', e); });
});
});
}; };
// Cancel button // Cancel button

View File

@@ -66,7 +66,7 @@ PAGES.personas = {
<button type="button" id="${btn_ver_monedero}" class="btn5">Ver Transacciones del Monedero</button> <button type="button" id="${btn_ver_monedero}" class="btn5">Ver Transacciones del Monedero</button>
</div> </div>
</details> </details>
<details style="background: #e3fde3ff; border: 2px solid #21f328ff; border-radius: 8px; padding: 10px; margin: 15px 0;"> <details style="background: #e3fde3ff; border: 2px solid #21f328ff; border-radius: 8px; padding: 10px; margin: 15px 0; display: none;">
<summary style="cursor: pointer; font-weight: bold; color: rgba(26, 141, 3, 1);">🔗 Generar enlaces</summary> <summary style="cursor: pointer; font-weight: bold; color: rgba(26, 141, 3, 1);">🔗 Generar enlaces</summary>
<div style="padding: 15px;"> <div style="padding: 15px;">
<label> <label>
@@ -110,9 +110,16 @@ PAGES.personas = {
document.getElementById(field_nombre).value = data["Nombre"] || ""; document.getElementById(field_nombre).value = data["Nombre"] || "";
document.getElementById(field_zona).value = data["Region"] || ""; document.getElementById(field_zona).value = data["Region"] || "";
document.getElementById(field_anilla).value = data["SC_Anilla"] || ""; document.getElementById(field_anilla).value = data["SC_Anilla"] || "";
document.getElementById(render_foto).src = // set fallback image immediately
data["Foto"] || "static/ico/user_generic.png"; document.getElementById(render_foto).src = data["Foto"] || "static/ico/user_generic.png";
resized = data["Foto"] || "static/ico/user_generic.png"; resized = data["Foto"] || "static/ico/user_generic.png";
// try to load attachment 'foto' if present (preferred storage)
DB.getAttachment('personas', mid, 'foto').then((durl) => {
if (durl) {
document.getElementById(render_foto).src = durl;
resized = durl;
}
}).catch(() => {});
document.getElementById(field_notas).value = data["markdown"] || ""; document.getElementById(field_notas).value = data["markdown"] || "";
document.getElementById(field_monedero_balance).value = document.getElementById(field_monedero_balance).value =
data["Monedero_Balance"] || 0; data["Monedero_Balance"] || 0;
@@ -120,29 +127,25 @@ PAGES.personas = {
data["Monedero_Notas"] || ""; data["Monedero_Notas"] || "";
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
load_data(data, "%E"); load_data(data, "%E");
}); }, 'personas', mid);
} else { } else {
load_data(data || {}); load_data(data || {});
} }
}); });
document document.getElementById(field_foto).addEventListener("change", function (e) {
.getElementById(field_foto) const file = e.target.files[0];
.addEventListener("change", function (e) { if (!file) return;
const file = e.target.files[0]; // Do NOT resize — keep original uploaded image
if (!file) return; const reader = new FileReader();
reader.onload = function (ev) {
resizeInputImage( const url = ev.target.result;
file, document.getElementById(render_foto).src = url;
function (url) { resized = url;
document.getElementById(render_foto).src = url; };
resized = url; reader.readAsDataURL(file);
}, });
256,
0.7
);
});
document.getElementById(btn_guardar).onclick = () => { document.getElementById(btn_guardar).onclick = () => {
var dt = new FormData(pdel); var dt = new FormData(pdel);
var data = { var data = {
@@ -150,23 +153,28 @@ PAGES.personas = {
Region: document.getElementById(field_zona).value, Region: document.getElementById(field_zona).value,
Roles: dt.getAll("perm").join(",") + ",", Roles: dt.getAll("perm").join(",") + ",",
SC_Anilla: document.getElementById(field_anilla).value, SC_Anilla: document.getElementById(field_anilla).value,
Foto: resized, // Foto moved to PouchDB attachment named 'foto'
markdown: document.getElementById(field_notas).value, markdown: document.getElementById(field_notas).value,
Monedero_Balance: Monedero_Balance:
parseFloat(document.getElementById(field_monedero_balance).value) || parseFloat(document.getElementById(field_monedero_balance).value) ||
0, 0,
Monedero_Notas: document.getElementById(field_monedero_notas).value, Monedero_Notas: document.getElementById(field_monedero_notas).value,
}; };
var enc = TS_encrypt(data, SECRET, (encrypted) => { document.getElementById("actionStatus").style.display = "block";
document.getElementById("actionStatus").style.display = "block"; DB.put('personas', mid, data).then(() => {
DB.put('personas', mid, encrypted).then(() => { // if resized is a data URL (new/updated image), save as attachment
var attachPromise = Promise.resolve(true);
if (typeof resized === 'string' && resized.indexOf('data:') === 0) {
attachPromise = DB.putAttachment('personas', mid, 'foto', resized, 'image/png');
}
attachPromise.then(() => {
toastr.success("Guardado!"); toastr.success("Guardado!");
setTimeout(() => { setTimeout(() => {
document.getElementById("actionStatus").style.display = "none"; document.getElementById("actionStatus").style.display = "none";
setUrlHash("personas"); setUrlHash("personas");
}, SAVE_WAIT); }, SAVE_WAIT);
}); }).catch((e) => { console.warn('putAttachment error', e); document.getElementById("actionStatus").style.display = "none"; });
}); }).catch((e) => { console.warn('DB.put error', e); document.getElementById("actionStatus").style.display = "none"; });
}; };
document.getElementById(btn_ver_monedero).onclick = () => { document.getElementById(btn_ver_monedero).onclick = () => {
setUrlHash("pagos"); // Navigate to pagos and show transactions for this person setUrlHash("pagos"); // Navigate to pagos and show transactions for this person

View File

@@ -51,9 +51,9 @@ PAGES.resumen_diario = {
); );
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data || {}); add_row(data || {});
}); }, 'comedor', CurrentISODate());
} else { } else {
add_row(data || {}); add_row(data || {});
} }
@@ -71,9 +71,9 @@ PAGES.resumen_diario = {
); );
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data || {}); add_row(data || {});
}); }, 'notas', 'tareas');
} else { } else {
add_row(data || {}); add_row(data || {});
} }
@@ -91,9 +91,9 @@ PAGES.resumen_diario = {
); );
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data || {}); add_row(data || {});
}); }, 'aulas_informes', 'diario-' + CurrentISODate());
} else { } else {
add_row(data || {}); add_row(data || {});
} }

View File

@@ -91,9 +91,9 @@ PAGES.supercafe = {
loadActions(); loadActions();
} }
if (typeof data == "string") { if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => { TS_decrypt(data, SECRET, (data, wasEncrypted) => {
load_data(data, "%E"); load_data(data, "%E");
}); }, 'supercafe', mid);
} else { } else {
load_data(data || {}); load_data(data || {});
} }
@@ -112,16 +112,14 @@ PAGES.supercafe = {
.getElementById(field_estado) .getElementById(field_estado)
.value.replace("%%", "Pedido"), .value.replace("%%", "Pedido"),
}; };
var enc = TS_encrypt(data, SECRET, (encrypted) => { document.getElementById("actionStatus").style.display = "block";
document.getElementById("actionStatus").style.display = "block"; DB.put('supercafe', mid, data).then(() => {
DB.put('supercafe', mid, encrypted).then(() => { toastr.success("Guardado!");
toastr.success("Guardado!"); setTimeout(() => {
setTimeout(() => { document.getElementById("actionStatus").style.display = "none";
document.getElementById("actionStatus").style.display = "none"; setUrlHash("supercafe");
setUrlHash("supercafe"); }, SAVE_WAIT);
}, SAVE_WAIT); }).catch((e) => { console.warn('DB.put error', e); });
});
});
}; };
document.getElementById(btn_borrar).onclick = () => { document.getElementById(btn_borrar).onclick = () => {
if ( if (