#kirin-luz * {
box-sizing: border-box;
}
#kirin-luz .kirin-wrap {
max-width: 1200px;
margin: 0 auto;
padding: 16px;
font-family: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,"Helvetica Neue","Noto Sans",Arial,sans-serif;
color: #111827;
}
#kirin-luz h2 {
font-size: clamp(28px,3.5vw,44px);
line-height: 1.1;
margin: 8px 0 6px;
text-align: center;
font-weight: 800;
}
#kirin-luz .kirin-sub {
text-align: center;
color: var(--kirin-muted);
margin-bottom: 18px;
}
#kirin-luz .ctrls {
display: grid;
gap: 10px;
margin: 8px 0 18px;
}
#kirin-luz .ctrls .nav {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
flex-wrap: nowrap;
}
#kirin-luz .ctrls .date {
display: flex;
justify-content: center;
}
#kirin-luz .btn {
border: 1px solid var(--kirin-border);
background: #fff;
padding: 8px 12px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
}
#kirin-luz .btn:hover {
background: #f9fafb;
}
#kirin-luz input[type="date"] {
padding: 8px 10px;
border: 1px solid var(--kirin-border);
border-radius: 10px;
min-width: 230px;
}
#kirin-luz .kirin-cards {
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 16px;
margin: 12px auto 24px;
max-width: 1000px;
}
#kirin-luz .kirin-card {
background: #fff;
border: 1px solid var(--kirin-border);
border-radius: 14px;
padding: 16px;
text-align: center;
box-shadow: 0 1px 0 rgba(17,24,39,.02);
}
#kirin-luz .kirin-card .label {
font-size: 12px;
letter-spacing: .04em;
color: var(--kirin-muted);
text-transform: uppercase;
margin-bottom: 6px;
}
#kirin-luz .kirin-card .value {
font-weight: 800;
font-size: clamp(20px,2.8vw,28px);
}
#kirin-luz .kirin-card .when {
font-size: 12px;
color: var(--kirin-muted);
margin-top: 4px;
}
#kirin-luz .kirin-card.high .value {
color: var(--kirin-red);
}
#kirin-luz .kirin-card.low .value {
color: var(--kirin-green);
}
#kirin-luz .kirin-card.avg .value {
color: #6b7280;
}
#kirin-luz .timeline-wrap {
margin: 6px auto 22px;
max-width: 1000px;
}
#kirin-luz .timeline {
display: grid;
grid-template-columns: repeat(24,1fr);
gap: 2px;
}
#kirin-luz .tcell {
height: 14px;
border-radius: 3px;
position: relative;
background: #e5e7eb;
}
#kirin-luz .tcell.min {
outline: 2px solid var(--kirin-green);
}
#kirin-luz .tcell.max {
outline: 2px solid var(--kirin-red);
}
#kirin-luz .tcell.now::after {
content: "";
position: absolute;
inset: -3px;
border: 2px dashed #111827;
border-radius: 4px;
}
#kirin-luz .timeline-labels {
display: flex;
justify-content: space-between;
font-size: 12px;
color: var(--kirin-muted);
margin: 4px 2px;
}
#kirin-luz .kirin-hrs {
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 18px;
margin-top: 10px;
}
#kirin-luz .col {
background: #fff;
border: 1px solid var(--kirin-border);
border-radius: 14px;
overflow: hidden;
}
#kirin-luz .col h3 {
text-align: center;
padding: 12px 10px;
margin: 0;
font-size: 14px;
letter-spacing: .06em;
text-transform: uppercase;
color: var(--kirin-muted);
background: #fff;
border-bottom: 1px solid var(--kirin-border);
}
#kirin-luz table {
width: 100%;
border-collapse: collapse;
font-variant-numeric: tabular-nums;
}
#kirin-luz th,
#kirin-luz td {
padding: 10px 12px;
border-bottom: 1px solid #eef2f7;
}
#kirin-luz th {
text-align: left;
font-size: 13px;
color: var(--kirin-muted);
background: var(--kirin-bg);
}
#kirin-luz td:last-child {
text-align: right;
}
#kirin-luz .cheap {
color: var(--kirin-green);
font-weight: 700;
}
#kirin-luz .exp {
color: var(--kirin-red);
font-weight: 700;
}
#kirin-luz .now-row td {
background: #fff8d6;
}
#kirin-luz .note {
margin-top: 10px;
font-size: 12px;
color: var(--kirin-muted);
text-align: center;
}
#kirin-luz .kirin-error {
margin-top: 12px;
text-align: center;
color: #991b1b;
background: #fff1f2;
border: 1px solid #fca5a5;
border-radius: 10px;
padding: 10px;
}
@media (max-width: 900px) {
#kirin-luz .kirin-cards {
grid-template-columns: 1fr;
}
#kirin-luz .kirin-hrs {
grid-template-columns: 1fr;
}
}
@media (max-width: 420px) {
#kirin-luz .ctrls .nav {
flex-wrap: wrap;
}
}
Resumen de precios de la luz hoy
Precio más bajo del día
—
—
Precio medio de la luz hoy
—
Precio más alto del día
—
—
00:00
06:00
12:00
18:00
24:00
Resumen de precios del kWh por horas
* Fuente: Red Eléctrica de España (REData). Valores expresados en €/kWh.
No ha sido posible cargar los precios ahora mismo. Inténtalo más tarde.
(function () {
"use strict";
const tz = "Europe/Madrid";
const fmtFechaLarga = new Intl.DateTimeFormat("es-ES", {
timeZone: tz,
dateStyle: "full"
});
const contenedor = document.getElementById("kirin-luz");
if (!contenedor || contenedor.dataset.iniciado === "1") {
return;
}
contenedor.dataset.iniciado = "1";
const fechaTitulo1 = document.getElementById("kirin-dia");
const fechaTitulo2 = document.getElementById("kirin-dia-2");
const selectorFecha = document.getElementById("kirin-date");
const timeline = document.getElementById("kirin-timeline");
const errorBox = document.getElementById("kirin-error");
function euro4(numero) {
return (Math.round(numero * 10000) / 10000).toFixed(4);
}
function toISO(fecha) {
return new Intl.DateTimeFormat("sv-SE", {
timeZone: tz
}).format(fecha);
}
function rango(fechaHora) {
const inicio = new Date(fechaHora);
const horaInicio = inicio.toLocaleTimeString("es-ES", {
hour: "2-digit",
minute: "2-digit",
timeZone: tz
});
const final = new Date(inicio.getTime() + 60 * 60 * 1000);
const horaFinal = final.toLocaleTimeString("es-ES", {
hour: "2-digit",
minute: "2-digit",
timeZone: tz
});
return horaInicio + " a " + horaFinal;
}
function pintarTabla(tablaId, filas, minimo, maximo, indiceActual) {
const cuerpo = document.querySelector(tablaId + " tbody");
cuerpo.innerHTML = filas.map(function (fila) {
let clasePrecio = "";
let claseActual = "";
if (fila.val = maximo * 0.92) {
clasePrecio = "exp";
}
if (fila.idx === indiceActual) {
claseActual = "now-row";
}
return (
'
' +
"| " + rango(fila.dt) + " | " +
'' +
euro4(fila.val) + " €/kWh" +
" | " +
"
"
);
}).join("");
}
function pintarTimeline(valores, minimo, maximo, indiceActual) {
timeline.innerHTML = "";
valores.forEach(function (valor, indice) {
const proporcion =
(valor - minimo) / Math.max(0.000000001, maximo - minimo);
const tono = Math.round(230 - proporcion * 120);
const celda = document.createElement("div");
celda.className = "tcell";
if (indice === valores.indexOf(minimo)) {
celda.classList.add("min");
}
if (indice === valores.indexOf(maximo)) {
celda.classList.add("max");
}
if (indice === indiceActual) {
celda.classList.add("now");
}
celda.title =
String(indice).padStart(2, "0") +
":00 → " +
euro4(valor) +
" €/kWh";
celda.style.background =
"rgb(" +
tono +
"," +
(tono + 5) +
"," +
(tono + 10) +
")";
timeline.appendChild(celda);
});
}
function separarTramos(horas) {
return {
noche: horas.filter(function (_, indice) {
return indice >= 0 && indice = 8 && indice = 16 && indice <= 23;
})
};
}
function obtenerIndiceActual(fechaISO) {
const hoyISO = toISO(new Date());
if (fechaISO !== hoyISO) {
return -1;
}
const hora = Number(
new Date().toLocaleTimeString("es-ES", {
hour: "2-digit",
hour12: false,
timeZone: tz
})
);
return Number.isNaN(hora) ? -1 : hora;
}
function mostrarError(mostrar) {
errorBox.hidden = !mostrar;
}
function cargarFecha(fechaISO) {
mostrarError(false);
const comienzo = fechaISO + "T00:00";
const final = fechaISO + "T23:59";
const url =
"https://apidatos.ree.es/es/datos/mercados/" +
"precios-mercados-tiempo-real" +
"?start_date=" + encodeURIComponent(comienzo) +
"&end_date=" + encodeURIComponent(final) +
"&time_trunc=hour" +
"&geo_limit=peninsular";
const fechaMediodia = new Date(fechaISO + "T12:00:00");
fechaTitulo1.textContent = fmtFechaLarga.format(fechaMediodia);
fechaTitulo2.textContent = fmtFechaLarga.format(fechaMediodia);
fetch(url, {
headers: {
Accept: "application/json"
}
})
.then(function (respuesta) {
if (!respuesta.ok) {
throw new Error("REData no disponible");
}
return respuesta.json();
})
.then(function (datos) {
const series = Array.isArray(datos.included)
? datos.included
: [];
const seriePVPC = series.find(function (serie) {
return (
serie.type === "PVPC" ||
serie.id === "1001" ||
serie.attributes?.title === "PVPC"
);
});
const valores =
seriePVPC &&
seriePVPC.attributes &&
Array.isArray(seriePVPC.attributes.values)
? seriePVPC.attributes.values
: [];
if (!valores.length) {
throw new Error("Sin datos disponibles");
}
const horas = valores
.slice()
.sort(function (a, b) {
return new Date(a.datetime) - new Date(b.datetime);
})
.map(function (valor, indice) {
return {
dt: valor.datetime,
val: valor.value / 1000,
idx: indice
};
});
const precios = horas.map(function (hora) {
return hora.val;
});
const minimo = Math.min.apply(null, precios);
const maximo = Math.max.apply(null, precios);
const indiceMinimo = precios.indexOf(minimo);
const indiceMaximo = precios.indexOf(maximo);
const media =
precios.reduce(function (total, precio) {
return total + precio;
}, 0) / precios.length;
document.querySelector("#kirin-low .value").textContent =
euro4(minimo) + " €/kWh";
document.querySelector("#kirin-low .when").textContent =
"de " + rango(horas[indiceMinimo].dt);
document.querySelector("#kirin-avg .value").textContent =
euro4(media) + " €/kWh";
document.querySelector("#kirin-high .value").textContent =
euro4(maximo) + " €/kWh";
document.querySelector("#kirin-high .when").textContent =
"de " + rango(horas[indiceMaximo].dt);
const indiceActual = obtenerIndiceActual(fechaISO);
pintarTimeline(
precios,
minimo,
maximo,
indiceActual
);
const tramos = separarTramos(horas);
pintarTabla(
"#kirin-noche",
tramos.noche,
minimo,
maximo,
indiceActual
);
pintarTabla(
"#kirin-manana",
tramos.manana,
minimo,
maximo,
indiceActual
);
pintarTabla(
"#kirin-tarde",
tramos.tarde,
minimo,
maximo,
indiceActual
);
})
.catch(function (error) {
console.error("Error al cargar precios REData:", error);
mostrarError(true);
});
}
const hoyISO = toISO(new Date());
selectorFecha.value = hoyISO;
cargarFecha(hoyISO);
document
.getElementById("btn-hoy")
.addEventListener("click", function () {
selectorFecha.value = hoyISO;
cargarFecha(hoyISO);
});
document
.getElementById("btn-ayer")
.addEventListener("click", function () {
const fecha = new Date(selectorFecha.value + "T12:00:00");
fecha.setDate(fecha.getDate() - 1);
const fechaISO = toISO(fecha);
selectorFecha.value = fechaISO;
cargarFecha(fechaISO);
});
document
.getElementById("btn-manana")
.addEventListener("click", function () {
const fecha = new Date(selectorFecha.value + "T12:00:00");
fecha.setDate(fecha.getDate() + 1);
const fechaISO = toISO(fecha);
selectorFecha.value = fechaISO;
cargarFecha(fechaISO);
});
selectorFecha.addEventListener("change", function (evento) {
const fechaISO = evento.target.value;
if (fechaISO) {
cargarFecha(fechaISO);
}
});
})();