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");
function LinkAccount(LinkAccount_secret, refresh = false) {
// Store group id for backward compatibility and keep secret
localStorage.setItem("TELESEC_AUTO", "YES");
SECRET = LinkAccount_secret.toUpperCase();
localStorage.setItem("TELESEC_secret", SECRET);
// Group identifier (no encryption). Secret usage removed.
if (LinkAccount_secret) {
SECRET = String(LinkAccount_secret).toUpperCase();
} else {
SECRET = "";
}
if (refresh == true) {
location.reload();
@@ -30,17 +32,6 @@ function LinkAccount(LinkAccount_secret, refresh = false) {
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) {
// 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
setInterval(() => {
if (typeof DB !== 'undefined') {
DB.put('heartbeat', getDBName() || 'heartbeat', 'heartbeat-' + CurrentISOTime());
}
}, 5000);
// setInterval(() => {
// if (typeof DB !== 'undefined') {
// DB.put('heartbeat', getDBName() || 'heartbeat', 'heartbeat-' + CurrentISOTime());
// }
// }, 5000);
function betterSorter(a, b) {

View File

@@ -313,6 +313,15 @@ function addCategory_Personas(
btn.append(br2);
var img = document.createElement("img");
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.padding = "5px";
img.style.backgroundColor = "white";
@@ -330,7 +339,16 @@ function addCategory_Personas(
defaultval = key;
span_0.innerText = "";
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";
span_0.append(img_5, value.Nombre);
change_cb(defaultval);
@@ -537,29 +555,62 @@ const SC_actions = {
],
};
// Listado precargado de personas:
function TS_decrypt(input, secret, callback) {
// Only support AES-based encrypted payloads in the format: RSA{<ciphertext>} or plain objects/strings
if (typeof input != "string") {
callback(input);
} else if (input.startsWith("RSA{") && input.endsWith("}")) {
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);
function TS_decrypt(input, secret, callback, table, id) {
// Accept objects or plaintext strings. Also support legacy RSA{...} AES-encrypted entries.
if (typeof input !== "string") {
try { callback(input, false); } catch (e) { console.error(e); }
return;
}
}
// 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") {
// Use AES symmetric encryption (RSA{} envelope for backwards compatibility)
var encrypted = CryptoJS.AES.encrypt(JSON.stringify(input), secret).toString();
callback("RSA{" + encrypted + "}");
}
// Encryption removed: store plaintext objects directly
try { callback(input); } catch (e) { console.error(e); }
}
// Populate SC_Personas from DB (PouchDB)
DB.map('personas', (data, key) => {
function add_row(data, key) {
@@ -571,9 +622,9 @@ DB.map('personas', (data, key) => {
}
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data, key);
});
}, 'personas', key);
} else {
add_row(data, key);
}
@@ -903,21 +954,19 @@ function TS_IndexElement(
event.preventDefault();
event.stopPropagation();
data.Estado = state;
TS_encrypt(data, SECRET, (encrypted) => {
if (typeof ref === 'string') {
DB.put(ref, data._key, encrypted).then(() => {
toastr.success("Guardado!");
});
} else {
try {
// legacy
ref.get(data._key).put(encrypted);
toastr.success("Guardado!");
} catch (e) {
console.warn('Could not save item', e);
}
if (typeof ref === 'string') {
DB.put(ref, data._key, data).then(() => {
toastr.success("Guardado!");
}).catch((e) => { console.warn('DB.put error', e); });
} else {
try {
// legacy
ref.get(data._key).put(data);
toastr.success("Guardado!");
} catch (e) {
console.warn('Could not save item', e);
}
});
}
return false;
};
return button;
@@ -992,6 +1041,17 @@ function TS_IndexElement(
infoSpan.style.color = "black";
const img = document.createElement("img");
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;
infoSpan.appendChild(img);
infoSpan.appendChild(document.createElement("br"));
@@ -1033,9 +1093,9 @@ function TS_IndexElement(
debounce(debounce_load, render, 300, [rows]);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data, key);
});
}, ref, key);
} else {
add_row(data, key);
}
@@ -1054,9 +1114,9 @@ function TS_IndexElement(
debounce(debounce_load, render, 300, [rows]);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data, key);
});
}, undefined, undefined);
} else {
add_row(data, key);
}
@@ -1127,7 +1187,7 @@ function SetPages() {
document.getElementById("appendApps2").append(a);
}
var Booted = false;
var TimeoutBoot = 6;
var TimeoutBoot = 3; // in loops of 750ms
var BootLoops = 0;
function getPeers() {
@@ -1220,12 +1280,10 @@ var BootIntervalID = setInterval(() => {
}
if (!data) {
const persona = { Nombre: 'Admin (bypass)', Roles: 'ADMIN,' };
TS_encrypt(persona, SECRET, (encrypted) => {
DB.put('personas', bypassId, encrypted).then(() => finish(persona, bypassId)).catch((e) => { console.warn('AC_BYPASS create error', e); open_page('login'); });
});
DB.put('personas', bypassId, persona).then(() => finish(persona, bypassId)).catch((e) => { console.warn('AC_BYPASS create error', e); open_page('login'); });
} else {
if (typeof data === 'string') {
TS_decrypt(data, SECRET, (pdata) => finish(pdata, bypassId));
TS_decrypt(data, SECRET, (pdata) => finish(pdata, bypassId), 'personas', bypassId);
} else {
finish(data, bypassId);
}

View File

@@ -10,6 +10,20 @@ var DB = (function () {
let changes = null;
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) {
return table + ':' + id;
}
@@ -47,6 +61,7 @@ var DB = (function () {
}
function replicateToRemote() {
ensureLocal();
if (!local || !remote) return;
PouchDB.replicate(local, remote, { live: true, retry: true }).on('error', function (err) {
console.warn('Replication error', err);
@@ -67,6 +82,7 @@ var DB = (function () {
}
async function put(table, id, data) {
ensureLocal();
const _id = makeId(table, id);
try {
const existing = await local.get(_id).catch(() => null);
@@ -89,6 +105,7 @@ var DB = (function () {
}
async function get(table, id) {
ensureLocal();
const _id = makeId(table, id);
try {
const doc = await local.get(_id);
@@ -103,6 +120,7 @@ var DB = (function () {
}
async function list(table) {
ensureLocal();
try {
const res = await local.allDocs({ include_docs: true, startkey: table + ':', endkey: table + ':\uffff' });
return res.rows.map(r => {
@@ -112,7 +130,62 @@ var DB = (function () {
} 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) {
ensureLocal();
callbacks[table] = callbacks[table] || [];
callbacks[table].push(cb);
// initial load
@@ -131,8 +204,27 @@ var DB = (function () {
list,
map,
replicateToRemote,
putAttachment,
getAttachment,
_internal: { local }
};
})();
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") {
TS_decrypt(data, SECRET, (data) => {
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data || {});
});
}, 'comedor', CurrentISODate());
} else {
add_row(data || {});
}
@@ -93,9 +93,9 @@ PAGES.aulas = {
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data || {});
});
}, 'notas', 'tareas');
} else {
add_row(data || {});
}
@@ -113,9 +113,9 @@ PAGES.aulas = {
);
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
add_row(data || {});
});
}, 'aulas_informes', 'diario-' + CurrentISODate());
} else {
add_row(data || {});
}
@@ -191,9 +191,9 @@ PAGES.aulas = {
document.getElementById(field_autor).value = data["Solicitante"] || SUB_LOGGED_IN_ID || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
load_data(data, "%E");
});
}, 'aulas_solicitudes', mid);
} else {
load_data(data || {});
}
@@ -204,16 +204,14 @@ PAGES.aulas = {
Contenido: document.getElementById(field_contenido).value,
Asunto: document.getElementById(field_asunto).value,
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
DB.put('aulas_solicitudes', mid, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("aulas,solicitudes");
}, SAVE_WAIT);
});
});
document.getElementById("actionStatus").style.display = "block";
DB.put('aulas_solicitudes', mid, data).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("aulas,solicitudes");
}, SAVE_WAIT);
}).catch((e) => { console.warn('DB.put error', e); });
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar esta solicitud?") == true) {
@@ -338,16 +336,14 @@ PAGES.aulas = {
Asunto: document.getElementById(field_asunto).value,
Fecha: document.getElementById(field_fecha).value || CurrentISODate(),
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
DB.put('aulas_informes', mid, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("aulas,informes");
}, SAVE_WAIT);
});
});
document.getElementById("actionStatus").style.display = "block";
DB.put('aulas_informes', mid, data).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("aulas,informes");
}, SAVE_WAIT);
}).catch((e) => { console.warn('DB.put error', e); });
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar este informe?") == true) {

View File

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

View File

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

View File

@@ -75,9 +75,9 @@ PAGES.dataman = {
const value = entry.data;
if (value != null) {
if (typeof value == 'string') {
TS_decrypt(value, SECRET, (data) => {
TS_decrypt(value, SECRET, (data, wasEncrypted) => {
output.materiales[key] = data;
});
}, 'materiales', key);
} else {
output.materiales[key] = value;
}
@@ -89,9 +89,9 @@ PAGES.dataman = {
const value = entry.data;
if (value != null) {
if (typeof value == 'string') {
TS_decrypt(value, SECRET, (data) => {
TS_decrypt(value, SECRET, (data, wasEncrypted) => {
output.personas[key] = data;
});
}, 'personas', key);
} else {
output.personas[key] = value;
}
@@ -168,12 +168,18 @@ PAGES.dataman = {
var sel = document.getElementById(select_type).value;
if (sel == "%telesec") {
// 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 {
Object.entries(JSON.parse(val)["data"]).forEach((entry) => {
var enc = TS_encrypt(entry[1], SECRET, (encrypted) => {
DB.put(sel, entry[0], encrypted);
});
DB.put(sel, entry[0], entry[1]).catch((e) => { console.warn('DB.put error', e); });
});
}
setTimeout(() => {

View File

@@ -101,10 +101,9 @@ PAGES.login = {
document.getElementById(btn_bypass_create).onclick = () => {
var name = prompt("Nombre de la persona (ej: Admin):");
if (!name) return;
var id = 'bypass-' + Date.now();
var persona = { Nombre: name, Roles: 'ADMIN,' };
TS_encrypt(persona, SECRET, (encrypted) => {
DB.put('personas', id, encrypted).then(() => {
var id = 'bypass-' + Date.now();
var persona = { Nombre: name, Roles: 'ADMIN,' };
DB.put('personas', id, persona).then(() => {
toastr.success('Persona creada: ' + id);
localStorage.setItem('TELESEC_BYPASS_ID', id);
SUB_LOGGED_IN_ID = id;
@@ -115,7 +114,6 @@ PAGES.login = {
}).catch((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"] || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
load_data(data, "%E");
});
}, 'materiales', mid);
} else {
load_data(data || {});
}
@@ -91,16 +91,14 @@ PAGES.materiales = {
Revision: document.getElementById(field_revision).value,
Notas: document.getElementById(field_notas).value,
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
DB.put('materiales', mid, encrypted).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("materiales");
}, SAVE_WAIT);
});
});
document.getElementById("actionStatus").style.display = "block";
DB.put('materiales', mid, data).then(() => {
toastr.success("Guardado!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("materiales");
}, SAVE_WAIT);
}).catch((e) => { console.warn('DB.put error', e); });
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm("¿Quieres borrar este material?") == true) {
@@ -184,11 +182,11 @@ PAGES.materiales = {
}
if (typeof data === "string") {
TS_decrypt(data, SECRET, (dec) => {
TS_decrypt(data, SECRET, (dec, wasEncrypted) => {
if (dec && typeof dec === "object") {
addUbicacion(dec);
}
});
}, 'materiales', key);
} else {
addUbicacion(data);
}

View File

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

View File

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

View File

@@ -66,7 +66,7 @@ PAGES.personas = {
<button type="button" id="${btn_ver_monedero}" class="btn5">Ver Transacciones del Monedero</button>
</div>
</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>
<div style="padding: 15px;">
<label>
@@ -110,9 +110,16 @@ PAGES.personas = {
document.getElementById(field_nombre).value = data["Nombre"] || "";
document.getElementById(field_zona).value = data["Region"] || "";
document.getElementById(field_anilla).value = data["SC_Anilla"] || "";
document.getElementById(render_foto).src =
data["Foto"] || "static/ico/user_generic.png";
// set fallback image immediately
document.getElementById(render_foto).src = 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_monedero_balance).value =
data["Monedero_Balance"] || 0;
@@ -120,29 +127,25 @@ PAGES.personas = {
data["Monedero_Notas"] || "";
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, (data) => {
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
load_data(data, "%E");
});
}, 'personas', mid);
} else {
load_data(data || {});
}
});
document
.getElementById(field_foto)
.addEventListener("change", function (e) {
const file = e.target.files[0];
if (!file) return;
resizeInputImage(
file,
function (url) {
document.getElementById(render_foto).src = url;
resized = url;
},
256,
0.7
);
});
document.getElementById(field_foto).addEventListener("change", function (e) {
const file = e.target.files[0];
if (!file) return;
// Do NOT resize — keep original uploaded image
const reader = new FileReader();
reader.onload = function (ev) {
const url = ev.target.result;
document.getElementById(render_foto).src = url;
resized = url;
};
reader.readAsDataURL(file);
});
document.getElementById(btn_guardar).onclick = () => {
var dt = new FormData(pdel);
var data = {
@@ -150,23 +153,28 @@ PAGES.personas = {
Region: document.getElementById(field_zona).value,
Roles: dt.getAll("perm").join(",") + ",",
SC_Anilla: document.getElementById(field_anilla).value,
Foto: resized,
// Foto moved to PouchDB attachment named 'foto'
markdown: document.getElementById(field_notas).value,
Monedero_Balance:
parseFloat(document.getElementById(field_monedero_balance).value) ||
0,
Monedero_Notas: document.getElementById(field_monedero_notas).value,
};
var enc = TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
DB.put('personas', mid, encrypted).then(() => {
document.getElementById("actionStatus").style.display = "block";
DB.put('personas', mid, data).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!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("personas");
}, 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 = () => {
setUrlHash("pagos"); // Navigate to pagos and show transactions for this person

View File

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

View File

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