diff --git a/intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.css b/intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.css
new file mode 100644
index 0000000..6918b38
--- /dev/null
+++ b/intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.css
@@ -0,0 +1,97 @@
+body {
+ font-family: Arial, Helvetica, sans-serif;
+ background-color: #14131a;
+ color: #f2ede6;
+ padding: 24px;
+ max-width: 760px;
+ margin: 0 auto;
+}
+
+h1 {
+ margin-bottom: 4px;
+}
+
+p {
+ color: #c9c2b8;
+}
+
+#lista-artistas {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 12px;
+ margin: 16px 0;
+}
+
+.artista {
+ background-color: #1f1e29;
+ border: 1px solid #3a3847;
+ border-radius: 8px;
+ padding: 12px;
+}
+
+.artista h3 {
+ margin: 0 0 8px;
+ font-size: 15px;
+}
+
+.artista button {
+ margin: 2px;
+ padding: 6px 10px;
+ background-color: #b8860b;
+ color: #1a1a1a;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: bold;
+}
+
+.sin-cupo {
+ color: #a05c5c;
+ font-style: italic;
+ margin: 0;
+}
+
+#formulario-reserva {
+ display: flex;
+ gap: 8px;
+ margin: 12px 0;
+}
+
+#formulario-reserva input {
+ flex: 1;
+ padding: 8px;
+ border-radius: 4px;
+ border: 1px solid #3a3847;
+}
+
+#formulario-reserva button {
+ padding: 8px 16px;
+ background-color: #b8860b;
+ color: #1a1a1a;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: bold;
+}
+
+#seleccion-actual {
+ color: #d9c78a;
+ min-height: 18px;
+}
+
+.mensaje {
+ font-weight: bold;
+ min-height: 18px;
+}
+
+.mensaje.exito {
+ color: #7ec488;
+}
+
+.mensaje.error {
+ color: #e07a7a;
+}
+
+#lista-agenda {
+ padding-left: 18px;
+}
diff --git a/intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.html b/intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.html
new file mode 100644
index 0000000..e6cef9c
--- /dev/null
+++ b/intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+ 77. Booking tattoo app
+
+
+
+ Reserva tu cita de tatuaje
+ Elige un artista y un horario disponible, luego confirma con tu nombre.
+
+
+
+
+
+
+
+ Agenda del dia
+
+
+
+
+
diff --git a/intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.js b/intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.js
new file mode 100644
index 0000000..936c09f
--- /dev/null
+++ b/intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.js
@@ -0,0 +1,244 @@
+// 77. Booking tattoo app
+// Resolucion: maria-montepeque
+//
+// Idea: cada artista tiene una lista fija de horarios que ofrece. Las reservas son un array
+// separado { id, artistaId, horario, cliente }; un horario esta realmente disponible solo si
+// el artista lo ofrece Y no hay ya una reserva para ese artista en ese horario (se busca con find).
+// El mismo archivo corre con `node maria-montepeque.js` (casos de prueba por consola)
+// y en el navegador (maria-montepeque.html) como una agenda interactiva real.
+
+const ARTISTAS = [
+ { id: 1, nombre: 'Camila Rios', estilo: 'blackwork', horarios: ['09:00', '11:00', '15:00'] },
+ { id: 2, nombre: 'Julian Vega', estilo: 'realismo', horarios: ['10:00', '13:00', '17:00'] },
+ { id: 3, nombre: 'Marcela Suarez', estilo: 'acuarela', horarios: ['09:00', '12:00'] }
+];
+
+// ---------------------------------------------------------------------------
+// Consultas con find/filter
+// ---------------------------------------------------------------------------
+
+function obtenerArtistaPorId(idArtista) {
+ return ARTISTAS.find((artista) => artista.id === Number(idArtista));
+}
+
+function existeReservaEnHorario(reservas, idArtista, horario) {
+ return reservas.some((reserva) => reserva.artistaId === Number(idArtista) && reserva.horario === horario);
+}
+
+function listarHorariosLibres(artista, reservas) {
+ return artista.horarios.filter((horario) => !existeReservaEnHorario(reservas, artista.id, horario));
+}
+
+// ---------------------------------------------------------------------------
+// Validacion y confirmacion de la reserva
+// ---------------------------------------------------------------------------
+
+function validarDatosReserva(datos) {
+ if (!datos || typeof datos !== 'object') {
+ return { valido: false, motivo: 'Los datos de la reserva no son validos.' };
+ }
+ if (!datos.cliente || typeof datos.cliente !== 'string' || datos.cliente.trim() === '') {
+ return { valido: false, motivo: 'El nombre del cliente es obligatorio.' };
+ }
+
+ const artista = obtenerArtistaPorId(datos.artistaId);
+ if (!artista) {
+ return { valido: false, motivo: `No existe un artista con id ${datos.artistaId}.` };
+ }
+ if (!artista.horarios.includes(datos.horario)) {
+ return { valido: false, motivo: `${artista.nombre} no ofrece el horario ${datos.horario}.` };
+ }
+
+ return { valido: true, artista };
+}
+
+function generarIdReserva(reservas) {
+ return reservas.length === 0 ? 1 : Math.max(...reservas.map((reserva) => reserva.id)) + 1;
+}
+
+function confirmarReserva(reservas, datos) {
+ const validacion = validarDatosReserva(datos);
+ if (!validacion.valido) {
+ return { exito: false, motivo: validacion.motivo, reservas };
+ }
+ if (existeReservaEnHorario(reservas, validacion.artista.id, datos.horario)) {
+ return { exito: false, motivo: `El horario ${datos.horario} con ${validacion.artista.nombre} ya esta reservado.`, reservas };
+ }
+
+ const nuevaReserva = {
+ id: generarIdReserva(reservas),
+ artistaId: validacion.artista.id,
+ horario: datos.horario,
+ cliente: datos.cliente.trim()
+ };
+
+ return { exito: true, reserva: nuevaReserva, reservas: [...reservas, nuevaReserva] };
+}
+
+// ---------------------------------------------------------------------------
+// Resumen de la agenda (reto extra: reservas por artista)
+// ---------------------------------------------------------------------------
+
+function generarResumenAgenda(reservas) {
+ return ARTISTAS.map((artista) => ({
+ artista: artista.nombre,
+ reservadas: reservas.filter((reserva) => reserva.artistaId === artista.id).length,
+ libres: listarHorariosLibres(artista, reservas).length
+ }));
+}
+
+// ---------------------------------------------------------------------------
+// Salida por consola
+// ---------------------------------------------------------------------------
+
+function mostrarAgendaEnConsola(reservas) {
+ console.log('\nAgenda actual:');
+ if (reservas.length === 0) {
+ console.log('Sin reservas todavia.');
+ } else {
+ console.table(reservas.map((r) => ({ ...r, artista: obtenerArtistaPorId(r.artistaId).nombre })));
+ }
+ console.log('Resumen por artista:');
+ console.table(generarResumenAgenda(reservas));
+}
+
+// ---------------------------------------------------------------------------
+// Interfaz en el DOM
+// ---------------------------------------------------------------------------
+
+function crearTarjetaArtista(artista, reservas, onSeleccionarHorario) {
+ const tarjeta = document.createElement('article');
+ tarjeta.className = 'artista';
+
+ const titulo = document.createElement('h3');
+ titulo.textContent = `${artista.nombre} ยท ${artista.estilo}`;
+ tarjeta.appendChild(titulo);
+
+ const horariosLibres = listarHorariosLibres(artista, reservas);
+ if (horariosLibres.length === 0) {
+ const sinCupo = document.createElement('p');
+ sinCupo.className = 'sin-cupo';
+ sinCupo.textContent = 'Sin horarios disponibles.';
+ tarjeta.appendChild(sinCupo);
+ } else {
+ horariosLibres.forEach((horario) => {
+ const boton = document.createElement('button');
+ boton.type = 'button';
+ boton.textContent = horario;
+ boton.addEventListener('click', () => onSeleccionarHorario(artista.id, horario));
+ tarjeta.appendChild(boton);
+ });
+ }
+
+ return tarjeta;
+}
+
+function iniciarAplicacionDOM() {
+ const listaArtistas = document.querySelector('#lista-artistas');
+ const formulario = document.querySelector('#formulario-reserva');
+ const seleccionElemento = document.querySelector('#seleccion-actual');
+ const mensajeElemento = document.querySelector('#mensaje-estado');
+ const listaAgenda = document.querySelector('#lista-agenda');
+
+ if (!listaArtistas || !formulario || !seleccionElemento || !mensajeElemento || !listaAgenda) return;
+
+ let reservasActuales = [];
+ let seleccionActual = null;
+
+ function renderizarTodo() {
+ listaArtistas.innerHTML = '';
+ ARTISTAS.forEach((artista) => {
+ listaArtistas.appendChild(crearTarjetaArtista(artista, reservasActuales, seleccionarHorario));
+ });
+
+ listaAgenda.innerHTML = '';
+ reservasActuales.forEach((reserva) => {
+ const item = document.createElement('li');
+ item.textContent = `${obtenerArtistaPorId(reserva.artistaId).nombre} - ${reserva.horario} - ${reserva.cliente}`;
+ listaAgenda.appendChild(item);
+ });
+ }
+
+ function seleccionarHorario(idArtista, horario) {
+ seleccionActual = { idArtista, horario };
+ seleccionElemento.textContent = `Horario elegido: ${obtenerArtistaPorId(idArtista).nombre} a las ${horario}.`;
+ }
+
+ formulario.addEventListener('submit', (evento) => {
+ evento.preventDefault();
+ if (!seleccionActual) {
+ mensajeElemento.textContent = 'Elige un horario antes de confirmar.';
+ mensajeElemento.className = 'mensaje error';
+ return;
+ }
+
+ const cliente = new FormData(formulario).get('cliente');
+ const resultado = confirmarReserva(reservasActuales, {
+ artistaId: seleccionActual.idArtista,
+ horario: seleccionActual.horario,
+ cliente
+ });
+
+ if (!resultado.exito) {
+ mensajeElemento.textContent = resultado.motivo;
+ mensajeElemento.className = 'mensaje error';
+ return;
+ }
+
+ reservasActuales = resultado.reservas;
+ seleccionActual = null;
+ seleccionElemento.textContent = '';
+ mensajeElemento.textContent = `Reserva confirmada para ${resultado.reserva.cliente}.`;
+ mensajeElemento.className = 'mensaje exito';
+ formulario.reset();
+ renderizarTodo();
+ });
+
+ renderizarTodo();
+}
+
+// ---------------------------------------------------------------------------
+// Ejecucion por consola (node maria-montepeque.js) con al menos dos casos de prueba
+// ---------------------------------------------------------------------------
+
+function resolverEjercicio() {
+ console.log('=== 77. Booking tattoo app ===');
+ let reservas = [];
+
+ // Caso 1: reservas validas para dos artistas distintos.
+ console.log('\nCaso 1 - reservas validas:');
+ const reserva1 = confirmarReserva(reservas, { artistaId: 1, horario: '09:00', cliente: 'Valentina Cruz' });
+ reservas = reserva1.reservas;
+ console.log('Reserva 1:', reserva1.exito ? reserva1.reserva : reserva1.motivo);
+
+ const reserva2 = confirmarReserva(reservas, { artistaId: 2, horario: '13:00', cliente: 'Andres Leon' });
+ reservas = reserva2.reservas;
+ console.log('Reserva 2:', reserva2.exito ? reserva2.reserva : reserva2.motivo);
+
+ mostrarAgendaEnConsola(reservas);
+
+ // Caso 2: casos limite - horario ya ocupado, horario que el artista no ofrece,
+ // artista inexistente y cliente vacio.
+ console.log('\nCaso 2 - validaciones y casos limite:');
+ const horarioOcupado = confirmarReserva(reservas, { artistaId: 1, horario: '09:00', cliente: 'Otro Cliente' });
+ console.log('Reservar horario ya ocupado:', horarioOcupado.exito ? 'confirmada' : horarioOcupado.motivo);
+
+ const horarioNoOfrecido = confirmarReserva(reservas, { artistaId: 1, horario: '20:00', cliente: 'Otro Cliente' });
+ console.log('Reservar horario que el artista no ofrece:', horarioNoOfrecido.exito ? 'confirmada' : horarioNoOfrecido.motivo);
+
+ const artistaInexistente = confirmarReserva(reservas, { artistaId: 99, horario: '09:00', cliente: 'Otro Cliente' });
+ console.log('Reservar con artista inexistente:', artistaInexistente.exito ? 'confirmada' : artistaInexistente.motivo);
+
+ const clienteVacio = confirmarReserva(reservas, { artistaId: 3, horario: '12:00', cliente: '' });
+ console.log('Reservar sin nombre de cliente:', clienteVacio.exito ? 'confirmada' : clienteVacio.motivo);
+
+ mostrarAgendaEnConsola(reservas);
+
+ return { reservasFinales: reservas };
+}
+
+if (typeof document !== 'undefined') {
+ document.addEventListener('DOMContentLoaded', iniciarAplicacionDOM);
+} else {
+ resolverEjercicio();
+}