Calendario de un mes con festivos y puentes
Dibuja en la consola el calendario de un mes empezando en lunes, marca los festivos, cuenta los días laborables y detecta los puentes posibles. El objeto Date sin sorpresas de zona horaria, Map, métodos de arrays y formato de texto con padStart.
- Date y Date.UTC
- getUTCDay
- Map
- padStart y trimEnd
- Expresiones regulares
- Arrays: sort, map, join
Enunciado
La intranet de una empresa quiere un calendario sencillo en el que cada departamento marque sus festivos y vea de un vistazo cuántos días se trabajan en el mes y qué puentes se pueden pedir. Antes de dibujarlo en la página, la lógica se escribe y se prueba en la consola con Node.js.
En España las semanas empiezan en lunes, pero getDay() numera los días con el domingo como 0. Además, new Date(2026, 1, 1) crea la fecha en la hora local del ordenador, y según la zona horaria el día puede cambiar al pasarla a UTC. Usar Date.UTC y los métodos getUTC… evita las dos trampas.
Un puente es un día laborable que queda encerrado entre un festivo y el fin de semana: si un festivo cae en martes, el lunes anterior; si cae en jueves, el viernes siguiente.
Qué tiene que hacer el programa
- La primera línea es
año mes(año de 1900 a 2100, mes de 1 a 12); si no es válida, escribeFecha no váliday nada más. Cada línea siguiente es un festivo:día nombre. Una línea con un día que no existe en ese mes, o sin nombre, escribeAviso: línea N ignorada(las líneas vacías no avisan). Si un día se repite, vale el último nombre. - Escribe el título (
Diciembre de 2026) y la cabeceraL M X J V S D. Después, una línea por semana: cada día ocupa 3 caracteres, el número alineado a la derecha en 2 y un*si es festivo o un espacio si no. La primera semana empieza con 3 espacios por cada día del mes anterior. Ninguna línea termina en espacios. Festivos:y, por orden de día, una línea por festivo con dos espacios delante:8 Inmaculada Concepción (martes). Si no hay ninguno,Festivos: ninguno.Días laborables: N: los días de lunes a viernes que no son festivos.Puentes posibles: lunes 7, viernes 24: los lunes anteriores a un festivo en martes y los viernes posteriores a un festivo en jueves, siempre que sean del mismo mes y no sean festivos, por orden de día y sin repetir. Si no hay ninguno,Puentes posibles: ninguno.
Entrada
Línea 1: año mes. Resto: un festivo por línea, día nombre.
Ejemplos de ejecución
Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.
Diciembre con tres festivos
Entrada
2026 12 6 Día de la Constitución 8 Inmaculada Concepción 25 Navidad
Salida por consola
Diciembre de 2026
L M X J V S D
1 2 3 4 5 6*
7 8* 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25*26 27
28 29 30 31
Festivos:
6 Día de la Constitución (domingo)
8 Inmaculada Concepción (martes)
25 Navidad (viernes)
Días laborables: 21
Puentes posibles: lunes 7Mayo con líneas que no valen
Entrada
2026 5 1 Fiesta del Trabajo 2 Comunidad de Madrid 40 Día imposible abc 15 San Isidro
Salida por consola
Aviso: línea 4 ignorada
Aviso: línea 5 ignorada
Mayo de 2026
L M X J V S D
1* 2* 3
4 5 6 7 8 9 10
11 12 13 14 15*16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Festivos:
1 Fiesta del Trabajo (viernes)
2 Comunidad de Madrid (sábado)
15 San Isidro (viernes)
Días laborables: 19
Puentes posibles: ningunoGuía paso a paso
Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.
1. Leer la entrada en Node.js
require("fs").readFileSync(0, "utf8") lee toda la entrada estándar (el descriptor 0) como texto. Pártela en líneas con split("\n"). Destructurar la primera línea te da el año y el mes en una sola instrucción.
const [anio, mes] = lineas[0].trim().split(/\s+/).map(Number);2. Fechas en UTC
El número de días del mes es el día 0 del mes siguiente: new Date(Date.UTC(anio, mes, 0)).getUTCDate() (los meses de Date empiezan en 0, por eso mes sin restar es ya el siguiente). Para que el lunes sea 0, suma 6 a getUTCDay() y haz el resto entre 7.
const diaSemana = (d) => (new Date(Date.UTC(anio, mes - 1, d)).getUTCDay() + 6) % 7;3. Dibujar las semanas
Construye cada fila como texto: empieza con " ".repeat(diaSemana(1)) y añade cada día con String(d).padStart(2) más la marca. Cuando el día sea domingo (o el último del mes), escribe la fila con trimEnd() y empieza otra.
4. Festivos en un Map
Un Map día → nombre responde al instante si un día es festivo (has) y guarda el nombre. Para listarlos en orden, conviértelo en array con [...festivos] y ordénalo por el día.
5. Puentes sin repetidos
Recorre los festivos y calcula su candidato (el día anterior si es martes, el siguiente si es jueves). Mételos en un Set para no repetir y ordénalos antes de escribirlos.
Resuélvelo aquí
El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 3 casos ocultos que buscan los errores típicos.
Ejemplo
2026 12 6 Día de la Constitución 8 Inmaculada Concepción 25 Navidad
Diciembre de 2026
L M X J V S D
1 2 3 4 5 6*
7 8* 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25*26 27
28 29 30 31
Festivos:
6 Día de la Constitución (domingo)
8 Inmaculada Concepción (martes)
25 Navidad (viernes)
Días laborables: 21
Puentes posibles: lunes 7Solución explicada
Ver la solución completa
1const lineas = require("fs").readFileSync(0, "utf8").split("\n");
2
3const MESES = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"];
4const DIAS = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"];
5
6function main() {
7 const [anio, mes] = (lineas[0] ?? "").trim().split(/\s+/).map(Number);
8 if (!Number.isInteger(anio) || !Number.isInteger(mes) || anio < 1900 || anio > 2100 || mes < 1 || mes > 12) {
9 console.log("Fecha no válida");
10 return;
11 }
12
13 // Con UTC no influye la zona horaria del ordenador. El día 0 del mes siguiente es el último de este
14 const diasMes = new Date(Date.UTC(anio, mes, 0)).getUTCDate();
15 // getUTCDay da 0 = domingo; se pasa a 0 = lunes … 6 = domingo
16 const diaSemana = (d) => (new Date(Date.UTC(anio, mes - 1, d)).getUTCDay() + 6) % 7;
17
18 const festivos = new Map();
19 lineas.slice(1).forEach((linea, i) => {
20 const texto = linea.trim();
21 if (!texto) return;
22 const m = texto.match(/^(\d{1,2})\s+(.+)$/);
23 const dia = m ? Number(m[1]) : 0;
24 if (!m || dia < 1 || dia > diasMes) {
25 console.log(`Aviso: línea ${i + 2} ignorada`);
26 return;
27 }
28 festivos.set(dia, m[2]);
29 });
30
31 console.log(`${MESES[mes - 1]} de ${anio}`);
32 console.log(" L M X J V S D");
33 let fila = " ".repeat(diaSemana(1));
34 for (let d = 1; d <= diasMes; d++) {
35 fila += String(d).padStart(2) + (festivos.has(d) ? "*" : " ");
36 if (diaSemana(d) === 6 || d === diasMes) {
37 console.log(fila.trimEnd());
38 fila = "";
39 }
40 }
41
42 const ordenados = [...festivos].sort((a, b) => a[0] - b[0]);
43 if (ordenados.length === 0) {
44 console.log("Festivos: ninguno");
45 } else {
46 console.log("Festivos:");
47 ordenados.forEach(([d, nombre]) => console.log(` ${d} ${nombre} (${DIAS[diaSemana(d)]})`));
48 }
49
50 let laborables = 0;
51 for (let d = 1; d <= diasMes; d++) if (diaSemana(d) < 5 && !festivos.has(d)) laborables++;
52 console.log(`Días laborables: ${laborables}`);
53
54 // Un festivo en martes deja «puente» el lunes anterior; uno en jueves, el viernes siguiente
55 const puentes = new Set();
56 for (const [d] of ordenados) {
57 const candidato = diaSemana(d) === 1 ? d - 1 : diaSemana(d) === 3 ? d + 1 : 0;
58 if (candidato >= 1 && candidato <= diasMes && !festivos.has(candidato)) puentes.add(candidato);
59 }
60 const lista = [...puentes].sort((a, b) => a - b).map((d) => `${DIAS[diaSemana(d)]} ${d}`);
61 console.log(`Puentes posibles: ${lista.length ? lista.join(", ") : "ninguno"}`);
62}
63
64main();Toda la aritmética de fechas la hace Date: el último día del mes con el truco del día 0 y el día de la semana con getUTCDay. Trabajar en UTC hace que el resultado sea el mismo en Madrid, en Canarias o en el servidor de Wandbox.
La conversión (getUTCDay() + 6) % 7 reordena la semana para que empiece en lunes. Con ese único cambio, el hueco inicial de la primera semana y el salto de línea del domingo salen solos.
Cada celda mide siempre 3 caracteres (dos para el número y uno para la marca), así que las columnas quedan alineadas sin calcular posiciones. trimEnd() quita el relleno sobrante del final de cada línea.
El Map de festivos sirve a la vez para marcar el calendario, listarlos, descontarlos de los laborables y descartar puentes que ya son festivo: elegir bien la estructura de datos simplifica el resto del programa.
Para ir más allá
- Dibuja el calendario de todo el año en tres columnas de meses.
- Lee los festivos de un JSON con fechas completas (
2026-12-08) y muestra solo los del mes pedido. - Lleva la misma lógica a una página: genera una
<table>con el DOM y marca los festivos y los puentes con clases CSS.