From 62192fea8141699cea0d19c7469e3cf68f478994 Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Tue, 4 Aug 2026 14:00:34 -0600 Subject: [PATCH] Resolver ejercicio 77 intermedio --- .../maria-montepeque/maria-montepeque.css | 97 +++++++ .../maria-montepeque/maria-montepeque.html | 27 ++ .../maria-montepeque/maria-montepeque.js | 244 ++++++++++++++++++ 3 files changed, 368 insertions(+) create mode 100644 intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.css create mode 100644 intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.html create mode 100644 intermedio/ejercicios/77-tattoo-booking-app/resoluciones/maria-montepeque/maria-montepeque.js 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(); +}