Apuntes DAM
Volver al inicio

Calendario de un mes con festivos y puentes

Ejercicio de JavaScriptMedioUnos 50 minutos

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

  1. La primera línea es año mes (año de 1900 a 2100, mes de 1 a 12); si no es válida, escribe Fecha no válida y 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, escribe Aviso: línea N ignorada (las líneas vacías no avisan). Si un día se repite, vale el último nombre.
  2. Escribe el título (Diciembre de 2026) y la cabecera L 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.
  3. 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.
  4. Días laborables: N: los días de lunes a viernes que no son festivos.
  5. 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 7

Mayo 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: ninguno

Guí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.

javascript
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.

javascript
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.

🟨JavaScriptCalendario de un mes con festivos y puentesMedio

Ejemplo

Entrada (lo que se escribe por teclado)
2026 12
6 Día de la Constitución
8 Inmaculada Concepción
25 Navidad
Salida esperada
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 7
⏳
Test oculto #3
⏳
Test oculto #4
⏳
Test oculto #5
0/5 tests pasados · pulsa un test para ver su entrada y su salida esperada

Solución explicada

Ver la solución completa
javascript
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.

Dónde se explica