Primer commit de los tres flujos: Raspipeso, Servidor y Gestor de semaforos

This commit is contained in:
2026-01-28 22:11:54 +01:00
parent 62d340d7e7
commit 57983096b1
72 changed files with 18931 additions and 2 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+540
View File
@@ -0,0 +1,540 @@
/* =============================== BASE GLOBAL =============================== */
body,
html {
margin: 0;
padding: 0;
font-family: "Segoe UI", sans-serif;
background-color: #fff;
color: #333;
}
.container {
max-width: 400px;
margin: auto;
padding: 20px;
}
#pantalla-tabla-general .container {
padding-left: 8px;
padding-right: 8px;
}
hr {
margin: 10px 0 20px;
border: 1px solid #ccc;
}
/* =============================== ENCABEZADO =============================== */
.header,
.encabezado {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
.logo {
width: 40px;
height: 40px;
}
.titulo,
h3 {
text-align: center;
margin-top: 10px;
font-size: 26px;
font-weight: bold;
}
/* =============================== PANTALLAS SPA =============================== */
.pantalla {
display: none;
}
.pantalla.visible {
display: block;
}
/* =============================== BOTONES =============================== */
.btn {
padding: 16px;
font-size: 16px;
font-weight: bold;
border: none;
border-radius: 10px;
color: white;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.btn.big {
width: 100%;
margin-top: 10px;
}
/* Colores */
.btn.yellow,
.btn.amarillo {
background: #f1c40f;
color: #444;
}
.btn.orange,
.btn.naranja {
background: #e67e22;
}
.btn.green,
.btn.verde {
background: #28a745;
}
.btn.teal,
.btn.verde-oscuro {
background: #049a81;
}
.btn.red,
.btn.rojo {
background: firebrick;
}
.btn.blue,
.btn.azul {
background: #3498db;
}
.btn.darkblue,
.btn.azuloscuro {
background: #0567a9;
}
.btn.aqua,
.btn.celeste {
background: #04fbc7;
color: #333;
}
.btn.purple,
.btn.morado {
background: #9b59b6;
}
.btn.gray,
.btn.gris {
background: #707b7c;
}
.btn.gold,
.btn.mostaza {
background: #DAB957;
color: #444;
}
/* =============================== BOTONERAS / GRID =============================== */
.grid-vertical {
display: grid;
grid-template-columns: 1fr;
gap: 15px;
}
.grid-2col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin: 20px 0;
}
.botonera-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
max-width: 400px;
margin: 20px auto;
}
.botonera-vertical {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 400px;
margin: auto;
}
/* =============================== COMPONENTES VISUALES =============================== */
.circle {
width: 100px;
height: 100px;
margin: 20px auto;
border-radius: 50%;
background: #239b56;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.imagen {
display: flex;
justify-content: center;
margin: 12px 0;
}
.imagen img {
max-width: 85vw;
max-height: 43vh;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
.matricula-nav {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 12px;
}
.placa {
background: white;
padding: 8px 16px;
border-radius: 10px;
font-size: 24px;
font-weight: bold;
color: #333;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
min-width: 100px;
text-align: center;
}
.fecha {
text-align: center;
font-size: 14px;
font-weight: bold;
color: #555;
margin-top: 8px;
}
.camara-row {
display: flex;
justify-content: space-between;
font-weight: bold;
margin: 10px 0;
}
.desarrollo {
text-align: center;
margin: 20px 0;
}
/* =============================== TABLA EDITABLE =============================== */
table {
width: 100%;
border-collapse: collapse;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
border-radius: 8px;
overflow: hidden;
table-layout: fixed;
word-wrap: break-word;
}
th,
td {
padding: 12px 16px;
border-bottom: 1px solid #e0e0e0;
text-align: left;
}
th {
background-color: #007acc;
color: white;
font-weight: bold;
width: 40%;
}
td {
background-color: #fafafa;
}
td[contenteditable="true"] {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
}
.button-container {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 25px;
}
.btn-actualizar,
.btn-cancelar {
padding: 12px 24px;
font-weight: bold;
font-size: 16px;
border-radius: 8px;
border: none;
cursor: pointer;
transition: background-color 0.2s ease;
}
.btn-actualizar {
background-color: #28a745;
color: white;
}
.btn-actualizar:hover {
background-color: #218838;
}
.btn-cancelar {
background-color: #dc3545;
color: white;
}
.btn-cancelar:hover {
background-color: #c82333;
}
/*----------------------------------------------- MODAL -------------------------------------*/
.modal {
display: none;
position: fixed;
z-index: 999;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.6);
}
.modal-content {
background-color: #fff;
margin: 15% auto;
padding: 20px;
border-radius: 10px;
max-width: 300px;
text-align: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.modal-buttons {
display: flex;
justify-content: space-around;
margin-top: 20px;
}
/*----------------------------------------------- PANTALLA PRINCIPAL DEL TOTEM -------------------------------------*/
.peso-section {
display: flex;
justify-content: center;
align-items: baseline;
font-weight: bold;
margin: 5px 0 5px;
font-size: 18px;
}
.peso-display {
display: flex;
justify-content: center;
align-items: baseline;
gap: 8px;
font-size: 44px;
font-weight: bold;
margin: 20px 0;
text-align: center;
}
.peso-label {
margin-right: 10px;
font-size: 26px;
}
.peso-valor {
font-size: 90px;
color: #2c3e50;
font-weight: bold;
}
.peso-unidad {
margin-left: 8px;
font-size: 18px;
}
.status-line {
display: flex;
justify-content: space-between;
font-size: 16px;
margin: 8px 0;
}
.status-line .label {
font-weight: bold;
}
.status-line.small {
font-size: 14px;
color: #555;
}
.led-semaforo {
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #ccc;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
margin-left: 10px;
display: inline-block;
}
.semaforo-display {
display: flex;
justify-content: center;
align-items: center;
margin: 20px 0;
}
.led-semaforo.led {
box-shadow: 0 0 12px currentColor, 0 0 3px currentColor inset;
}
.etiqueta {
min-width: 90px;
}
.labelMat{
font-size: 24px;
color: red;
font-weight: bold;
}
.labelPE {
font-size: 18px;
color: blue;
font-weight: bold;
}
.labelValue {
font-size: 18px;
color: blue;
}
#pantalla-barrera .container {
text-align: center;
}
/*-------------------------------------------------- PANTALLA DE LA BARRERA ---------------------------------------------------------------------*/
.barrera-icono {
margin: 20px auto;
height: 100px;
}
.barrera-icono img {
max-height: 100px;
}
/*-------------------------------------------------- TIPOS DE CONFIGURACION ---------------------------------------------------------------------*/
#tipo-configuracion .parametro-row {
display: flex;
gap: 10px;
margin-bottom: 8px;
}
#tipo-configuracion input {
flex: 1;
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
}
.parametro-row button {
font-size: 20px;
line-height: 20px;
padding: 0;
text-align: center;
}
.botonera-tipo {
margin-top: 15px;
display: flex;
justify-content: center;
gap: 10px;
}
/* Estilo base para la fila */
.parametro-row {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 4px;
width: 100%;
}
.parametro-row input.parametro {
flex: 1 1 50%;
min-width: 100px;
max-width: 300px;
padding: 4px;
font-size: 14px;
}
.parametro-row input.valor {
flex: 0 0 30%;
min-width: 60px;
max-width: 150px;
padding: 4px;
font-size: 14px;
text-align: center;
}
.parametro-row input[type="checkbox"].valor {
flex: 0 0 30px;
transform: scale(1.8);
margin: 0;
}
.parametro-row button {
flex: 0 0 30px;
width: 30px;
height: 30px;
font-size: 14px;
padding: 0;
margin: 0;
}
@media (max-width: 600px) {
.parametro-row input.parametro {
flex: 1 1 40%;
font-size: 12px;
}
.parametro-row input.valor {
flex: 1 1 30%;
font-size: 12px;
}
.parametro-row input[type="checkbox"].valor {
flex: 0 0 30px;
/*zoom: 1.5; */
transform: scale(1.5) ;
margin: 4px;
}
.parametro-row button {
flex: 0 0 25px;
width: 25px;
height: 25px;
font-size: 12px;
}
}
/*-------------------------------------------------- IMAGEN INICIAL DEL SNAPSHOT ---------------------------------------------------------------------*/
/*
#foto {
width: 65vw;
height: 30vh;
background-color: #eee;
border-radius: 10px;
object-fit: contain;
display: block;
margin: auto;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
*/
@import url("../uibuilder/uib-brand.min.css");
+317
View File
@@ -0,0 +1,317 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interfaz Totem</title>
<script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
<script src="../uibuilder/uibuilder.iife.min.js"></script>
<link rel="stylesheet" href="./index.css">
<script src="./index.js" defer></script>
</head>
<body>
<!-- ********************************************* PANTALLA INICIAL DEL TOTEM ******************************************************* -->
<div id="pantalla-principal" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" alt="Logo" class="logo" />
<h1 class="titulo">Sistema de Pesaje</h1>
</div>
<hr />
<div class="peso-display">
<span id="peso" class="peso-valor">0</span>
<span class="peso-unidad">Kg</span>
</div>
<div class="semaforo-display">
<span id="semaforo-color" class="led-semaforo led"></span>
</div>
<div class="status-line"><span class="label">Matrícula:</span> <span class="labelMat" id="matricula">----</span></div>
<div class="status-line"><span class="label">Peso Estable:</span> <span class="labelPE" id="peso-estable">----</span></div>
<div class="status-line"><span class="label">Vial:</span> <span class="labelValue" id="vial">--</span></div>
<div class="status-line"><span class="label">ID:</span> <span class="labelValue" id="totem-id">--</span></div>
<div class="status-line small"><span class="label">IP LAN:</span> <span class="labelValue" id="iplan">----</span></div>
<div class="status-line small"><span class="label">IP Supervisor:</span> <span class="labelValue" id="ipsupervisor">----</span></div>
<div class="botonera-grid">
<button class="btn verde big" data-action="menu-principal">CONFIG</button>
<button class="btn celeste big" data-action="menu-sistema">SISTEMA</button>
</div>
</div>
</div>
<!-- ************************************** MENU PRINCIPAL DE CONFIGURACION ******************************************* -->
<div id="pantalla-menu-principal" class="pantalla visible">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">CONFIGURACIÓN GESTOR SEMAFOROS</h3>
<hr>
<div class="grid-vertical">
<button class="btn blue" data-action="editar">EDITAR CONFIGURACION</button>
<button class="btn green" data-action="aplicar">APLICAR CONFIGURACION</button>
<!--
<button class="btn orange" data-action="pruebas">PRUEBAS DEL SISTEMA</button>
<button class="btn purple" data-action="otras">OTRAS ACCIONES</button>
-->
<button class="btn teal" data-action="salir">SALIR</button>
</div>
</div>
</div>
<!-- ******************************************* MENU EDITAR CONFIGURACION ****************************************** -->
<div id="pantalla-editar" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" alt="Logo" class="logo" />
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">EDITAR CONFIGURACION</h3>
<hr />
<div class="botonera-grid">
<button data-action="general" class="btn amarillo">GENERAL</button>
<button data-action="conexion" class="btn celeste">CONEXIONES</button>
<!--
<button data-action="sensores" class="btn morado">SENSORES</button>
<button data-action="mensajes" class="btn gris">MENSAJES</button>
-->
<button data-action="tiempos" class="btn azul">TIEMPOS</button>
</div>
<div class="botonera-vertical">
<button data-action="salvar" class="btn verde">SALVAR MODIFICACIONES</button>
<button data-action="ayuda" class="btn mostaza">AYUDA</button>
<button data-action="cancelar" class="btn verde-oscuro">CANCELAR Y SALIR</button>
</div>
</div>
</div>
<!-- ****************************************MENU PRUEBAS DEL SISTEMA ****************************************** -->
<div id="pantalla-pruebas" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">PRUEBAS DEL TOTEM</h3>
<hr>
<div class="grid-vertical">
<button class="btn yellow" data-action="semaforo">ENCENDER SEMAFORO</button>
<button class="btn aqua" data-action="barrera">ACTIVAR BARRERA</button>
<button class="btn orange" data-action="snapshot">PEDIR SNAPSHOT</button>
<button class="btn green" data-action="escuchar">ESCUCHAR CAMARA</button>
<button class="btn teal" data-action="regresar">SALIR</button>
</div>
</div>
</div>
<!-- -------------------------------------- MENU OTRAS ACCIONES --------------------------------------------------- -->
<div id="pantalla-otras" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">OTRAS ACCIONES</h3>
<hr>
<div class="desarrollo">
<h3 style="color:#2e86c1;">EN DESARROLLO</h3>
</div>
<div class="grid-vertical">
<button class="btn blue" data-action="enviar">ENVIAR SNAPSHOT</button>
<button class="btn aqua" data-action="reboot">REBOOT RPi</button>
<button class="btn red" data-action="apagar">APAGAR RPi</button>
<button class="btn teal" data-action="regresar">SALIR</button>
</div>
</div>
</div>
<!-- -------------------------------------- PRUEBA DEL SEMAFORO --------------------------------------------------- -->
<div id="pantalla-semaforo" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Trypton Software</h2>
</div>
<h3 class="titulo">PRUEBA DEL SEMAFORO</h3>
<hr>
<div class="circle" id="estado"></div>
<div class="grid-2col">
<button class="btn red" data-action="semaforo_on">ON</button>
<button class="btn green" data-action="semaforo_off">OFF</button>
</div>
<button class="btn teal big" data-action="salir-semaforo">SALIR</button>
</div>
</div>
<!-- -------------------------------------- PANTALLA DE SNAPSHOT DE LA CAMARA -------------------------------------------------- -->
<div id="pantalla-snapshot" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Snapshot Recibido</h2>
</div>
<div class="camara-row">
<span>Camara: <strong id="camara">---</strong></span>
</div>
<div class="imagen"><img id="foto" src="" alt="Foto"></div>
<div class="matricula-nav">
<div class="placa" id="placa">----</div>
</div>
<div class="fecha" id="fecha">----</div>
<button class="btn teal big" data-action="salir-snapshot">SALIR</button>
</div>
</div>
<!-- -------------------------------------- PANTALLA DE ACTIVACION DE LA BARRERA -------------------------------------------------- -->
<div id="pantalla-barrera" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>ACTIVACION DE BARRERA</h2>
</div>
<!-- ICONO BARRERA -->
<div class="barrera-icono" id="barrera-icono">
<img id="icono-barrera" src="/barrera_cerrada.png" alt="Barrera" />
</div>
<!-- BOTONES -->
<div class="botonera-vertical">
<button class="btn verde big" data-action="activar-barrera">ACTIVAR BARRERA</button>
<button class="btn teal big" data-action="salir-barrera">SALIR</button>
</div>
</div>
</div>
<!-- -------------------------------------- PANTALLA PARA ESCUCHAR LA CAMARA -------------------------------------------------- -->
<div id="pantalla-escuchar" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Snapshot Recibido</h2>
</div>
<div class="camara-row">
<span>Camara: <strong id="camara_escuchar">---</strong></span>
<span id="contador">0 / 0</span>
</div>
<div class="imagen"><img id="foto_escuchar" src="" alt="Foto"></div>
<div class="matricula-nav">
<button class="btn green" data-action="left">&lt;</button>
<div class="placa" id="placa_escuchar">----</div>
<button class="btn blue" data-action="right">&gt;</button>
</div>
<div class="fecha" id="fecha_escuchar">----</div>
<button class="btn teal big" data-action="salir-escuchar">SALIR</button>
</div>
</div>
<!-- ------------------------------- PANTALLA DE TABLA DINAMICA PARA MOSTRAR PARAMETROS DE CONFIGURACION------------------------------------------------->
<div id="pantalla-tabla-general" class="pantalla">
<div class="container">
<h2 style="text-align:center;">Parámetros del Sistema</h2>
<div class="button-container">
<table id="table2">
<thead>
<tr>
<th style="text-align:center;">Parámetro</th>
<th style="text-align:center;">Valor</th>
</tr>
</thead>
<tbody id="config-table-body">
<!-- Se rellena dinámicamente -->
</tbody>
</table>
</div>
<div class="button-container">
<button class="custom-btn btn-actualizar" onclick="sendRow()">ACTUALIZAR</button>
<button class="custom-btn btn-cancelar" onclick="sendCancel()">CANCELAR</button>
</div>
</div>
</div>
<!-- ------------------------------------- PANTALLA TIPOS DE CONFIGURACION ---------------------------------------------------- -->
<div id="pantalla-tipos" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>Tipos de Configuración</h2>
</div>
<hr>
<div class="botonera-grid">
<button class="btn blue" onclick="seleccionarTipo(1)">TIPO 1</button>
<button class="btn orange" onclick="seleccionarTipo(2)">TIPO 2</button>
<button class="btn yellow" onclick="seleccionarTipo(3)">TIPO 3</button>
<button class="btn celeste" onclick="seleccionarTipo(4)">TIPO 4</button>
<button class="btn purple" onclick="seleccionarTipo(5)">TIPO 5</button>
<button class="btn gris" onclick="seleccionarTipo(6)">TIPO 6</button>
</div>
<div class="botonera-vertical">
<button class="btn green" id="btn-volver-tipos">REGRESAR</button>
</div>
<div id="tipo-configuracion" style="margin-top:20px; display:none;">
<h3 id="titulo-tipo"></h3>
<div id="parametros"></div>
<div class="botonera-tipo">
<button id="btn-add-param" class="btn azul">+</button>
<button class="btn verde" id="btn-guardar-tipo">Guardar</button>
<button class="btn rojo" id="btn-cancelar-tipo">Cancelar</button>
</div>
</div>
</div>
</div>
<!-- ------------------------------------- PANTALLA DE AYUDA ---------------------------------------------------- -->
<div id="pantalla-ayuda" class="pantalla">
<div class="container">
<div class="header">
<img src="/logo-trypton2.png" class="logo" alt="Logo">
<h2>AYUDA</h2>
</div>
<hr>
<iframe src="./ayuda.pdf" style="width:100%; height:600px; border:none;"></iframe>
<div style="margin-top: 20px; text-align: center;">
<button class="btn rojo" data-action="salir-ayuda">SALIR</button>
</div>
</div>
</div>
<!-- ------------------------------------- MODAL PARA POPUP ---------------------------------------------------- -->
<div id="confirm-modal" class="modal">
<div class="modal-content">
<p id="modal-text">¿Deseas guardar los cambios?</p>
<div class="modal-buttons">
<button id="btn-modal-si" class="btn verde" onclick="confirmGuardar()"></button>
<button id="btn-modal-no" class="btn rojo" onclick="cancelarGuardar()">No</button>
</div>
</div>
</div>
</body>
</html>
+494
View File
@@ -0,0 +1,494 @@
/* global uibuilder */
uibuilder.start();
// Navegación entre pantallas
function mostrarPantalla(id) {
document.querySelectorAll('.pantalla').forEach(div =>
div.classList.toggle('visible', div.id === id)
);
}
let currentTopic = "";
let result = [];
//****************************************************RECEPCION DE MENSAJES DESDE NODE_RED ***************************************************************** */
uibuilder.onChange('msg', msg => {
if (msg.payload && msg.payload.pantalla) {
mostrarPantalla('pantalla-' + msg.payload.pantalla);
}
if (msg.semaforo) {
document.getElementById('estado').style.backgroundColor = msg.semaforo;
}
if (msg.payload && msg.payload.pantalla === 'snapshot') {
// Limpiar datos visibles
document.getElementById('camara').textContent = '---';
document.getElementById('placa').textContent = '----';
document.getElementById('fecha').textContent = '----';
// Mostrar imagen vacía
document.getElementById('foto').src = "";
}
if (msg.payload && msg.payload.pantalla === 'escuchar' && msg.buttons != true) {
// Limpiar datos visibles
document.getElementById('camara_escuchar').textContent = '---';
document.getElementById('placa_escuchar').textContent = '----';
document.getElementById('fecha_escuchar').textContent = '----';
document.getElementById('contador').textContent = `0 / 0`;
// Mostrar imagen vacía
document.getElementById('foto_escuchar').src = "";
}
if (msg.hasOwnProperty("picture")) {
//uibuilder.send({ payload: msg.payload.Picture.Plate.PlateNumber, topic: msg.payload.Picture.SnapInfo.DeviceID });
if (msg.snapshot_cam) {
document.getElementById('foto').src = msg.picture;
document.getElementById('placa').textContent = msg.plate || '----';
document.getElementById('fecha').textContent = msg.plate_time || '';
document.getElementById('camara').textContent = msg.camera_id || '---';
}
else if (msg.escuchar_cam){
document.getElementById('foto_escuchar').src = msg.picture;
document.getElementById('placa_escuchar').textContent = msg.plate || '----';
document.getElementById('fecha_escuchar').textContent = msg.plate_time || '';
document.getElementById('camara_escuchar').textContent = msg.camera_id || '---';
const total = msg.snapshot_indexmax || 0;
const actual = (msg.snapshot_readindex || 0) + 1;
document.getElementById('contador').textContent = `${actual} / ${total}`;
}
}
else if (msg.payload?.pantalla === 'tipos') {
mostrarPantalla('pantalla-tipos');
if (msg.payload.tiposDatos) {
tiposDatos = msg.payload.tiposDatos;
}
}
if (msg.barrera) {
mostrarBarreraAbierta()
}
else{
mostrarBarreraCerrada()
}
// tabla dinamica
if (msg.topic == "general" || msg.topic == "conexion" || msg.topic == "camara" || msg.topic == "sensores" || msg.topic == "mensajes" || msg.topic == "tiempos"){
if (!msg.tabla || !Array.isArray(msg.tabla)) return;
const tbody = document.getElementById('config-table-body');
tbody.innerHTML = '';
currentTopic = msg.topic || 'general';
msg.tabla.forEach(item => { //generamos dinamixamente la tabla que vera el usuario en el dispositivo (PC o smartphone).
// notese que la celda 2 de cada fila es del tipo editable.
const row = document.createElement('tr');
const cell1 = document.createElement('td');
cell1.textContent = item.parametro;
const cell2 = document.createElement('td');
cell2.contentEditable = true;
//cell2.textContent = item.valor;
cell2.textContent = formatForCell(item.valor);
row.appendChild(cell1);
row.appendChild(cell2);
tbody.appendChild(row); // enviamos la tabla al elemento html (config-table-body)
});
}
// configuracion del pop up de notificaciones
if (msg.modalText) {
document.getElementById('modal-text').textContent = msg.modalText;
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
if (msg.modalType === "notificacion") {
btnSi.textContent = 'OK';
btnNo.style.display = 'none';
window.modalCallback = null;
} else {
btnSi.textContent = 'Sí';
btnNo.style.display = 'inline-block';
window.modalCallback = msg.modalCallback || null;
}
document.getElementById('confirm-modal').style.display = 'block';
}
// ======================================== Datos para la Pantalla Principal ========================================================================
if (msg.hasOwnProperty("peso")) document.getElementById('peso').textContent = msg.peso || 0;
//if (msg.semaforo_color) document.getElementById('semaforo-color').style.backgroundColor = msg.semaforo_color;
const led = document.getElementById('semaforo-color');
if (msg.semaforo_color) {
led.style.backgroundColor = msg.semaforo_color;
led.classList.add('led');
} else {
led.style.backgroundColor = '#ccc';
led.classList.remove('led');
}
if (msg.hasOwnProperty("matricula")) document.getElementById('matricula').textContent = msg.matricula;
if (msg.hasOwnProperty("peso_estable_flag")) document.getElementById('peso-estable').textContent = msg.peso_estable_flag ? 'SI' : 'NO';
if (msg.hasOwnProperty("vial")) document.getElementById('vial').textContent = msg.vial;
if (msg.hasOwnProperty("totem_id")) document.getElementById('totem-id').textContent = msg.totem_id;
if (msg.hasOwnProperty("iplan")) document.getElementById('iplan').textContent = msg.iplan;
if (msg.hasOwnProperty("ipsupervisor")) document.getElementById('ipsupervisor').textContent = msg.ipsupervisor;
});
//****************************************************************************************************************************************************** */
// Detectar clics en botones
document.addEventListener('click', ev => {
if (ev.target.matches('button[data-action]')) {
const action = ev.target.getAttribute('data-action');
console.log('⏺ Acción botón:', action);
uibuilder.send({ payload: { seccion: action } });
}
});
// Mostrar pantalla tipos
document.addEventListener('click', ev => {
if (ev.target.matches('button[data-action="tipos"]')) {
mostrarPantalla('pantalla-tipos');
}
});
document.getElementById('btn-add-param').addEventListener('click', agregarParametro);
document.getElementById('btn-guardar-tipo').addEventListener('click', () => {
window.modalCallback = guardarTipo;
document.getElementById('modal-text').textContent = `¿Deseas guardar los cambios de Tipo ${tipoActual}?`;
document.getElementById('confirm-modal').style.display = 'block';
});
document.getElementById('btn-cancelar-tipo').addEventListener('click', () => {
uibuilder.send({ payload: { accion: "cancelar_tipo", tipo: tipoActual } });
volverATipos();
});
document.getElementById('btn-volver-tipos').addEventListener('click', () => {
volverATipos(); // esto limpia la tabla con los parametros del Tipo en el que estemos
uibuilder.send({ payload: { seccion: "volver_tipos" } });
});
let tipoActual = null;
let tiposDatos = {}; // aquí se guarda la configuración temporal
//******************************************* FUNCIONES ******************************************************** */
// Funcion para asegurar que se vea el array en la pantalla de configuracion. Si no se hace esto se ve los numeros sin los []
// y luego al pasarlo al archivo no se puede identificar si es un array
function formatForCell(v) {
if (v === null || v === undefined) return '';
if (typeof v === 'object') return JSON.stringify(v); // arrays y objetos
return String(v);
}
// Acciones a partir de botones en html
function sendRow() {// Aqui llega cuando se hace click en el boton Actualizar de las tablas dinamicas
// recoge el contenido de la tabla en ese instante y saca Notificacion de guardar con opcion Si o No
const rows = document.querySelectorAll('#config-table-body tr');
result = []; // inicializamos result para poner exactamente la tabla actual
/*
rows.forEach(row => {
const parametro = row.cells[0].textContent.trim();
let valor = row.cells[1].textContent.trim();
// normaliza a booleano si corresponde
if (valor === "true") valor = true;
if (valor === "false") valor = false;
result.push({ parametro, valor });
});
*/
//Modificacion de la lectura de la tabla para asegurar que los numeros son mantenidos como numeros y no como texto
// al igual que los valores booleanos.
rows.forEach(row => {
const parametro = row.cells[0].textContent.trim();
let valor = row.cells[1].textContent.trim();
// 1) Boolean
if (/^(true|false)$/i.test(valor)) {
valor = valor.toLowerCase() === "true";
// 2) JSON Array u Object: "[3,1,2]" o {"a":1}
} else if (
(valor.startsWith("[") && valor.endsWith("]")) ||
(valor.startsWith("{") && valor.endsWith("}"))
) {
try {
valor = JSON.parse(valor);
// Opcional: si es array, intenta convertir strings numéricas a números
if (Array.isArray(valor)) {
valor = valor.map(v => {
if (typeof v === "string" && /^-?\d+$/.test(v)) return parseInt(v, 10);
if (typeof v === "string" && /^-?\d+\.\d+$/.test(v)) return parseFloat(v);
return v;
});
}
} catch (e) {
// Si no es JSON válido, se queda como string
}
// 3) Int
} else if (/^-?\d+$/.test(valor)) {
valor = parseInt(valor, 10);
// // 4) Float
} else if (/^-?\d+\.\d+$/.test(valor)) {
valor = parseFloat(valor);
}
result.push({ parametro, valor });
});
// Mostramos directamente la confirmación y definimos la callback. "topic" contiene el nombre de la tabla donde se guardan los valores que vienen en "result"
mostrarConfirmacion("¿Deseas guardar los cambios?", () => {
uibuilder.send({
payload: { table: result, seccion: "respuesta" },
topic: currentTopic,
origen: "actualizar"
});
});
}
function sendCancel() { // boton Cancelar de las tablas dinamicas
uibuilder.send({ payload: {seccion:'cancel-table'}, topic: currentTopic });
}
function confirmGuardar() { // Respuesta Afirmativa del Modal de tabla dinamica
document.getElementById('confirm-modal').style.display = 'none';
if (typeof window.modalCallback === 'function') {
const cb = window.modalCallback;
window.modalCallback = null; // limpia para la siguiente vez
cb(); // ejecuta
} else if (typeof window.modalCallback === 'string') {
uibuilder.send({ payload: { table: result }, modalCallback: window.modalCallback, origen: "modal" });
}
}
/*
function confirmGuardar() { // Respuesta Afirmativa del Modal de tabla dinamica
uibuilder.send({ payload: { table: result }, topic: currentTopic, origen: "modal-tabla" });
document.getElementById('confirm-modal').style.display = 'none';
}
*/
function cancelarGuardar() { // Respuesta Negativa del Modal de tabla dinamica
result = [];
document.getElementById('confirm-modal').style.display = 'none';
}
// Funciones para icono barrera
function mostrarBarreraAbierta() {
const icono = document.getElementById('icono-barrera');
icono.src = "/barrera_abierta.png";
/*
setTimeout(() => {
mostrarBarreraCerrada();
}, 1000); // 1 segundo
*/
}
function mostrarBarreraCerrada() {
const icono = document.getElementById('icono-barrera');
icono.src = "/barrera_cerrada.png";
}
//************************* FUNCIONES DE TIPOS DE CONFIGURACION *****************************/
function seleccionarTipo(n) {
tipoActual = n;
document.getElementById('titulo-tipo').textContent = `TIPO ${n}`;
document.getElementById('tipo-configuracion').style.display = 'block';
const contenedor = document.getElementById('parametros');
contenedor.innerHTML = '';
const datos = tiposDatos[n] || []; // ya es un array
datos.forEach(d => {
const row = crearFilaParametro(d.parametro, d.valor);
contenedor.appendChild(row);
});
}
function agregarParametro() {
const contenedor = document.getElementById('parametros');
if (contenedor.children.length >= 8) return;
const row = crearFilaParametro('', '');
contenedor.appendChild(row);
}
function crearFilaParametro(parametro, valor) {
const div = document.createElement('div');
div.className = 'parametro-row';
// Input para el nombre del parámetro
const inputParam = document.createElement('input');
inputParam.placeholder = 'Parámetro';
inputParam.value = parametro;
// Input para el valor: checkbox o texto
let inputValor;
if (valor === true || valor === false || valor === "true" || valor === "false") {
inputValor = document.createElement('input');
inputValor.type = 'checkbox';
inputValor.checked = (valor === true || valor === "true");
} else {
inputValor = document.createElement('input');
inputValor.placeholder = 'Valor';
inputValor.value = valor;
}
inputParam.classList.add('parametro');
inputValor.classList.add('valor');
// Botón para eliminar la fila
const btnDelete = document.createElement('button');
btnDelete.textContent = '-';
btnDelete.className = 'btn rojo';
btnDelete.style.flex = '0 0 40px';
btnDelete.style.width = '40px';
btnDelete.style.height = '40px';
btnDelete.style.marginLeft = '4px';
btnDelete.style.zIndex = '1';
btnDelete.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
window.modalCallback = () => div.remove();
document.getElementById('modal-text').textContent = '¿Deseas eliminar este parámetro?';
document.getElementById('confirm-modal').style.display = 'block';
return false;
});
// Añadir los elementos a la fila
div.appendChild(inputParam);
div.appendChild(inputValor);
div.appendChild(btnDelete);
return div;
}
function crearFilaParametro_viejo(parametro, valor) { // Crea un nuevo elemento en la tabla del Tipo que estemos trabajando con dos campos y un boton de borrado
const div = document.createElement('div');
div.className = 'parametro-row';
const inputParam = document.createElement('input');
inputParam.placeholder = 'Parámetro';
inputParam.value = parametro;
const inputValor = document.createElement('input');
inputValor.placeholder = 'Valor';
inputValor.value = valor;
const btnDelete = document.createElement('button');
btnDelete.textContent = '-';
btnDelete.className = 'btn rojo';
btnDelete.style.width = '40px';
btnDelete.style.height = '40px';
btnDelete.onclick = () => { // gestion de la alerta para el borrado, haciendo uso del modal que tenemos en html
window.modalCallback = () => div.remove();
document.getElementById('modal-text').textContent = '¿Deseas eliminar este parámetro?';
document.getElementById('confirm-modal').style.display = 'block';
};
div.appendChild(inputParam);
div.appendChild(inputValor);
div.appendChild(btnDelete);
return div;
}
function guardarTipo() {
const contenedor = document.getElementById('parametros');
const filas = contenedor.querySelectorAll('.parametro-row');
const datos = [];
filas.forEach(fila => {
const [param, val] = fila.querySelectorAll('input');
let valor;
if (val.type === 'checkbox') {
valor = val.checked;
} else {
valor = val.value.trim();
}
if (param.value.trim()) {
datos.push({ parametro: param.value.trim(), valor });
}
});
/*
filas.forEach(fila => {
const [param, val] = fila.querySelectorAll('input');
if (param.value.trim()) {
datos.push({ parametro: param.value.trim(), valor: val.value.trim() });
}
});
*/
tiposDatos[tipoActual] = datos;
uibuilder.send({ payload: { tipo: tipoActual, datos }, topic: "tipoconfig" }); // el topic nos sirve para seleccionar en el proximo nodo la funcion de guardar los datos
volverATipos(); // 🔷 salimos de la pantalla del tipo actual
}
function volverATipos() {
document.getElementById('tipo-configuracion').style.display = 'none';
tipoActual = null;
}
//==========================================FUNCIONES PARA MANEJAR LAS NOTIFICACIONES ======================================================
function mostrarConfirmacion(texto, callback) {
document.getElementById('modal-text').textContent = texto;
// Restablece estado normal del modal
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
btnSi.textContent = 'Sí';
btnNo.style.display = 'inline-block';
window.modalCallback = callback;
document.getElementById('confirm-modal').style.display = 'block';
}
function mostrarNotificacion(texto) {
document.getElementById('modal-text').textContent = texto;
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
btnSi.textContent = 'OK';
btnNo.style.display = 'none';
window.modalCallback = null;
document.getElementById('confirm-modal').style.display = 'block';
}
/*
Cuando necesitemos un modal clásico (Sí/No), por ejemplo para confirmar actualización:
mostrarConfirmacion("¿Deseas guardar los cambios?", () => {
uibuilder.send({ payload: { table: result, seccion:"respuesta" }, topic: currentTopic, origen: "actualizar" });
});
Cuando necesitemos simplemente notificar un error:
mostrarNotificacion("La IP introducida es inválida, por favor corrígela.");
*/