5 Commits

9 changed files with 712 additions and 957 deletions

View File

@@ -65,9 +65,11 @@ a:hover {
@media print {
.supermesh-indicator,
.no_print,
.no_print * {
.no_print *,
.saveico, .delico, .opicon {
display: none !important;
}
main {padding: 0;}
}
button,

View File

@@ -372,7 +372,7 @@ def should_shutdown(data: Dict[str, Any], server_now: datetime) -> bool:
target = parse_iso(str(data.get("ShutdownBeforeDate", "") or ""))
if not target:
return False
return server_now >= target
return server_now <= target
def execute_shutdown(dry_run: bool = False) -> None:
@@ -421,8 +421,8 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--secret", default="", help="TeleSec secret para cifrado")
parser.add_argument("--machine-id", default="", help="ID de máquina (default: hostname)")
parser.add_argument("--interval", type=int, default=15, help="Intervalo en segundos")
parser.add_argument("--once", action="store_false", help="Ejecutar una sola iteración")
parser.add_argument("--dry-run", action="store_false", help="No apagar realmente, solo log")
parser.add_argument("--once", action="store_true", help="Ejecutar una sola iteración")
parser.add_argument("--dry-run", action="store_true", help="No apagar realmente, solo log")
parser.add_argument(
"--config",
default="",

View File

@@ -85,15 +85,11 @@
<script src="page/dataman.js"></script>
<script src="page/aulas.js"></script>
<script src="page/materiales.js"></script>
<script src="page/resumen_diario.js"></script>
<script src="page/personas.js"></script>
<script src="page/supercafe.js"></script>
<!-- <script src="page/avisos.js"></script> -->
<script src="page/comedor.js"></script>
<script src="page/notas.js"></script>
<script src="page/mensajes.js"></script>
<script src="page/panel.js"></script>
<!-- <script src="page/chat.js"></script> -->
<script src="page/buscar.js"></script>
<script src="page/pagos.js"></script>
</body>

View File

@@ -1,4 +1,6 @@
PERMS['aulas'] = 'Aulas (Solo docentes!)';
PERMS['aulas:resumen_diario'] = '> Resumen diario';
PERMS['aulas:puntos_interes'] = '> Puntos de interés';
PAGES.aulas = {
//navcss: "btn1",
Title: 'Gest-Aula',
@@ -13,168 +15,102 @@ PAGES.aulas = {
var data_Tareas = safeuuid();
var data_Diario = safeuuid();
var data_Weather = safeuuid();
var link_alertas = safeuuid();
var link_diario = safeuuid();
var link_actividades = safeuuid();
var link_puntos_interes = safeuuid();
container.innerHTML = html`
<h1>Gestión del Aula</h1>
<div>
<fieldset style="float: left;">
<legend><img src="${PAGES.notas.icon}" height="20" /> Notas esenciales</legend>
<a class="button" style="font-size: 25px;" href="#notas,inicio_dia"
>Como iniciar el día</a
>
<a class="button" style="font-size: 25px;" href="#notas,realizacion_cafe"
>Como realizar el café</a
>
<a class="button" style="font-size: 25px;" href="#notas,fin_dia">Como acabar el día</a>
<a class="button" style="font-size: 25px;" href="#notas,horario">Horario</a>
<a class="button" style="font-size: 25px;" href="#notas,tareas">Tareas</a>
<legend>Atajos de hoy</legend>
<a class="button" id="${link_alertas}" href="#notas,alertas">
Alertas
</a>
<a class="button btn2" href="#aulas,solicitudes,${safeuuid('')}">
Solicitar material
</a>
<a class="button" id="${link_diario}" href="#aulas,informes,diario-${CurrentISODate()}">
Informe
</a>
<a class="button" id="${link_actividades}" href="#aulas,informes,actividades-${CurrentISODate()}">
Actividades
</a>
<a class="button btn5" href="#aulas,ordenadores">
Ordenadores
</a>
<a class="button btn6" href="#aulas,resumen_diario">
Resumen Diario
</a>
</fieldset>
<fieldset style="float: left;">
<legend>Acciones</legend>
<a class="button" style="font-size: 25px;" href="#aulas,solicitudes"
><img src="${PAGES.materiales.icon}" height="20" /> Solicitudes de material</a
>
<a
class="button"
style="font-size: 25px;"
href="#aulas,informes,diario-${CurrentISODate()}"
>Diario de hoy</a
>
<a class="button rojo" style="font-size: 25px;" href="#notas,alertas"
><img src="${PAGES.notas.icon}" height="20" /> Ver Alertas</a
>
<a class="button" style="font-size: 25px;" href="#aulas,informes"
><img src="${PAGES.aulas.icon}" height="20" /> Informes y diarios</a
>
<a class="button btn4" style="font-size: 25px;" href="#supercafe"
><img src="${PAGES.supercafe.icon}" height="20" /> Ver comandas</a
>
<a class="button btn8" style="font-size: 25px;" href="#aulas,ordenadores"
><img src="${PAGES.aulas.icon}" height="20" /> Control de ordenadores</a
>
</fieldset>
<fieldset style="float: left;">
<legend>Datos de hoy</legend>
<span
class="btn7"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black; max-width: 25rem;"
><b>Menú Comedor:</b> <br /><span id="${data_Comedor}">Cargando...</span></span
>
<span
class="btn6"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black; max-width: 25rem;"
><b>Tareas:</b> <br />
<pre style="overflow-wrap: break-word;white-space:pre-wrap;" id="${data_Tareas}">
Cargando...</pre
>
</span>
<span
class="btn5"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black; max-width: 25rem;"
><b>Diario:</b> <br />
<pre style="overflow-wrap: break-word;white-space:pre-wrap;" id="${data_Diario}">
Cargando...</pre
>
</span>
<span
class="btn4"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black; max-width: 25rem;"
><b>Clima:</b> <br /><img
loading="lazy"
style="padding: 15px; background-color: white; width: 245px;"
id="${data_Weather}"
/></span>
<a class="button" style="font-size: 25px;" href="#aulas,solicitudes">
Solicitudes de materiales
</a>
<a class="button" style="font-size: 25px;" href="#aulas,informes">
Informes
</a>
<a class="button btn8" style="font-size: 25px;" href="#aulas,puntos_interes">
Puntos de interés
</a>
</fieldset>
</div>
`;
//#region Cargar Clima
// Get location from DB settings.weather_location; if missing ask user and save it
// url format: https://wttr.in/<loc>?F0m
DB.get('settings', 'weather_location').then((loc) => {
if (!loc) {
loc = prompt('Introduce tu ubicación para el clima (ciudad, país):', 'Madrid, Spain');
if (loc) {
DB.put('settings', 'weather_location', loc);
}
}
if (loc) {
document.getElementById(data_Weather).src =
'https://wttr.in/' + encodeURIComponent(loc) + '_IF0m_background=FFFFFF.png';
} else {
document.getElementById(data_Weather).src = 'https://wttr.in/_IF0m_background=FFFFFF.png';
}
//#region Contar alertas activas y mostrarlas en el botón
DB.get('notas', 'alertas')
.then((res) => TS_decrypt(res, SECRET, (data) => {
var count = 0;
// Sumar el total de alertas activas, cada linea de "Contenido"
// es una alerta, aunque podrían hacerse varias por nota.
// Ignora lineas que no empiezen por > (por si el profesor escribe algo que no es una alerta)
data.Contenido.split('\n').forEach((line) => {
if (line.trim().startsWith('>')) count++;
});
//#endregion Cargar Clima
//#region Cargar Comedor
DB.get('comedor', CurrentISODate()).then((data) => {
function add_row(data) {
// Fix newlines
data.Platos = data.Platos || 'No hay platos registrados para hoy.';
// Display platos
document.getElementById(data_Comedor).innerHTML = data.Platos.replace(/\n/g, '<br>');
}
if (typeof data == 'string') {
TS_decrypt(
data,
SECRET,
(data, wasEncrypted) => {
add_row(data || {});
},
'comedor',
CurrentISODate()
);
if (count > 0) {
document.getElementById(link_alertas).innerText = `Alertas (${count})`;
document.getElementById(link_alertas).classList.add('rojo');
} else {
add_row(data || {});
document.getElementById(link_alertas).innerText = 'Alertas';
document.getElementById(link_alertas).classList.remove('rojo');
}
}))
.catch((e) => {
console.warn('Error contando alertas activas', e);
});
//#endregion Cargar Comedor
//#region Cargar Tareas
DB.get('notas', 'tareas').then((data) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || 'No hay tareas.';
// Display platos
document.getElementById(data_Tareas).innerHTML = data.Contenido.replace(/\n/g, '<br>');
}
if (typeof data == 'string') {
TS_decrypt(
data,
SECRET,
(data, wasEncrypted) => {
add_row(data || {});
},
'notas',
'tareas'
);
//#endregion Contar alertas activas
//#region Comprobar si hay un diario para hoy y marcar el botón
DB.get('aulas_informes', 'diario-' + CurrentISODate())
.then((res) => {
if (res) {
document.getElementById(link_diario).classList.add('btn2');
} else {
add_row(data || {});
document.getElementById(link_diario).classList.remove('btn2');
}
})
.catch((e) => {
console.warn('Error comprobando diario de hoy', e);
});
//#endregion Cargar Tareas
//#region Cargar Diario
DB.get('aulas_informes', 'diario-' + CurrentISODate()).then((data) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || 'No hay un diario.';
// Display platos
document.getElementById(data_Diario).innerHTML = data.Contenido.replace(/\n/g, '<br>');
}
if (typeof data == 'string') {
TS_decrypt(
data,
SECRET,
(data, wasEncrypted) => {
add_row(data || {});
},
'aulas_informes',
'diario-' + CurrentISODate()
);
//#endregion Comprobar diario
//#region Comprobar si hay un informe de actividades para hoy y contar las actividades (mismo formato que alertas)
DB.get('aulas_informes', 'actividades-' + CurrentISODate()).then((res) => TS_decrypt(res, SECRET, (data) => {
var count = 0;
data.Contenido.split('\n').forEach((line) => {
if (line.trim().startsWith('>')) count++;
});
if (count > 0) {
document.getElementById(link_actividades).innerText = `Actividades (${count})`;
document.getElementById(link_actividades).classList.add('btn4');
} else {
add_row(data || {});
document.getElementById(link_actividades).innerText = 'Actividades';
document.getElementById(link_actividades).classList.remove('btn4');
}
}))
.catch((e) => {
console.warn('Error comprobando actividades de hoy', e);
});
//#endregion Cargar Diario
//#endregion Comprobar actividades
},
_solicitudes: function () {
const tablebody = safeuuid();
@@ -190,7 +126,7 @@ Cargando...</pre
[
{
key: 'Solicitante',
type: 'persona',
type: 'persona-nombre',
default: '',
label: 'Solicitante',
},
@@ -233,8 +169,14 @@ Cargando...</pre
><br /><br />
</label>
<hr />
<button class="btn5" id="${btn_guardar}">Guardar</button>
<button class="rojo" id="${btn_borrar}">Borrar</button>
<button class="saveico" id="${btn_guardar}">
<img src="static/floppy_disk_green.png" />
<br>Guardar
</button>
<button class="delico" id="${btn_borrar}">
<img src="static/garbage.png" />
<br>Borrar
</button>
</fieldset>
`;
(async () => {
@@ -311,7 +253,7 @@ Cargando...</pre
<div
style="display: inline-block; border: 2px solid black; padding: 5px; border-radius: 5px;"
>
<b>Diario:</b><br />
<b>Por fecha:</b><br />
<input type="date" id="${field_new_byday}" value="${CurrentISODate()}" />
<button id="${btn_new_byday}">Abrir / Nuevo</button>
</div>
@@ -324,7 +266,7 @@ Cargando...</pre
[
{
key: 'Autor',
type: 'persona',
type: 'persona-nombre',
default: '',
label: 'Autor',
},
@@ -367,7 +309,10 @@ Cargando...</pre
var title = '';
if (mid.startsWith('diario-')) {
var date = mid.replace('diario-', '').split('-');
title = 'Diario ' + date[2] + '/' + date[1] + '/' + date[0];
title = 'Informe del ' + date[2] + '/' + date[1] + '/' + date[0];
} else if (mid.startsWith('actividades-')) {
var date = mid.replace('actividades-', '').split('-');
title = 'Actividades para el ' + date[2] + '/' + date[1] + '/' + date[0];
}
container.innerHTML = html`
<a class="button" href="#aulas,informes">← Volver a informes</a>
@@ -388,8 +333,14 @@ Cargando...</pre
><br /><br />
</label>
<hr />
<button class="btn5" id="${btn_guardar}">Guardar</button>
<button class="rojo" id="${btn_borrar}">Borrar</button>
<button class="saveico" id="${btn_guardar}">
<img src="static/floppy_disk_green.png" />
<br>Guardar
</button>
<button class="delico" id="${btn_borrar}">
<img src="static/garbage.png" />
<br>Borrar
</button>
</fieldset>
`;
(async () => {
@@ -471,6 +422,54 @@ Cargando...</pre
);
});
},
__leafletPromise: null,
__ensureLeaflet: function () {
if (window.L && typeof window.L.map === 'function') {
return Promise.resolve(window.L);
}
if (PAGES.aulas.__leafletPromise) {
return PAGES.aulas.__leafletPromise;
}
PAGES.aulas.__leafletPromise = new Promise((resolve, reject) => {
try {
if (!document.getElementById('telesec-leaflet-css')) {
var css = document.createElement('link');
css.id = 'telesec-leaflet-css';
css.rel = 'stylesheet';
css.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
css.integrity = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=';
css.crossOrigin = '';
document.head.appendChild(css);
}
if (window.L && typeof window.L.map === 'function') {
resolve(window.L);
return;
}
var existing = document.getElementById('telesec-leaflet-js');
if (existing) {
existing.addEventListener('load', () => resolve(window.L));
existing.addEventListener('error', () => reject(new Error('No se pudo cargar Leaflet')));
return;
}
var script = document.createElement('script');
script.id = 'telesec-leaflet-js';
script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
script.integrity = 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=';
script.crossOrigin = '';
script.onload = () => resolve(window.L);
script.onerror = () => reject(new Error('No se pudo cargar Leaflet'));
document.body.appendChild(script);
} catch (e) {
reject(e);
}
});
return PAGES.aulas.__leafletPromise;
},
__getServerNow: async function () {
try {
var couchUrl = (localStorage.getItem('TELESEC_COUCH_URL') || '').replace(/\/$/, '');
@@ -659,6 +658,510 @@ Cargando...</pre
await loadData();
};
},
_puntos_interes: function () {
var map_id = safeuuid();
var btn_new = safeuuid();
var btn_my_gps = safeuuid();
container.innerHTML = html`
<a class="button" href="#aulas">← Volver a Gestión de Aulas</a>
<h1>Puntos de interés</h1>
<p>Registra ubicaciones (tiendas, bares, entidades, etc.) y visualízalas en el mapa.</p>
<button id="${btn_new}">Nuevo punto</button>
<button class="btn5" id="${btn_my_gps}">Centrar en mi GPS</button>
<div id="${map_id}" style="height: 380px; border: 2px solid black; margin-top: 8px; border-radius: 8px;"></div>
<div id="cont"></div>
`;
var map = null;
var layer = null;
var markers = {};
function parseCoord(value) {
if (value === null || value === undefined || value === '') return null;
var parsed = parseFloat(String(value).replace(',', '.'));
return isNaN(parsed) ? null : parsed;
}
function updateMarker(data) {
if (!map || !layer || !data || !data._key) return;
var lat = parseCoord(data.Latitud);
var lng = parseCoord(data.Longitud);
if (lat === null || lng === null) {
if (markers[data._key]) {
layer.removeLayer(markers[data._key]);
delete markers[data._key];
}
return;
}
var popup = '<b>' + (data.Nombre || data._key) + '</b>';
if (data.Tipo) popup += '<br>' + data.Tipo;
if (data.Direccion) popup += '<br>' + data.Direccion;
if (markers[data._key]) {
markers[data._key].setLatLng([lat, lng]).bindPopup(popup);
} else {
markers[data._key] = L.marker([lat, lng]).addTo(layer).bindPopup(popup);
}
}
function removeMarker(key) {
if (!layer) return;
if (markers[key]) {
layer.removeLayer(markers[key]);
delete markers[key];
}
}
function focusPoint(data) {
if (!map) return;
var lat = parseCoord(data.Latitud);
var lng = parseCoord(data.Longitud);
if (lat === null || lng === null) {
toastr.error('Este punto no tiene coordenadas válidas');
return;
}
map.setView([lat, lng], 17);
if (markers[data._key]) {
markers[data._key].openPopup();
}
}
TS_IndexElement(
'aulas,puntos_interes',
[
{ key: 'Nombre', type: 'raw', default: '', label: 'Nombre' },
{ key: 'Tipo', type: 'raw', default: '', label: 'Tipo' },
{ key: 'Direccion', type: 'raw', default: '', label: 'Dirección' },
{
key: 'Latitud',
type: 'template',
label: 'Ubicación',
template: (data, td) => {
var lat = parseCoord(data.Latitud);
var lng = parseCoord(data.Longitud);
if (lat === null || lng === null) {
td.innerText = 'Sin coordenadas';
return;
}
var txt = document.createElement('div');
txt.innerText = lat.toFixed(6) + ', ' + lng.toFixed(6);
td.appendChild(txt);
var btn = document.createElement('button');
btn.className = 'btn6';
btn.innerText = 'Ver en mapa';
btn.onclick = (ev) => {
ev.preventDefault();
ev.stopPropagation();
focusPoint(data);
return false;
};
td.appendChild(btn);
},
},
],
'aulas_puntos_interes',
document.querySelector('#cont')
);
document.getElementById(btn_new).onclick = () => {
setUrlHash('aulas,puntos_interes,' + safeuuid(''));
};
document.getElementById(btn_my_gps).onclick = () => {
if (!navigator.geolocation) {
toastr.error('Tu navegador no soporta geolocalización');
return;
}
navigator.geolocation.getCurrentPosition(
(pos) => {
if (!map) return;
map.setView([pos.coords.latitude, pos.coords.longitude], 16);
L.circleMarker([pos.coords.latitude, pos.coords.longitude], {
radius: 8,
color: '#2b8a3e',
fillColor: '#2b8a3e',
fillOpacity: 0.7,
})
.addTo(map)
.bindPopup('Tu ubicación actual')
.openPopup();
},
(err) => {
console.warn('Error obteniendo GPS', err);
toastr.error('No se pudo obtener la ubicación GPS');
},
{ enableHighAccuracy: true, timeout: 10000 }
);
};
(async () => {
try {
await PAGES.aulas.__ensureLeaflet();
map = L.map(map_id).setView([40.4168, -3.7038], 6);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; OpenStreetMap',
}).addTo(map);
layer = L.layerGroup().addTo(map);
EventListeners.DB.push(
DB.map('aulas_puntos_interes', (raw, key) => {
if (raw === null) {
removeMarker(key);
return;
}
PAGES.aulas
.__decryptIfNeeded('aulas_puntos_interes', key, raw)
.then((data) => {
data = data || {};
data._key = key;
updateMarker(data);
})
.catch((e) => {
console.warn('Error cargando punto de interés para mapa', e);
});
})
);
} catch (e) {
console.warn('Leaflet no disponible', e);
toastr.error('No se pudo cargar el mapa');
}
})();
},
_puntos_interes__edit: function (mid) {
var field_nombre = safeuuid();
var field_tipo = safeuuid();
var field_direccion = safeuuid();
var field_descripcion = safeuuid();
var field_lat = safeuuid();
var field_lng = safeuuid();
var btn_gps = safeuuid();
var btn_guardar = safeuuid();
var btn_borrar = safeuuid();
var map_id = safeuuid();
container.innerHTML = html`
<a class="button" href="#aulas,puntos_interes">← Volver a puntos de interés</a>
<h1>Punto de interés <code>${mid}</code></h1>
<fieldset style="float: none; width: calc(100% - 40px); max-width: none;">
<legend>Datos</legend>
<label>Nombre<br /><input type="text" id="${field_nombre}" /></label><br /><br />
<label>Tipo (tienda, bar, entidad...)<br /><input type="text" id="${field_tipo}" /></label><br /><br />
<label>Dirección<br /><input type="text" id="${field_direccion}" style="width: calc(100% - 20px);" /></label><br /><br />
<label>Descripción<br /><textarea id="${field_descripcion}" style="width: 100%; height: 120px;"></textarea></label><br /><br />
<div style="display: flex; gap: 8px; flex-wrap: wrap; align-items: end;">
<label>Latitud<br /><input type="text" id="${field_lat}" placeholder="40.4168" /></label>
<label>Longitud<br /><input type="text" id="${field_lng}" placeholder="-3.7038" /></label>
<button class="btn5" id="${btn_gps}">Usar GPS</button>
</div>
<br />
<div id="${map_id}" style="height: 360px; border: 2px solid black; border-radius: 8px;"></div>
<hr />
<button class="saveico" id="${btn_guardar}">
<img src="static/floppy_disk_green.png" />
<br>Guardar
</button>
<button class="delico" id="${btn_borrar}">
<img src="static/garbage.png" />
<br>Borrar
</button>
</fieldset>
`;
var map = null;
var marker = null;
function parseCoord(value) {
if (value === null || value === undefined || value === '') return null;
var parsed = parseFloat(String(value).replace(',', '.'));
return isNaN(parsed) ? null : parsed;
}
function setCoordInputs(lat, lng) {
document.getElementById(field_lat).value =
lat === null || lat === undefined ? '' : Number(lat).toFixed(6);
document.getElementById(field_lng).value =
lng === null || lng === undefined ? '' : Number(lng).toFixed(6);
}
function refreshMarker(centerMap = false) {
if (!map) return;
var lat = parseCoord(document.getElementById(field_lat).value);
var lng = parseCoord(document.getElementById(field_lng).value);
if (lat === null || lng === null) {
if (marker) {
map.removeLayer(marker);
marker = null;
}
return;
}
if (!marker) {
marker = L.marker([lat, lng], { draggable: true }).addTo(map);
marker.on('dragend', (ev) => {
var ll = ev.target.getLatLng();
setCoordInputs(ll.lat, ll.lng);
});
} else {
marker.setLatLng([lat, lng]);
}
if (centerMap) {
map.setView([lat, lng], 17);
}
}
(async () => {
try {
await PAGES.aulas.__ensureLeaflet();
map = L.map(map_id).setView([40.4168, -3.7038], 6);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; OpenStreetMap',
}).addTo(map);
map.on('click', (ev) => {
setCoordInputs(ev.latlng.lat, ev.latlng.lng);
refreshMarker(false);
});
var raw = await DB.get('aulas_puntos_interes', mid);
var data = await PAGES.aulas.__decryptIfNeeded('aulas_puntos_interes', mid, raw);
data = data || {};
document.getElementById(field_nombre).value = data.Nombre || '';
document.getElementById(field_tipo).value = data.Tipo || '';
document.getElementById(field_direccion).value = data.Direccion || '';
document.getElementById(field_descripcion).value = data.Descripcion || '';
setCoordInputs(parseCoord(data.Latitud), parseCoord(data.Longitud));
refreshMarker(true);
} catch (e) {
console.warn('Error iniciando mapa de punto de interés', e);
toastr.error('No se pudo cargar el mapa del punto de interés');
}
})();
document.getElementById(field_lat).addEventListener('input', () => refreshMarker(false));
document.getElementById(field_lng).addEventListener('input', () => refreshMarker(false));
document.getElementById(btn_gps).onclick = (ev) => {
ev.preventDefault();
if (!navigator.geolocation) {
toastr.error('Tu navegador no soporta geolocalización');
return false;
}
navigator.geolocation.getCurrentPosition(
(pos) => {
setCoordInputs(pos.coords.latitude, pos.coords.longitude);
refreshMarker(true);
toastr.success('Ubicación GPS capturada');
},
(err) => {
console.warn('Error GPS', err);
toastr.error('No se pudo obtener tu ubicación GPS');
},
{ enableHighAccuracy: true, timeout: 10000 }
);
return false;
};
document.getElementById(btn_guardar).onclick = () => {
var guardarBtn = document.getElementById(btn_guardar);
if (guardarBtn.disabled) return;
var lat = parseCoord(document.getElementById(field_lat).value);
var lng = parseCoord(document.getElementById(field_lng).value);
var hasLat = document.getElementById(field_lat).value.trim() !== '';
var hasLng = document.getElementById(field_lng).value.trim() !== '';
if ((hasLat && lat === null) || (hasLng && lng === null) || (hasLat !== hasLng)) {
toastr.error('Introduce latitud y longitud válidas');
return;
}
guardarBtn.disabled = true;
guardarBtn.style.opacity = '0.5';
var data = {
Nombre: document.getElementById(field_nombre).value,
Tipo: document.getElementById(field_tipo).value,
Direccion: document.getElementById(field_direccion).value,
Descripcion: document.getElementById(field_descripcion).value,
Latitud: lat === null ? '' : Number(lat).toFixed(6),
Longitud: lng === null ? '' : Number(lng).toFixed(6),
UpdatedAt: new Date().toISOString(),
Autor: SUB_LOGGED_IN_ID || '',
};
document.getElementById('actionStatus').style.display = 'block';
DB.put('aulas_puntos_interes', mid, data)
.then(() => {
toastr.success('Guardado!');
setTimeout(() => {
document.getElementById('actionStatus').style.display = 'none';
setUrlHash('aulas,puntos_interes');
}, SAVE_WAIT);
})
.catch((e) => {
console.warn('DB.put error', e);
guardarBtn.disabled = false;
guardarBtn.style.opacity = '1';
document.getElementById('actionStatus').style.display = 'none';
toastr.error('Error al guardar el punto de interés');
});
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm('¿Quieres borrar este punto de interés?') == true) {
DB.del('aulas_puntos_interes', mid).then(() => {
toastr.error('Borrado!');
setTimeout(() => {
setUrlHash('aulas,puntos_interes');
}, SAVE_WAIT);
});
}
};
},
_resumen_diario: function () {
var data_Comedor = safeuuid();
var data_Tareas = safeuuid();
var data_Diario = safeuuid();
var data_Weather = safeuuid();
if (!checkRole('aulas:resumen_diario')) {
setUrlHash('index');
return;
}
container.innerHTML = html`
<h1>Resumen Diario ${CurrentISODate()}</h1>
<button onclick="print()" class="no_print">Imprimir</button>
<a class="button no_print" href="#aulas">← Volver a Gestión de Aulas</a>
<br /><span
class="btn7"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black;"
><b>Menú Comedor:</b> <br /><span id="${data_Comedor}">Cargando...</span></span
>
<br /><span
class="btn6"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black;"
><b>Tareas:</b> <br />
<pre style="overflow-wrap: break-word;white-space:pre-wrap;" id="${data_Tareas}">
Cargando...</pre
>
</span>
<br /><span
class="btn5"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black;"
><b>Informe:</b> <br />
<pre style="overflow-wrap: break-word;white-space:pre-wrap;" id="${data_Diario}">
Cargando...</pre
>
</span>
<br /><span
class="btn4"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black;"
><b>Clima:</b> <br /><img
loading="lazy"
style="padding: 15px; background-color: white; height: 75px;"
id="${data_Weather}"
/></span>
`;
//#region Cargar Clima
// Get location from DB settings.weather_location; if missing ask user and save it
// url format: https://wttr.in/<loc>?F0m
DB.get('settings', 'weather_location').then((loc) => {
if (!loc) {
loc = prompt('Introduce tu ubicación para el clima (ciudad, país):', 'Madrid, Spain');
if (loc) {
DB.put('settings', 'weather_location', loc);
}
}
if (loc) {
document.getElementById(data_Weather).src =
'https://wttr.in/' + encodeURIComponent(loc) + '_IF0m_background=FFFFFF.png';
} else {
document.getElementById(data_Weather).src = 'https://wttr.in/_IF0m_background=FFFFFF.png';
}
});
//#endregion Cargar Clima
//#region Cargar Comedor
DB.get('comedor', CurrentISODate()).then((data) => {
function add_row(data) {
if (!data.Primero) {
var result = 'No hay información del comedor para hoy.';
} else {
var result = data.Primero + "<br>" + data.Segundo + "<br>" + data.Postre;
}
// Display platos
document.getElementById(data_Comedor).innerHTML = result;
}
if (typeof data == 'string') {
TS_decrypt(
data,
SECRET,
(data, wasEncrypted) => {
add_row(data || {});
},
'comedor',
CurrentISODate()
);
} else {
add_row(data || {});
}
});
//#endregion Cargar Comedor
//#region Cargar Tareas
DB.get('notas', 'tareas').then((data) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || 'No hay tareas.';
// Display tareas
document.getElementById(data_Tareas).innerHTML = data.Contenido.replace(/\n/g, '<br>');
}
if (typeof data == 'string') {
TS_decrypt(
data,
SECRET,
(data, wasEncrypted) => {
add_row(data || {});
},
'notas',
'tareas'
);
} else {
add_row(data || {});
}
});
//#endregion Cargar Tareas
//#region Cargar Diario
DB.get('aulas_informes', 'diario-' + CurrentISODate()).then((data) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || 'No hay un diario.';
// Display platos
document.getElementById(data_Diario).innerHTML = data.Contenido.replace(/\n/g, '<br>');
}
if (typeof data == 'string') {
TS_decrypt(
data,
SECRET,
(data, wasEncrypted) => {
add_row(data || {});
},
'aulas_informes',
'diario-' + CurrentISODate()
);
} else {
add_row(data || {});
}
});
//#endregion Cargar Diario
},
edit: function (fsection) {
if (!checkRole('aulas')) {
setUrlHash('index');
@@ -678,6 +1181,12 @@ Cargando...</pre
case 'ordenadores':
this._ordenadores();
break;
case 'puntos_interes':
this._puntos_interes();
break;
case 'resumen_diario':
this._resumen_diario();
break;
default:
this.index();
break;
@@ -694,6 +1203,9 @@ Cargando...</pre
case 'ordenadores':
this._ordenadores__edit(item);
break;
case 'puntos_interes':
this._puntos_interes__edit(item);
break;
}
}
},

View File

@@ -1,247 +0,0 @@
PERMS['avisos'] = 'Avisos';
PERMS['avisos:edit'] = '&gt; Editar';
PAGES.avisos = {
navcss: 'btn5',
icon: 'static/appico/File_Plugin.svg',
AccessControl: true,
Title: 'Avisos',
edit: function (mid) {
if (!checkRole('avisos:edit')) {
setUrlHash('avisos');
return;
}
var nameh1 = safeuuid();
var field_fecha = safeuuid();
var field_asunto = safeuuid();
var field_origen = safeuuid();
var field_destino = safeuuid();
var field_estado = safeuuid();
var field_mensaje = safeuuid();
var field_respuesta = safeuuid();
var btn_leer = safeuuid();
var btn_desleer = safeuuid();
var btn_guardar = safeuuid();
var btn_borrar = safeuuid();
var div_actions = safeuuid();
container.innerHTML = html`
<h1>Aviso <code id="${nameh1}"></code></h1>
<fieldset style="float: left;">
<legend>Valores</legend>
<label>
Fecha<br />
<input readonly disabled type="text" id="${field_fecha}" value="" /><br /><br />
</label>
<label>
Asunto<br />
<input type="text" id="${field_asunto}" value="" /><br /><br />
</label>
<input type="hidden" id="${field_origen}" />
<input type="hidden" id="${field_destino}" />
<div id="${div_actions}"></div>
<label>
Mensaje<br />
<textarea id="${field_mensaje}"></textarea><br /><br />
</label>
<label>
Respuesta<br />
<textarea id="${field_respuesta}"></textarea><br /><br />
</label>
<label>
Estado<br />
<input readonly disabled type="text" id="${field_estado}" value="" />
<br />
<button id="${btn_leer}">Leido</button>
<button id="${btn_desleer}">No leido</button>
<br />
</label>
<hr />
<button class="btn5" id="${btn_guardar}">Guardar</button>
<button class="rojo" id="${btn_borrar}">Borrar</button>
</fieldset>
`;
document.getElementById(btn_leer).onclick = () => {
document.getElementById(field_estado).value = 'leido';
};
document.getElementById(btn_desleer).onclick = () => {
document.getElementById(field_estado).value = 'por_leer';
};
var divact = document.getElementById(div_actions);
addCategory_Personas(
divact,
SC_Personas,
'',
(value) => {
document.getElementById(field_origen).value = value;
},
'Origen'
);
addCategory_Personas(
divact,
SC_Personas,
'',
(value) => {
document.getElementById(field_destino).value = value;
},
'Destino'
);
(async () => {
const data = await DB.get('notificaciones', mid);
function load_data(data, ENC = '') {
document.getElementById(nameh1).innerText = mid;
document.getElementById(field_fecha).value = data['Fecha'] || CurrentISODate() || '';
document.getElementById(field_asunto).value = data['Asunto'] || '';
document.getElementById(field_mensaje).value = data['Mensaje'] || '';
document.getElementById(field_origen).value = data['Origen'] || SUB_LOGGED_IN_ID || '';
document.getElementById(field_destino).value = data['Destino'] || '';
document.getElementById(field_estado).value = data['Estado'] || '%%' || '';
document.getElementById(field_respuesta).value = data['Respuesta'] || '';
// Persona select
divact.innerHTML = '';
addCategory_Personas(
divact,
SC_Personas,
data['Origen'] || '',
(value) => {
document.getElementById(field_origen).value = value;
},
'Origen'
);
addCategory_Personas(
divact,
SC_Personas,
data['Destino'] || '',
(value) => {
document.getElementById(field_destino).value = value;
},
'Destino'
);
}
if (typeof data == 'string') {
TS_decrypt(
data,
SECRET,
(data, wasEncrypted) => {
load_data(data, '%E');
},
'notificaciones',
mid
);
} else {
load_data(data || {});
}
})();
document.getElementById(btn_guardar).onclick = () => {
// Check if button is already disabled to prevent double-clicking
var guardarBtn = document.getElementById(btn_guardar);
if (guardarBtn.disabled) return;
// Validate before disabling button
if (document.getElementById(field_origen).value == '') {
alert('¡Hay que elegir una persona de origen!');
return;
}
if (document.getElementById(field_destino).value == '') {
alert('¡Hay que elegir una persona de origen!');
return;
}
// Disable button after validation passes
guardarBtn.disabled = true;
guardarBtn.style.opacity = '0.5';
var data = {
Fecha: document.getElementById(field_fecha).value,
Origen: document.getElementById(field_origen).value,
Destino: document.getElementById(field_destino).value,
Mensaje: document.getElementById(field_mensaje).value,
Respuesta: document.getElementById(field_respuesta).value,
Asunto: document.getElementById(field_asunto).value,
Estado: document.getElementById(field_estado).value.replace('%%', 'por_leer'),
};
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);
guardarBtn.disabled = false;
guardarBtn.style.opacity = '1';
document.getElementById('actionStatus').style.display = 'none';
toastr.error('Error al guardar la notificación');
});
};
document.getElementById(btn_borrar).onclick = () => {
if (confirm('¿Quieres borrar esta notificación?') == true) {
DB.del('notificaciones', mid).then(() => {
toastr.error('Borrado!');
setTimeout(() => {
setUrlHash('avisos');
}, SAVE_WAIT);
});
}
};
},
index: function () {
if (!checkRole('avisos')) {
setUrlHash('index');
return;
}
const tablebody = safeuuid();
var btn_new = safeuuid();
container.innerHTML = html`
<h1>Avisos</h1>
<button id="${btn_new}">Nuevo aviso</button>
<div id="cont"></div>
`;
TS_IndexElement(
'avisos',
[
{
key: 'Origen',
type: 'persona',
default: '',
label: 'Origen',
},
{
key: 'Destino',
type: 'persona',
default: '',
label: 'Destino',
},
{
key: 'Asunto',
type: 'raw',
default: '',
label: 'Asunto',
},
{
key: 'Estado',
type: 'raw',
default: '',
label: 'Estado',
},
],
'notificaciones',
document.querySelector('#cont'),
(data, new_tr) => {
new_tr.style.backgroundColor = '#FFCCCB';
if (data.Estado == 'leido') {
new_tr.style.backgroundColor = 'lightgreen';
}
}
);
if (!checkRole('avisos:edit')) {
document.getElementById(btn_new).style.display = 'none';
} else {
document.getElementById(btn_new).onclick = () => {
setUrlHash('avisos,' + safeuuid(''));
};
}
},
};

View File

@@ -285,9 +285,9 @@ PAGES.materiales = {
<div style="display: flex;flex-direction: column;align-items: stretch;gap: 6px;min-width: 180px;flex: 1 1 220px;">
<label for="${mov_tipo}">Tipo</label>
<select id="${mov_tipo}" style="flex: 1;">
<option value="Entrada">Entrada</option>
<option value="Salida">Salida</option>
<option value="Ajuste">Ajuste</option>
<option value="Entrada">Entrada - Meter al almacen</option>
<option value="Salida">Salida - Sacar del almacen</option>
<option value="Ajuste">Ajuste - Existencias actuales</option>
</select>
</div>
<div style="display: flex;flex-direction: column;align-items: stretch;gap: 6px;min-width: 180px;flex: 1 1 220px;">
@@ -424,7 +424,7 @@ PAGES.materiales = {
var nota = document.getElementById(mov_nota).value || '';
var actual = parseNum(document.getElementById(field_cantidad).value, 0);
if (!Number.isFinite(cantidadMov) || cantidadMov <= 0) {
if ((!Number.isFinite(cantidadMov) || cantidadMov <= 0) && tipo !== 'Ajuste') {
toastr.warning('Indica una cantidad válida para el movimiento');
return;
}

View File

@@ -1,294 +0,0 @@
PERMS['mensajes'] = 'Mensajes';
PERMS['mensajes:edit'] = '&gt; 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`
<h1>Mensaje <code id="${nameh1}"></code></h1>
<fieldset style="float: none; width: calc(100% - 40px);max-width: none;">
<legend>Valores</legend>
<div style="max-width: 400px;">
<label>
Asunto<br />
<input type="text" id="${field_asunto}" value="" /><br /><br />
</label>
<label>
Origen<br />
<input type="text" id="${field_autor}" value="" /><br /><br />
</label>
</div>
<label>
Contenido<br />
<textarea
id="${field_contenido}"
style="width: calc(100% - 15px); height: 400px;"
></textarea
><br /><br />
</label>
<label>
Adjuntos (Fotos o archivos)<br />
<input type="file" id="${field_files}" multiple /><br /><br />
<div id="${attachments_list}"></div>
</label>
<hr />
<button class="saveico" id="${btn_guardar}">
<img src="static/floppy_disk_green.png" />
<br>Guardar
</button>
<button class="delico" id="${btn_borrar}">
<img src="static/garbage.png" />
<br>Borrar
</button>
<button class="opicon" onclick="setUrlHash('mensajes')" style="float: right;"> <!-- Align to the right -->
<img src="static/exit.png" />
<br>Salir
</button>
<button class="opicon" onclick="window.print()" style="float: right;"> <!-- Align to the right -->
<img src="static/printer2.png" />
<br>Imprimir
</button>
</fieldset>
`;
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
? `<img src="${url}" height="80" style="margin-right:8px;">`
: `<a href="${url}" target="_blank">${name}</a>`;
const html = `
<div id="${idRow}" style="display:flex;align-items:center;margin:6px 0;border:1px solid #ddd;padding:6px;border-radius:6px;">
<div style="flex:1">${preview}<strong style="margin-left:8px">${name}</strong></div>
<div><button type="button" class="rojo" data-name="${name}">Borrar</button></div>
</div>`;
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`
<h1>Mensajes</h1>
<button id="${btn_new}">Nuevo mensaje</button>
<div id="cont"></div>
`;
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(''));
};
}
},
};

View File

@@ -718,111 +718,35 @@ PAGES.pagos = {
var btn_revert = safeuuid();
container.innerHTML = html`
<h1>Transacción <code id="${nameh1}"></code></h1>
${BuildQR('pagos,' + tid, 'Esta Transacción')}
<button id="${btn_volver}">← Volver a Pagos</button>
<button id="${btn_volver2}">← Volver a SuperCafé</button>
<fieldset>
<legend>Detalles de la Transacción</legend>
<h1 class="no_print">Transacción <code id="${nameh1}"></code></h1>
<button class="no_print" id="${btn_volver}">← Volver a Pagos</button>
<button class="no_print" id="${btn_volver2}">← Volver a SuperCafé</button>
<label>
Ticket/ID<br />
<input
type="text"
id="${field_ticket}"
readonly
style="background: #f0f0f0;"
/><br /><br />
</label>
<label>
Fecha y Hora<br />
<input
type="text"
id="${field_fecha}"
readonly
style="background: #f0f0f0;"
/><br /><br />
</label>
<label>
Tipo<br />
<input type="text" id="${field_tipo}" readonly style="background: #f0f0f0;" /><br /><br />
</label>
<label>
Monto<br />
<input
type="text"
id="${field_monto}"
readonly
style="background: #f0f0f0; font-size: 24px; font-weight: bold;"
/><br /><br />
</label>
<label>
Monedero (Persona)<br />
<input
type="text"
id="${field_persona}"
readonly
style="background: #f0f0f0;"
/><br /><br />
</label>
<h4>Ticket - ${tid}</h4>
<b>Fecha</b>: <span id="${field_fecha}"></span><br />
<b>Operación</b>: <span id="${field_tipo}"></span> realizado por <span id="${field_persona}"></span><br />
<div id="${div_persona_destino}" style="display: none;">
<label>
Monedero Destino<br />
<input
type="text"
id="${field_persona_destino}"
readonly
style="background: #f0f0f0;"
/><br /><br />
</label>
<b>Destino</b>: <span id="${field_persona_destino}"></span><br />
</div>
<label>
Método de Pago<br />
<input
type="text"
id="${field_metodo}"
readonly
style="background: #f0f0f0;"
/><br /><br />
</label>
<label>
Estado<br />
<input
type="text"
id="${field_estado}"
readonly
style="background: #f0f0f0;"
/><br /><br />
</label>
<b>Metodo</b>: <span id="${field_metodo}"></span><br />
<div id="${div_origen}" style="display: none;">
<label>
Origen<br />
<input
type="text"
id="${field_origen}"
readonly
style="background: #f0f0f0;"
/><br /><br />
</label>
<b>Origen</b>: <span id="${field_origen}"></span><br />
</div>
<hr />
<span id="${field_notas}"></span><br />
<hr>
<b>Estado</b>: <span id="${field_estado}"></span><br />
<h1 id="${field_monto}" style="color: green; text-align: center;">0.00€</h1>
<hr>
<label>
Notas<br />
<textarea id="${field_notas}" readonly rows="4" style="background: #f0f0f0;"></textarea
><br /><br />
</label>
</fieldset>
<fieldset style="margin-top: 20px;">
<fieldset style="margin-top: 20px;" class="no_print">
<legend>Acciones</legend>
<button onclick="window.print()" class="btn4" style="font-size: 16px; padding: 10px 20px; margin: 5px;">
🖨️ Imprimir Ticket
</button>
<button
id="${btn_edit}"
class="btn5"
@@ -859,33 +783,33 @@ PAGES.pagos = {
function load_data(data) {
console.log('Transaction data:', data);
document.getElementById(nameh1).innerText = tid;
document.getElementById(field_ticket).value = data.Ticket || tid;
//document.getElementById(field_ticket).innerText = data.Ticket || tid;
var fecha = data.Fecha || '';
if (fecha) {
var d = new Date(fecha);
document.getElementById(field_fecha).value = d.toLocaleString('es-ES');
document.getElementById(field_fecha).innerText = d.toLocaleString('es-ES');
}
document.getElementById(field_tipo).value = data.Tipo || '';
document.getElementById(field_monto).value = (data.Monto || 0).toFixed(2) + '€';
document.getElementById(field_tipo).innerText = data.Tipo || '';
document.getElementById(field_monto).innerText = (data.Monto || 0).toFixed(2) + '€';
var persona = SC_Personas[data.Persona] || {};
document.getElementById(field_persona).value = persona.Nombre || data.Persona || '';
document.getElementById(field_persona).innerText = persona.Nombre || data.Persona || '';
if (data.PersonaDestino) {
var personaDestino = SC_Personas[data.PersonaDestino] || {};
document.getElementById(field_persona_destino).value =
document.getElementById(field_persona_destino).innerText =
personaDestino.Nombre || data.PersonaDestino || '';
document.getElementById(div_persona_destino).style.display = 'block';
}
document.getElementById(field_metodo).value = data.Metodo || '';
document.getElementById(field_estado).value = data.Estado || '';
document.getElementById(field_notas).value = data.Notas || '';
document.getElementById(field_metodo).innerText = data.Metodo || '';
document.getElementById(field_estado).innerText = data.Estado || '';
document.getElementById(field_notas).innerText = data.Notas || '';
if (data.Origen) {
document.getElementById(field_origen).value =
document.getElementById(field_origen).innerText =
data.Origen + (data.OrigenID ? ' (' + data.OrigenID + ')' : '');
document.getElementById(div_origen).style.display = 'block';
}

View File

@@ -1,138 +0,0 @@
PERMS['resumen_diario'] = 'Resumen diario (Solo docentes!)';
PAGES.resumen_diario = {
icon: 'static/appico/calendar.png',
navcss: 'btn3',
AccessControl: true,
Title: 'Resumen Diario',
index: function () {
var data_Comedor = safeuuid();
var data_Tareas = safeuuid();
var data_Diario = safeuuid();
var data_Weather = safeuuid();
if (!checkRole('resumen_diario')) {
setUrlHash('index');
return;
}
container.innerHTML = html`
<h1>Resumen Diario ${CurrentISODate()}</h1>
<button onclick="print()">Imprimir</button>
<br /><span
class="btn7"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black;"
><b>Menú Comedor:</b> <br /><span id="${data_Comedor}">Cargando...</span></span
>
<br /><span
class="btn6"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black;"
><b>Tareas:</b> <br />
<pre style="overflow-wrap: break-word;white-space:pre-wrap;" id="${data_Tareas}">
Cargando...</pre
>
</span>
<br /><span
class="btn5"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black;"
><b>Diario:</b> <br />
<pre style="overflow-wrap: break-word;white-space:pre-wrap;" id="${data_Diario}">
Cargando...</pre
>
</span>
<br /><span
class="btn4"
style="display: inline-block; margin: 5px; padding: 5px; border-radius: 5px; border: 2px solid black;"
><b>Clima:</b> <br /><img
loading="lazy"
style="padding: 15px; background-color: white; height: 75px;"
id="${data_Weather}"
/></span>
`;
//#region Cargar Clima
// Get location from DB settings.weather_location; if missing ask user and save it
// url format: https://wttr.in/<loc>?F0m
DB.get('settings', 'weather_location').then((loc) => {
if (!loc) {
loc = prompt('Introduce tu ubicación para el clima (ciudad, país):', 'Madrid, Spain');
if (loc) {
DB.put('settings', 'weather_location', loc);
}
}
if (loc) {
document.getElementById(data_Weather).src =
'https://wttr.in/' + encodeURIComponent(loc) + '_IF0m_background=FFFFFF.png';
} else {
document.getElementById(data_Weather).src = 'https://wttr.in/_IF0m_background=FFFFFF.png';
}
});
//#endregion Cargar Clima
//#region Cargar Comedor
DB.get('comedor', CurrentISODate()).then((data) => {
function add_row(data) {
// Fix newlines
data.Platos = data.Platos || 'No hay platos registrados para hoy.';
// Display platos
document.getElementById(data_Comedor).innerHTML = data.Platos.replace(/\n/g, '<br>');
}
if (typeof data == 'string') {
TS_decrypt(
data,
SECRET,
(data, wasEncrypted) => {
add_row(data || {});
},
'comedor',
CurrentISODate()
);
} else {
add_row(data || {});
}
});
//#endregion Cargar Comedor
//#region Cargar Tareas
DB.get('notas', 'tareas').then((data) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || 'No hay tareas.';
// Display platos
document.getElementById(data_Tareas).innerHTML = data.Contenido.replace(/\n/g, '<br>');
}
if (typeof data == 'string') {
TS_decrypt(
data,
SECRET,
(data, wasEncrypted) => {
add_row(data || {});
},
'notas',
'tareas'
);
} else {
add_row(data || {});
}
});
//#endregion Cargar Tareas
//#region Cargar Diario
DB.get('aulas_informes', 'diario-' + CurrentISODate()).then((data) => {
function add_row(data) {
// Fix newlines
data.Contenido = data.Contenido || 'No hay un diario.';
// Display platos
document.getElementById(data_Diario).innerHTML = data.Contenido.replace(/\n/g, '<br>');
}
if (typeof data == 'string') {
TS_decrypt(
data,
SECRET,
(data, wasEncrypted) => {
add_row(data || {});
},
'aulas_informes',
'diario-' + CurrentISODate()
);
} else {
add_row(data || {});
}
});
//#endregion Cargar Diario
},
};