diff --git a/intermedio/ejercicios/78-canvas-dibujo-basico/resoluciones/maria-montepeque/maria-montepeque.css b/intermedio/ejercicios/78-canvas-dibujo-basico/resoluciones/maria-montepeque/maria-montepeque.css
new file mode 100644
index 0000000..b10c1a0
--- /dev/null
+++ b/intermedio/ejercicios/78-canvas-dibujo-basico/resoluciones/maria-montepeque/maria-montepeque.css
@@ -0,0 +1,49 @@
+body {
+ font-family: Arial, Helvetica, sans-serif;
+ background-color: #f4f4f6;
+ color: #232323;
+ padding: 24px;
+ max-width: 700px;
+ margin: 0 auto;
+}
+
+h1 {
+ margin-bottom: 4px;
+}
+
+#barra-herramientas {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 12px 0;
+}
+
+#barra-herramientas label {
+ font-size: 13px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+#barra-herramientas button {
+ padding: 6px 14px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: #495057;
+ color: #fff;
+}
+
+#lienzo {
+ background-color: #ffffff;
+ border: 2px solid #495057;
+ border-radius: 6px;
+ cursor: crosshair;
+ touch-action: none;
+}
+
+#resumen-dibujo {
+ margin-top: 8px;
+ font-weight: bold;
+ color: #495057;
+}
diff --git a/intermedio/ejercicios/78-canvas-dibujo-basico/resoluciones/maria-montepeque/maria-montepeque.html b/intermedio/ejercicios/78-canvas-dibujo-basico/resoluciones/maria-montepeque/maria-montepeque.html
new file mode 100644
index 0000000..93795d9
--- /dev/null
+++ b/intermedio/ejercicios/78-canvas-dibujo-basico/resoluciones/maria-montepeque/maria-montepeque.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ 78. Canvas de dibujo basico
+
+
+
+ Mini herramienta de dibujo
+ Haz click y arrastra sobre el lienzo para dibujar.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/intermedio/ejercicios/78-canvas-dibujo-basico/resoluciones/maria-montepeque/maria-montepeque.js b/intermedio/ejercicios/78-canvas-dibujo-basico/resoluciones/maria-montepeque/maria-montepeque.js
new file mode 100644
index 0000000..bcd9fb4
--- /dev/null
+++ b/intermedio/ejercicios/78-canvas-dibujo-basico/resoluciones/maria-montepeque/maria-montepeque.js
@@ -0,0 +1,264 @@
+// 78. Canvas de dibujo basico
+// Resolucion: maria-montepeque
+//
+// Idea: el dibujo se modela como estado puro (color, grosor, lista de trazos, trazo en curso),
+// separado por completo de la pintura real en el canvas. Cada trazo es { color, grosor, puntos }.
+// Esto permite probar toda la logica (validaciones, agregar puntos, deshacer, limpiar) con
+// `node maria-montepeque.js` sin necesitar un canvas real, y en el navegador (maria-montepeque.html)
+// esa misma logica se conecta a los eventos del mouse para dibujar de verdad.
+
+const COLOR_POR_DEFECTO = '#1c7ed6';
+const GROSOR_POR_DEFECTO = 3;
+const GROSOR_MINIMO = 1;
+const GROSOR_MAXIMO = 50;
+const PATRON_COLOR_HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
+
+// ---------------------------------------------------------------------------
+// Estado del dibujo
+// ---------------------------------------------------------------------------
+
+function crearEstadoDibujo() {
+ return { color: COLOR_POR_DEFECTO, grosor: GROSOR_POR_DEFECTO, trazos: [], trazoEnCurso: null };
+}
+
+function validarColor(color) {
+ return typeof color === 'string' && PATRON_COLOR_HEX.test(color);
+}
+
+function validarGrosor(grosor) {
+ const numero = Number(grosor);
+ return Number.isFinite(numero) && numero >= GROSOR_MINIMO && numero <= GROSOR_MAXIMO;
+}
+
+function validarPunto(punto) {
+ return punto && Number.isFinite(Number(punto.x)) && Number.isFinite(Number(punto.y));
+}
+
+// ---------------------------------------------------------------------------
+// Cambios de herramienta (color y grosor)
+// ---------------------------------------------------------------------------
+
+function cambiarColor(estado, color) {
+ if (!validarColor(color)) {
+ return { exito: false, motivo: `Color invalido: "${color}". Usa formato hexadecimal, ej: #ff0000.`, estado };
+ }
+ return { exito: true, estado: { ...estado, color } };
+}
+
+function cambiarGrosor(estado, grosor) {
+ if (!validarGrosor(grosor)) {
+ return { exito: false, motivo: `El grosor debe ser un numero entre ${GROSOR_MINIMO} y ${GROSOR_MAXIMO}.`, estado };
+ }
+ return { exito: true, estado: { ...estado, grosor: Number(grosor) } };
+}
+
+// ---------------------------------------------------------------------------
+// Trazos: iniciar, agregar puntos, finalizar
+// ---------------------------------------------------------------------------
+
+function iniciarTrazo(estado, punto) {
+ if (!validarPunto(punto)) {
+ return { exito: false, motivo: 'El punto inicial no es valido.', estado };
+ }
+ const trazoEnCurso = { color: estado.color, grosor: estado.grosor, puntos: [{ x: Number(punto.x), y: Number(punto.y) }] };
+ return { exito: true, estado: { ...estado, trazoEnCurso } };
+}
+
+function agregarPuntoTrazo(estado, punto) {
+ if (!estado.trazoEnCurso) {
+ return { exito: false, motivo: 'No hay un trazo en curso. Usa iniciarTrazo primero.', estado };
+ }
+ if (!validarPunto(punto)) {
+ return { exito: false, motivo: 'El punto a agregar no es valido.', estado };
+ }
+ const trazoEnCurso = { ...estado.trazoEnCurso, puntos: [...estado.trazoEnCurso.puntos, { x: Number(punto.x), y: Number(punto.y) }] };
+ return { exito: true, estado: { ...estado, trazoEnCurso } };
+}
+
+function finalizarTrazo(estado) {
+ if (!estado.trazoEnCurso) {
+ return { exito: false, motivo: 'No hay un trazo en curso para finalizar.', estado };
+ }
+ return { exito: true, estado: { ...estado, trazos: [...estado.trazos, estado.trazoEnCurso], trazoEnCurso: null } };
+}
+
+// ---------------------------------------------------------------------------
+// Limpiar y deshacer (reto extra: deshacer el ultimo trazo)
+// ---------------------------------------------------------------------------
+
+function limpiarCanvas(estado) {
+ return { ...estado, trazos: [], trazoEnCurso: null };
+}
+
+function deshacerUltimoTrazo(estado) {
+ if (estado.trazos.length === 0) {
+ return { exito: false, motivo: 'No hay trazos para deshacer.', estado };
+ }
+ return { exito: true, estado: { ...estado, trazos: estado.trazos.slice(0, -1) } };
+}
+
+// ---------------------------------------------------------------------------
+// Resumen del dibujo (reto extra)
+// ---------------------------------------------------------------------------
+
+function generarResumenDibujo(estado) {
+ const totalPuntos = estado.trazos.reduce((acumulado, trazo) => acumulado + trazo.puntos.length, 0);
+ const conteoColores = estado.trazos.reduce((conteo, trazo) => {
+ conteo[trazo.color] = (conteo[trazo.color] ?? 0) + 1;
+ return conteo;
+ }, {});
+
+ return { totalTrazos: estado.trazos.length, totalPuntos, conteoColores };
+}
+
+// ---------------------------------------------------------------------------
+// Renderizado real en el canvas (solo en el navegador)
+// ---------------------------------------------------------------------------
+
+function redibujarCanvas(ctx, estado) {
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ estado.trazos.forEach((trazo) => dibujarTrazo(ctx, trazo));
+}
+
+function dibujarTrazo(ctx, trazo) {
+ if (trazo.puntos.length === 0) return;
+ ctx.strokeStyle = trazo.color;
+ ctx.lineWidth = trazo.grosor;
+ ctx.lineCap = 'round';
+ ctx.beginPath();
+ ctx.moveTo(trazo.puntos[0].x, trazo.puntos[0].y);
+ trazo.puntos.slice(1).forEach((punto) => ctx.lineTo(punto.x, punto.y));
+ ctx.stroke();
+}
+
+// ---------------------------------------------------------------------------
+// Interfaz en el DOM
+// ---------------------------------------------------------------------------
+
+function obtenerPuntoDelMouse(canvas, evento) {
+ const rect = canvas.getBoundingClientRect();
+ return { x: evento.clientX - rect.left, y: evento.clientY - rect.top };
+}
+
+function iniciarAplicacionDOM() {
+ const canvas = document.querySelector('#lienzo');
+ const entradaColor = document.querySelector('#entrada-color');
+ const entradaGrosor = document.querySelector('#entrada-grosor');
+ const botonLimpiar = document.querySelector('#boton-limpiar');
+ const botonDeshacer = document.querySelector('#boton-deshacer');
+ const resumenElemento = document.querySelector('#resumen-dibujo');
+
+ if (!canvas || !entradaColor || !entradaGrosor || !botonLimpiar || !botonDeshacer || !resumenElemento) return;
+
+ const ctx = canvas.getContext('2d');
+ let estado = crearEstadoDibujo();
+ let dibujando = false;
+
+ function actualizarResumen() {
+ const resumen = generarResumenDibujo(estado);
+ resumenElemento.textContent = `Trazos: ${resumen.totalTrazos} ยท Puntos: ${resumen.totalPuntos}`;
+ }
+
+ canvas.addEventListener('mousedown', (evento) => {
+ dibujando = true;
+ estado = iniciarTrazo(estado, obtenerPuntoDelMouse(canvas, evento)).estado;
+ });
+
+ canvas.addEventListener('mousemove', (evento) => {
+ if (!dibujando) return;
+ estado = agregarPuntoTrazo(estado, obtenerPuntoDelMouse(canvas, evento)).estado;
+ redibujarCanvas(ctx, estado);
+ dibujarTrazo(ctx, estado.trazoEnCurso);
+ });
+
+ function terminarTrazoActual() {
+ if (!dibujando) return;
+ dibujando = false;
+ estado = finalizarTrazo(estado).estado;
+ redibujarCanvas(ctx, estado);
+ actualizarResumen();
+ }
+
+ canvas.addEventListener('mouseup', terminarTrazoActual);
+ canvas.addEventListener('mouseleave', terminarTrazoActual);
+
+ entradaColor.addEventListener('input', () => {
+ estado = cambiarColor(estado, entradaColor.value).estado;
+ });
+
+ entradaGrosor.addEventListener('input', () => {
+ estado = cambiarGrosor(estado, entradaGrosor.value).estado;
+ });
+
+ botonLimpiar.addEventListener('click', () => {
+ estado = limpiarCanvas(estado);
+ redibujarCanvas(ctx, estado);
+ actualizarResumen();
+ });
+
+ botonDeshacer.addEventListener('click', () => {
+ const resultado = deshacerUltimoTrazo(estado);
+ estado = resultado.estado;
+ redibujarCanvas(ctx, estado);
+ actualizarResumen();
+ });
+
+ actualizarResumen();
+}
+
+// ---------------------------------------------------------------------------
+// Ejecucion por consola (node maria-montepeque.js) con al menos dos casos de prueba
+// ---------------------------------------------------------------------------
+
+function resolverEjercicio() {
+ console.log('=== 78. Canvas de dibujo basico ===');
+ let estado = crearEstadoDibujo();
+
+ // Caso 1: dibujar un trazo, cambiar de color y dibujar otro, luego limpiar.
+ console.log('\nCaso 1 - dibujar, cambiar color y limpiar:');
+ estado = iniciarTrazo(estado, { x: 0, y: 0 }).estado;
+ estado = agregarPuntoTrazo(estado, { x: 10, y: 10 }).estado;
+ estado = agregarPuntoTrazo(estado, { x: 20, y: 5 }).estado;
+ estado = finalizarTrazo(estado).estado;
+ console.log('Trazo 1 finalizado:', estado.trazos[0]);
+
+ estado = cambiarColor(estado, '#ff0000').estado;
+ estado = iniciarTrazo(estado, { x: 30, y: 30 }).estado;
+ estado = agregarPuntoTrazo(estado, { x: 40, y: 35 }).estado;
+ estado = finalizarTrazo(estado).estado;
+ console.log('Resumen antes de limpiar:', generarResumenDibujo(estado));
+
+ estado = limpiarCanvas(estado);
+ console.log('Resumen despues de limpiar:', generarResumenDibujo(estado));
+
+ // Caso 2: validaciones y casos limite - color invalido, grosor fuera de rango,
+ // agregar punto sin trazo en curso, y deshacer.
+ console.log('\nCaso 2 - validaciones y casos limite:');
+ const colorInvalido = cambiarColor(estado, 'azul');
+ console.log('Cambiar a color invalido "azul":', colorInvalido.exito ? 'aplicado' : colorInvalido.motivo);
+
+ const grosorInvalido = cambiarGrosor(estado, 100);
+ console.log('Cambiar a grosor invalido (100):', grosorInvalido.exito ? 'aplicado' : grosorInvalido.motivo);
+
+ const puntoSinTrazo = agregarPuntoTrazo(estado, { x: 5, y: 5 });
+ console.log('Agregar punto sin trazo en curso:', puntoSinTrazo.exito ? 'agregado' : puntoSinTrazo.motivo);
+
+ estado = iniciarTrazo(estado, { x: 1, y: 1 }).estado;
+ estado = finalizarTrazo(estado).estado;
+ estado = iniciarTrazo(estado, { x: 2, y: 2 }).estado;
+ estado = finalizarTrazo(estado).estado;
+ console.log('Trazos antes de deshacer:', generarResumenDibujo(estado).totalTrazos);
+ estado = deshacerUltimoTrazo(estado).estado;
+ console.log('Trazos despues de deshacer:', generarResumenDibujo(estado).totalTrazos);
+
+ const deshacerVacio = deshacerUltimoTrazo(limpiarCanvas(estado));
+ console.log('Deshacer sin trazos:', deshacerVacio.exito ? 'ok' : deshacerVacio.motivo);
+
+ return { estadoFinal: estado };
+}
+
+if (typeof document !== 'undefined') {
+ document.addEventListener('DOMContentLoaded', iniciarAplicacionDOM);
+} else {
+ resolverEjercicio();
+}