megafonias exteriores

This commit is contained in:
2026-02-11 01:25:13 +01:00
parent 1b0e879ba2
commit 834b4de03e
45 changed files with 157 additions and 87 deletions

View File

@@ -1,12 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="es"> <html lang="es">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Megafonías TsSAEx</title> <title>Megafonías TsSAEx</title>
<link rel="stylesheet" href="/src/style.css"> <link rel="stylesheet" href="/src/style.css">
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
</head> </head>
<body> <body>
<div id="loading-screen"> <div id="loading-screen">
@@ -28,16 +33,24 @@
<div class="vol-header"><md-icon>meeting_room</md-icon><span>Panel Interior</span></div> <div class="vol-header"><md-icon>meeting_room</md-icon><span>Panel Interior</span></div>
<md-filled-select id="int-linea" label="Línea"></md-filled-select> <md-filled-select id="int-linea" label="Línea"></md-filled-select>
<md-filled-select id="int-parada" label="Parada" disabled></md-filled-select> <md-filled-select id="int-parada" label="Parada" disabled></md-filled-select>
<div class="radio-container"> <div class="radio-container">
<label><md-radio name="tipo" value="actual" checked></md-radio>Parada actual</label> <label><md-radio name="tipo" value="actual" checked></md-radio>Parada actual</label>
<label><md-radio name="tipo" value="siguiente"></md-radio>Parada siguiente</label> <label><md-radio name="tipo" value="siguiente"></md-radio>Parada siguiente</label>
</div> </div>
<div id="container-regulacion" class="regulacion-box">
<md-checkbox id="chk-regulacion"></md-checkbox>
<label for="chk-regulacion">Parada de regulación</label>
</div>
<md-filled-button id="btn-int">Anunciar Parada</md-filled-button> <md-filled-button id="btn-int">Anunciar Parada</md-filled-button>
</section> </section>
<section class="m3-card secondary"> <section class="m3-card secondary">
<div class="vol-header"><md-icon>campaign</md-icon><span>Exterior</span></div> <div class="vol-header"><md-icon>campaign</md-icon><span>Exterior</span></div>
<p style="margin:0; opacity:0.7; font-size:0.9rem;">Configuración de letrero y megafonía externa para paradas.</p> <p style="margin:0; opacity:0.7; font-size:0.9rem;">Configuración de letrero y megafonía externa para paradas.
</p>
<md-filled-tonal-button id="btn-abrir-exterior"> <md-filled-tonal-button id="btn-abrir-exterior">
<md-icon slot="icon">open_in_new</md-icon>Configurar Exterior <md-icon slot="icon">open_in_new</md-icon>Configurar Exterior
</md-filled-tonal-button> </md-filled-tonal-button>
@@ -74,7 +87,8 @@
</div> </div>
<div id="lista-paradas-editor"></div> <div id="lista-paradas-editor"></div>
<div class="editor-actions"> <div class="editor-actions">
<md-filled-tonal-button id="btn-add-parada-ed"><md-icon slot="icon">add</md-icon>Añadir Parada</md-filled-tonal-button> <md-filled-tonal-button id="btn-add-parada-ed"><md-icon slot="icon">add</md-icon>Añadir
Parada</md-filled-tonal-button>
<md-filled-button id="btn-generar-json">Exportar JSON</md-filled-button> <md-filled-button id="btn-generar-json">Exportar JSON</md-filled-button>
</div> </div>
<pre id="output-json" class="json-preview"></pre> <pre id="output-json" class="json-preview"></pre>
@@ -88,7 +102,8 @@
<md-filled-select id="ext-linea" label="Línea"></md-filled-select> <md-filled-select id="ext-linea" label="Línea"></md-filled-select>
<md-filled-select id="ext-destino" label="Destino" disabled></md-filled-select> <md-filled-select id="ext-destino" label="Destino" disabled></md-filled-select>
<md-filled-select id="ext-coche" label="Coche"></md-filled-select> <md-filled-select id="ext-coche" label="Coche"></md-filled-select>
<div class="vol-header" style="margin-top:10px"><md-icon>volume_down</md-icon><span>Volumen Exterior</span></div> <div class="vol-header" style="margin-top:10px"><md-icon>volume_down</md-icon><span>Volumen Exterior</span>
</div>
<md-slider id="vol-ext" min="0" max="1" step="0.01" value="0.9"></md-slider> <md-slider id="vol-ext" min="0" max="1" step="0.01" value="0.9"></md-slider>
</div> </div>
</form> </form>
@@ -101,4 +116,5 @@
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
</body> </body>
</html> </html>

1
package-lock.json generated
View File

@@ -735,6 +735,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -7,6 +7,10 @@
"L2": { "L2": {
"nombre": "Línea 2", "nombre": "Línea 2",
"destinos": ["Zafra", "Hospital JRJ"] "destinos": ["Zafra", "Hospital JRJ"]
},
"L3": {
"nombre": "Línea 3",
"destinos": ["Zafra", "Orden Baja"]
} }
} }
} }

View File

@@ -1,118 +1,151 @@
import '@material/web/all.js'; import 'https://cdn.jsdelivr.net/npm/@material/web/all.js/+esm';
import "@fontsource/roboto";
let dbInt = {}, dbExt = {}; let dbInt = {};
let audio = null; let dbExt = { lineas: {} }; // Base de datos exterior inicializada
let audioActual = null;
const get = (id) => document.getElementById(id); const get = (id) => document.getElementById(id);
// --- 1. INICIO DEL SISTEMA ---
async function init() { async function init() {
try { try {
const [rI, rE] = await Promise.all([ const [resInt, resExt] = await Promise.all([
fetch('/data/lineas.json'), fetch('data/lineas.json'),
fetch('/data/exterior.json') fetch('data/exterior.json')
]); ]);
dbInt = await rI.json();
dbExt = await rE.json();
// 1. Rellenar Líneas (Interior y Exterior) if (!resInt.ok || !resExt.ok) throw new Error("Error cargando archivos JSON");
const lineas = Object.keys(dbInt);
const opts = lineas.map(id => `<md-select-option value="${id}"><div slot="headline">${id}</div></md-select-option>`).join('');
if(get('int-linea')) get('int-linea').innerHTML = opts; dbInt = await resInt.json();
if(get('ext-linea')) get('ext-linea').innerHTML = opts; dbExt = await resExt.json();
// 2. Generar Coches const lineasHTML = Object.keys(dbInt).map(id =>
let coches = []; `<md-select-option value="${id}"><div slot="headline">${id}</div></md-select-option>`
for (let i = 315; i <= 334; i++) coches.push(i);
for (let i = 400; i <= 409; i++) coches.push(i);
if(get('ext-coche')) {
get('ext-coche').innerHTML = coches.map(c => `<md-select-option value="${c}"><div slot="headline">${c}</div></md-select-option>`).join('');
}
} catch (e) {
console.error("Error cargando TsSAEx:", e);
}
// Quitar Splash
setTimeout(() => {
const splash = get('loading-screen');
if(splash) splash.style.opacity = '0';
setTimeout(() => {
if(splash) splash.style.display = 'none';
const app = get('app');
if(app) app.style.display = 'flex';
}, 500);
}, 2000);
}
// REPRODUCTOR
function play(lista, volumen) {
if (audio) { audio.pause(); audio.currentTime = 0; }
if (!lista || lista.length === 0) return;
const file = lista.shift();
audio = new Audio(`${file}?t=${Date.now()}`);
audio.volume = volumen;
audio.play().catch(() => play(lista, volumen));
audio.onended = () => play(lista, volumen);
}
// --- EVENTO CLAVE: CAMBIO DE LÍNEA INTERIOR ---
get('int-linea')?.addEventListener('change', (e) => {
const lineaSeleccionada = e.target.value;
const paradas = dbInt[lineaSeleccionada] || [];
const selectorParadas = get('int-parada');
if (selectorParadas) {
// Insertar nuevas paradas
selectorParadas.innerHTML = paradas.map(p =>
`<md-select-option value="${p.id}"><div slot="headline">${p.nombre}</div></md-select-option>`
).join(''); ).join('');
// IMPORTANTE: Resetear valor y habilitar if (get('int-linea')) get('int-linea').innerHTML = lineasHTML;
selectorParadas.value = ""; if (get('ext-linea')) get('ext-linea').innerHTML = lineasHTML;
selectorParadas.disabled = false;
// Generar números de coche de Huelva (315-334 y 400-409)
const coches = [...Array(20).keys()].map(i => i + 315).concat([...Array(10).keys()].map(i => i + 400));
if (get('ext-coche')) {
get('ext-coche').innerHTML = coches.map(c =>
`<md-select-option value="${c}"><div slot="headline">${c}</div></md-select-option>`
).join('');
} }
} catch (err) {
console.error("Error cargando DB:", err);
} finally {
const splash = get('loading-screen');
if (splash) {
splash.style.opacity = '0';
setTimeout(() => {
splash.style.display = 'none';
get('app').style.display = 'flex';
}, 500);
}
}
}
// --- 2. MOTOR DE AUDIO ---
function play(cola, volId = 'vol') {
if (audioActual) { audioActual.pause(); audioActual.currentTime = 0; }
if (!cola || cola.length === 0) return;
const item = cola.shift();
audioActual = new Audio(`${item.file}?cb=${Date.now()}`);
// Usar el slider correspondiente (interior o exterior)
const slider = get(volId);
audioActual.volume = slider ? slider.value : 0.8;
audioActual.play().catch(e => console.warn("Audio no encontrado:", item.file));
audioActual.onended = () => setTimeout(() => play(cola, volId), item.gap || 400);
}
// --- 3. EVENTOS PANEL INTERIOR ---
get('int-linea')?.addEventListener('change', (e) => {
const linea = dbInt[e.target.value];
const selParada = get('int-parada');
if (linea && selParada) {
selParada.innerHTML = linea.paradas.map(p =>
`<md-select-option value="${p.id}"><div slot="headline">${p.nombre}</div></md-select-option>`
).join('');
selParada.disabled = false;
selParada.value = "";
}
get('container-regulacion').style.display = 'none';
});
get('int-parada')?.addEventListener('change', (e) => {
const paradasReg = ['zafra', 'orden_baja', 'hospital_jrj'];
const esReg = paradasReg.includes(e.target.value);
get('container-regulacion').style.display = esReg ? 'flex' : 'none';
if (!esReg) get('chk-regulacion').checked = false;
}); });
// BOTÓN ANUNCIAR
get('btn-int')?.addEventListener('click', () => { get('btn-int')?.addEventListener('click', () => {
const lineaId = get('int-linea').value; const lId = get('int-linea').value, pId = get('int-parada').value;
const paradaId = get('int-parada').value; if (!lId || !pId) return;
const tipo = document.querySelector('md-radio[name="tipo"][checked]')?.value || "actual";
if(!lineaId || !paradaId) return alert("Selecciona línea y parada"); const paradaData = dbInt[lId].paradas.find(p => p.id === pId);
const tipo = document.querySelector('md-radio[value="siguiente"]')?.checked ? "siguiente" : "actual";
const p = dbInt[lineaId]?.find(x => x.id === paradaId); let cola = [
if (!p) return; { file: `audio/parada_${tipo}.wav`, gap: 400 },
{ file: `audio/${pId}.wav`, gap: 600 }
];
let cola = [`/audio/parada_${tipo}.wav`, `/audio/${p.id}.wav` ]; if (paradaData.enlaces?.length > 0) {
if (p.enlaces?.length > 0) { const enlaces = paradaData.enlaces.filter(en => en.trim() !== "");
cola.push(`/audio/correspondencia.wav`); if (enlaces.includes("todas")) {
p.enlaces.includes("todas") ? cola.push(`/audio/todas_las_lineas.wav`) : p.enlaces.forEach(e => cola.push(`/audio/linea_${e}.wav`)); cola.push({ file: `audio/correspondencia_todas_las_lineas.wav`, gap: 400 });
} else if (enlaces.length > 0) {
cola.push({ file: `audio/${enlaces.length === 1 ? 'correspondencia_linea' : 'correspondencia_lineas'}.wav`, gap: 300 });
enlaces.forEach(en => cola.push({ file: `audio/linea_${en}.wav`, gap: 300 }));
} }
play(cola, get('vol').value); }
if (get('chk-regulacion')?.checked) cola.push({ file: `audio/parada_regulacion.wav`, gap: 400 });
play(cola, 'vol');
}); });
// LOGICA EXTERIOR (POPUP) // --- 4. LÓGICA EXTERIOR (RESTAURADA) ---
get('btn-abrir-exterior').onclick = () => get('dialog-exterior').show(); get('btn-abrir-exterior').onclick = () => get('dialog-exterior').show();
get('ext-linea')?.addEventListener('change', (e) => { get('ext-linea')?.addEventListener('change', (e) => {
const d = dbExt.lineas[e.target.value]?.destinos || []; const lineaId = e.target.value;
get('ext-destino').innerHTML = d.map(x => `<md-select-option value="${x}"><div slot="headline">${x}</div></md-select-option>`).join(''); const destinos = dbExt.lineas[lineaId]?.destinos || [];
get('ext-destino').disabled = false; const selDest = get('ext-destino');
if (selDest) {
selDest.innerHTML = destinos.map(d => `<md-select-option value="${d}"><div slot="headline">${d}</div></md-select-option>`).join('');
selDest.disabled = false;
}
}); });
get('btn-reproducir-ext').onclick = () => { get('btn-reproducir-ext')?.addEventListener('click', () => {
const c = get('ext-coche').value, l = get('ext-linea').value, d = get('ext-destino').value; const linea = get('ext-linea').value;
if (!c || !l || !d) return; const destino = get('ext-destino').value;
const destFile = d.toLowerCase().replace(/ /g, "_"); const coche = get('ext-coche').value;
play(['/audio/linea.wav', `/audio/${l}.wav`, '/audio/autobus.wav', `/audio/${c}.wav`, '/audio/destino.wav', `/audio/${destFile}.wav`], get('vol-ext').value);
};
// BOTONES ESPECIALES if (!linea || !destino || !coche) return;
get('btn-colision').onclick = () => play(['/audio/colision.wav'], get('vol').value);
get('btn-hora').onclick = () => play(['/audio/hora_salida.wav'], get('vol').value); const destFile = destino.toLowerCase().replace(/ /g, "_");
get('btn-saldo').onclick = () => play(['/audio/atencion_saldo.wav'], get('vol').value);
get('btn-insuficiente').onclick = () => play(['/audio/saldo_insuficiente.wav'], get('vol').value); // Cola exterior: Línea X -> Autobús XXX -> Destino YYY
const colaExt = [
{ file: `audio/exterior/linea_${linea}.wav`, gap: 300 },
{ file: `audio/exterior/autobus_${coche}.wav`, gap: 500 },
{ file: `audio/exterior/destino_${destFile}.wav`, gap: 300 }
];
play(colaExt, 'vol-ext');
});
// --- 5. ESPECIALES ---
get('btn-colision').onclick = () => play([{ file: `audio/colision.wav` }]);
get('btn-hora').onclick = () => play([{ file: `audio/hora_salida.wav` }]);
get('btn-saldo').onclick = () => play([{ file: `audio/atencion_saldo.wav` }]);
get('btn-insuficiente').onclick = () => play([{ file: `audio/saldo_insuficiente.wav` }]);
init(); init();

View File

@@ -110,6 +110,22 @@ md-icon { font-family: 'Material Symbols Outlined' !important; }
} }
.radio-container label { display: flex; align-items: center; gap: 12px; cursor: pointer; } .radio-container label { display: flex; align-items: center; gap: 12px; cursor: pointer; }
/* --- ESTILOS REGULACIÓN --- */
#container-regulacion {
/* display: none; El JS lo cambia a flex */
background: rgba(109, 94, 0, 0.08); /* Uso del color primary con baja opacidad */
padding: 12px 16px;
border-radius: 20px;
border: 1px dashed var(--md-sys-color-primary);
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
/* --- BOTONES Y ESPECIALES --- */
.button-grid-specials { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .button-grid-specials { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.danger-btn { --md-filled-tonal-button-container-color: var(--md-sys-color-error-container); } .danger-btn { --md-filled-tonal-button-container-color: var(--md-sys-color-error-container); }