Autor: ciberkaos

  • Taller de DevTools para desarrollo web y seguridad

    Taller de DevTools para desarrollo web y seguridad

    El navegador permite observar cómo una página construye su interfaz, ejecuta JavaScript y se comunica con un servidor. Sus herramientas de desarrollo también permiten descubrir fallos de seguridad: datos expuestos, validaciones que solo existen en el cliente, accesos a objetos ajenos y uso inseguro del DOM. En este taller trabajarás sobre Loot Lab, una tienda ficticia que incluye comportamientos vulnerables y versiones corregidas para poder comparar resultados.

    Audio

    La recomendación es utilizar Chrome como navegador principal y Firefox para una sesión de contraste. Se pueden realizar las prácticas esenciales con ambos. La elección organiza las explicaciones y las capturas; no implica que uno convierta una aplicación en más segura.

    Al terminar deberás poder explicar un hallazgo con pruebas reproducibles, identificar dónde debe corregirse y verificar que la corrección funciona. Modificar un precio en pantalla será el inicio del análisis; comprobar cuánto acepta el servidor será la evidencia relevante.

    1 Elección del navegador y equivalencias

    Propuesta para este grupo

    Utiliza una versión estable actual de Chrome para la demostración común. Instala también Firefox si el aula lo permite. Dedica aproximadamente el 80 % del trabajo a una interfaz común y el resto a repetir algunas operaciones en el segundo navegador. Anota la versión instalada al comenzar: las etiquetas y la posición de algunas opciones cambian entre versiones e idiomas.

    Chrome agrupa en Application el almacenamiento y ofrece un flujo cómodo para depurar, analizar red y probar respuestas locales. Firefox resulta especialmente didáctico al editar y reenviar peticiones desde su monitor de red. Chrome también permite editar y reenviar peticiones como fetch; no es una capacidad exclusiva de Firefox. Firefox dispone asimismo de overrides de respuestas en versiones recientes. No conviene justificar la elección diciendo que solo Chrome puede hacerlo.

    Estas observaciones se apoyan en las referencias oficiales R1–R5 y R9; la elección para el aula es una propuesta docente.

    TareaChromeFirefox
    Ver y modificar DOM y CSSElements o ElementosInspector
    Ejecutar JavaScript y leer erroresConsole o ConsolaConsola web
    Detener y recorrer JavaScriptSources o FuentesDebugger o Depurador
    Analizar HTTPNetwork o RedRed o Monitor de red
    Cookies y almacenamientoApplication o AplicaciónAlmacenamiento
    Editar y reenviarEdit and resend as fetch o copiar como fetchEdit and Resend o Editar y reenviar
    Sustituir respuestas localmenteLocal OverridesNetwork Overrides
    Medir ejecuciónPerformance o RendimientoRendimiento o herramientas de perfilado
    Inspeccionar accesibilidadPanel de accesibilidad del elementoInspector de accesibilidad
    Revisar HTTPSSecurity o Privacy and security, según versiónInformación de conexión y detalles de seguridad en red

    Si solo puedes instalar uno: Chrome simplifica seguir una única secuencia de instrucciones para el conjunto del taller. Si ya trabajáis con Firefox en Kali o Ubuntu: puedes impartir el núcleo completo con Firefox y utilizar la columna de equivalencias. No hace falta una edición Developer para los ejercicios.

    2 Objetivos y organización del taller

    Conocimientos previos

    Se recomienda conocer etiquetas HTML, selectores CSS, variables y funciones de JavaScript, JSON, URL, métodos GET y POST y la diferencia básica entre cliente y servidor. No necesitas saber programar Python: el servidor se entrega terminado y se ejecuta con un comando.

    Resultados observables

    Al completar el trabajo podrás localizar el elemento que genera una parte de la interfaz; explicar una petición a partir de método, URL, cabeceras y cuerpo; seguir el origen de un dato durante una pausa; distinguir un fallo visual de un fallo de autorización; demostrar una inyección DOM sin extraer información; y verificar controles de servidor, cookies, CSP, CORS y CSRF sin confundir su función.

    Normas de trabajo

    Las modificaciones y peticiones de las prácticas se dirigen al laboratorio local. Utiliza datos ficticios y un perfil de navegador para clase. No pegues código en la consola de tu sesión personal sin comprenderlo. Si el navegador muestra protección frente a self-XSS, lee cada instrucción y aplica el flujo que muestre tu versión solo después de entender el código; no desactives protecciones del navegador para completar el taller.

    DevTools no sustituye a un proxy de auditoría, a una revisión del servidor ni a una captura de red completa. Permite estudiar la actividad de la página y las respuestas que recibe; no permite leer el PHP, Python o Java que se ejecuta en el servidor ni observar tráfico de otros equipos.

    3 Preparación y arranque del laboratorio

    Contenido del paquete

    La carpeta laboratorio contiene el servidor Python, una página HTML, JavaScript, CSS y una página de segundo origen. La carpeta documentacion incluye esta guía y el solucionario del profesor. El servidor no utiliza bibliotecas externas, no requiere cuentas y almacena las sesiones en memoria.

    Arranque en Windows

    1. Comprueba Python con py -3 --version.
    2. Si no está instalado, prepara Python 3.10 o posterior antes del taller.
    3. Extrae el ZIP. No ejecutes el laboratorio dentro de la vista comprimida.
    4. Abre una terminal en la carpeta laboratorio.
    5. Ejecuta:
    py -3 servidor.py

    Arranque en macOS o Linux

    Comprueba python3 --version, sitúate en la carpeta laboratorio y ejecuta:

    python3 servidor.py

    Abre http://localhost:8000. El servidor también utiliza http://localhost:8001. Mantén la terminal abierta; Ctrl+C detiene ambos servicios. Si localhost se resuelve de forma problemática en un equipo, revísalo antes de clase: la guía y los controles de origen utilizan literalmente localhost.

    No uses file://, Live Server ni python3 -m http.server: solo servirían archivos y no implementarían las API del taller. No cambies un puerto aisladamente; existen referencias a ambos en el código y en las comprobaciones de seguridad.

    Comprobación inicial

    1. Pulsa Cargar catálogo. Deben aparecer dos productos.
    2. Pulsa Iniciar sesión. La respuesta debe indicar estado 200.
    3. Pulsa Consultar sesión. Deben aparecer usuario alumno, rol alumno y un token CSRF.
    4. Abre la página de segundo origen desde el enlace de navegación.
    5. Regresa a la tienda y abre DevTools.

    Credenciales ficticias: alumno / aula123. No existe cuenta administradora para el alumnado. Todas las compras son simuladas.

    Reinicio entre ejercicios

    Desactiva Offline, el bloqueo de peticiones y cualquier override. Limpia filtros de Network. Si has alterado archivos fuente, restaura la copia original del ZIP. Reinicia el servidor para borrar sesiones, contador y preferencias; las cookies y el almacenamiento del navegador no desaparecen por reiniciar Python. Puedes borrarlos desde DevTools y volver a iniciar sesión.

    El programa escucha únicamente en la interfaz de bucle local. Cada estudiante ejecuta su propia copia. Esta configuración evita depender de una red común y mantiene las prácticas reproducibles; no es un despliegue para Internet.

    4 Fundamentos de navegador y seguridad

    Qué se recibe y qué se ejecuta

    Al solicitar la página, el navegador recibe HTML. Después puede solicitar CSS, JavaScript y datos. El HTML se convierte en un árbol DOM; JavaScript puede modificarlo sin que cambie el archivo original del servidor. Por ello, Ver código fuente y Elements no tienen por qué mostrar lo mismo.

    El servidor recibe peticiones y decide qué responder. Un botón oculto puede impedir que alguien encuentre una función por casualidad, pero no impide que envíe una petición. Un campo type="hidden" participa en los formularios; su valor sigue siendo controlable por el usuario.

    Cuatro preguntas antes de declarar un fallo

    1. ¿Qué dato controlo: texto visible, atributo, parámetro, JSON, cookie o identificador?
    2. ¿Qué sistema consume ese dato: DOM, lógica JavaScript o servidor?
    3. ¿Qué efecto se ha confirmado: visualización, ejecución, lectura o modificación real?
    4. ¿Qué comprobación faltaba y en qué componente debe hacerse?

    Cambiar tu propia pantalla no demuestra acceso a datos ajenos. Leer una respuesta con un pedido de otro usuario sí puede demostrarlo. La evidencia debe llegar hasta el efecto que afirmas.

    Origen y site

    Un origen combina esquema, host y puerto. http://localhost:8000 y http://localhost:8001 son distintos orígenes. Cambiar solo la ruta mantiene el origen. Cambiar entre localhost y 127.0.0.1 también cambia el host y puede separar cookies y almacenamiento.

    El concepto de site utilizado por SameSite no equivale al origen: el puerto no lo separa. Los dos orígenes localhost del taller son same-site. Además, las cookies no se aíslan por puerto. El segundo servicio de este laboratorio es de confianza y comparte el entorno docente; no se simula aquí un dominio atacante totalmente independiente.

    Controles y alcance

    ControlQué aportaQué no permite concluir
    Validación HTMLMejor experiencia al introducir datosQue el servidor validará la petición
    AutenticaciónIdentificar una sesiónQue pueda leer cualquier objeto
    AutorizaciónDecidir qué puede hacer esa identidadQue cualquier dato del cliente sea fiable
    HttpOnlyImpedir lectura de la cookie mediante document.cookieQue no haya XSS o que la cookie no viaje por HTTP
    SecureRestringir el envío de la cookie a contextos HTTPS admitidosQue la sesión esté autorizada para una acción
    SameSiteLimitar envío de cookies según contexto de siteQue cubra todos los casos de CSRF
    CORSControlar qué orígenes pueden leer ciertas respuestas con JavaScriptQue una API tenga autenticación o que no reciba peticiones
    CSPRestringir recursos y ciertas formas de ejecuciónQue todo HTML insertado sea seguro
    HTTPSProteger el transporte y verificar el servidor mediante certificadosQue la aplicación no tenga IDOR o XSS

    5 Práctica 1 Inspeccionar HTML y CSS

    Objetivo: identificar qué cambios afectan al DOM y qué cambios llegan al servidor. Tiempo: 25 minutos.

    1. Abre la tienda y pulsa con el botón derecho sobre Figura astronauta. Elige Inspeccionar.
    2. Localiza <p id="precio" data-precio="25"> en Elements o Inspector.
    3. Edita el texto y escribe Figura astronauta · 1 €.
    4. En Styles añade temporalmente color: red y un tamaño de letra mayor.
    5. Desactiva una regla CSS y observa qué propiedad pasa a aplicarse. Revisa Computed para conocer el valor final, no solo la regla escrita.
    6. Localiza el botón Administración con id="admin" y elimina hidden del DOM. Ahora se ve un control que antes no se mostraba.
    7. Inicia sesión y pulsa el botón que acabas de revelar.
    8. Abre Network y revisa la respuesta de /api/admin.
    9. Recarga la página sin overrides activos.

    Resultado esperado: el precio y el estilo vuelven a su estado inicial. El botón revelado no da acceso: el servidor devuelve 403 a la sesión alumno. Si no has iniciado sesión devuelve 401.

    Interpretación: has modificado tu representación local. No has actualizado el catálogo del servidor ni conseguido autorización. El servidor del laboratorio rechaza el acceso administrativo aunque el control se vea.

    Evidencia: captura del elemento modificado y respuesta de la API administrativa. Explica por qué una captura del precio alterado no demuestra una compra por ese importe.

    Ampliación de desarrollo: inspecciona el modelo de caja de una sección. Identifica padding, borde y margen. Cambia el ancho de la ventana y explica qué regla @media entra en funcionamiento. Activa el estado :hover de un botón desde el inspector si tu versión lo ofrece.

    6 Práctica 2 Usar la consola con criterio

    Objetivo: consultar el DOM y trabajar con datos sin confundir la consola con el servidor. Tiempo: 25 minutos.

    Abre Console y ejecuta una instrucción cada vez:

    document.title
    location.origin
    document.querySelector('#precio').textContent
    document.querySelector('#precio').dataset.precio

    El último resultado es una cadena. Un atributo HTML no se convierte en número automáticamente.

    const precioLeido = document.querySelector('#precio').dataset.precio;
    console.log(typeof precioLeido);
    console.log(Number(precioLeido) * 2);
    console.table([{producto: 'Figura', precio: 25}, {producto: 'Robot', precio: 40}]);

    Cambia el texto con una operación explícita:

    document.querySelector('#precio').textContent = 'Texto de prueba';

    Consulta datos de la API sin depender de funciones internas de la página:

    fetch('/api/productos')
      .then(respuesta => respuesta.json())
      .then(datos => console.table(datos))
      .catch(error => console.error(error));

    Resultado esperado: se genera una petición que también aparece en Network. Console ejecuta JavaScript en el contexto seleccionado; no ejecuta Python en el servidor. Si hay iframes, comprueba el contexto antes de lanzar una prueba.

    Errores didácticos: una promesa pendiente no significa que no haya respuesta; espera su resolución. Un HTTP 403 no provoca por sí mismo que fetch rechace su promesa: para tratarlo como error de aplicación comprueba respuesta.ok o respuesta.status. Un error de red o CORS sí puede hacer que fetch rechace.

    fetch('/api/admin').then(async respuesta => {
      const datos = await respuesta.json();
      console.log('HTTP', respuesta.status, 'OK', respuesta.ok, datos);
    });

    Evidencia: anota el tipo de dato del precio y el estado HTTP de la petición administrativa. Recarga para limpiar los cambios del DOM. Si redeclaras una constante en una sesión de consola y aparece un error, cambia el nombre o recarga; el comportamiento de redeclaración puede variar entre herramientas.

    7 Práctica 3 Seguir una petición HTTP

    Objetivo: reconstruir una conversación entre navegador y API. Tiempo: 35 minutos.

    Preparar la captura

    Abre Network antes de pulsar botones. Limpia la lista, comprueba que la grabación está activa y selecciona Fetch/XHR. Activa Preserve log si vas a estudiar una navegación. Disable cache afecta a la caché del navegador mientras la herramienta aplica esa opción; no elimina por sí mismo cachés de servidor o de un service worker.

    Pulsa Cargar catálogo y selecciona /api/productos. Examina URL, método GET, estado 200, cabeceras de petición, cabeceras de respuesta, contenido JSON e iniciador. La pestaña Initiator o la traza equivalente ayuda a encontrar la línea de JavaScript que originó la petición.

    Comparar URL y cuerpo

    Ejecuta esta petición desde la consola:

    fetch('/api/eco?origen=consola', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({nombre: 'Ada', curso: 'ASIR'})
    }).then(r => r.json()).then(console.log);

    El parámetro origen=consola forma parte de la URL. nombre y curso están en el cuerpo JSON. En una petición real podrían registrarse por mecanismos distintos; no uses URLs para transportar contraseñas o tokens.

    Leer cabeceras sin mezclarlas

    Content-Type describe el formato del cuerpo correspondiente. Accept expresa formatos que admite el cliente. Cookie viaja en la petición cuando corresponde; Set-Cookie es una instrucción del servidor en la respuesta. El navegador controla cabeceras como Cookie y Origin en fetch; no puedes simular cualquier escenario cambiándolas como propiedades arbitrarias de JavaScript.

    La API del laboratorio devuelve Cache-Control: no-store. Esto pide no almacenar las respuestas en caché; no sustituye al control de acceso ni impide capturas o copias del contenido ya recibido.

    Analizar tiempos

    Pulsa Cargar con retraso y examina Timing. El servidor espera aproximadamente 800 ms antes de responder. El tiempo total también incluye planificación, transporte y procesamiento. Un TTFB alto es una pista para investigar, no una prueba automática de que la base de datos sea lenta.

    Exportar una evidencia

    En el menú contextual busca Copy as fetch o la opción equivalente; conserva la petición de ejemplo sin cookies ni credenciales. Si exportas HAR, revisa su contenido antes de entregarlo. Aunque la herramienta ofrezca una exportación saneada, parámetros, cuerpos, respuestas y datos de negocio pueden contener información sensible. En este taller todos los datos son ficticios.

    Entrega: una tabla con método, ruta, estado, datos enviados, datos recibidos e iniciador para catálogo, eco y petición administrativa. Adjunta un HAR mínimo opcional del laboratorio, revisado manualmente.

    8 Práctica 4 Depurar JavaScript

    Objetivo: localizar un fallo por tipos de datos siguiendo el flujo real. Tiempo: 35 minutos.

    1. Escribe 2 y 3 en los campos del cálculo y pulsa Sumar. Aparece 23.
    2. Abre Sources o Depurador y localiza app.js.
    3. Busca la función sumar y coloca un punto de ruptura en const resultado = a + b.
    4. Pulsa Sumar otra vez. La ejecución se detiene antes de ejecutar esa línea.
    5. Examina a y b en Scope. Añade typeof a, typeof b y Number(a) + Number(b) a Watch o inspección equivalente.
    6. Usa Step over para ejecutar una línea y comprueba el valor de resultado.
    7. Examina Call stack para identificar el manejador del clic.
    8. Reanuda la ejecución. Desactiva el punto de ruptura al terminar.

    Resultado esperado: ambos valores son cadenas porque se leen de .value. El operador + las concatena. La conversión numérica explícita da 5.

    Aplicar la corrección

    Edita el archivo original laboratorio/app.js con un editor y sustituye las tres líneas del cálculo por:

    const a = Number(document.querySelector('#a').value);
    const b = Number(document.querySelector('#b').value);
    const resultado = a + b;

    Guarda, recarga y comprueba 2 + 3 y 10 + 5. No hace falta reiniciar el servidor para volver a servir un archivo estático. Considera además el caso de campos vacíos: Number('') vale 0; si eso no cumple el requisito, valida el texto original antes de convertirlo.

    Puedes probar la corrección con un override en lugar de modificar el archivo. Anota qué método has usado: una edición temporal del depurador no equivale siempre a guardar el archivo del proyecto.

    Otras pausas útiles

    Activa una pausa XHR/fetch sobre una parte de la ruta /api/compra cuando tu navegador lo permita. También puedes usar una pausa de eventos de clic o de envío de formulario. Así encuentras el flujo sin conocer el nombre de la función. Una pausa DOM sobre cambios del subárbol de #salida ayuda a localizar quién actualiza la respuesta visible.

    Evidencia: valores y tipos durante la pausa, línea causante y resultado después de corregir. Relaciona esta técnica con seguridad: seguir un dato de entrada hasta un uso sensible permite decidir si se valida o se interpreta de forma peligrosa.

    9 Práctica 5 Saltar validaciones del cliente

    Objetivo: comprobar por qué las restricciones HTML necesitan validación de servidor. Tiempo: 25 minutos.

    Inicia sesión. En el campo Cantidad escribe 0 y pulsa Comprar. La validación del navegador impide el envío normal porque el mínimo es 1. Confirma en Network que no aparece una nueva compra.

    Inspecciona el campo y elimina temporalmente min y max. Envía cantidad 0. Esta vez llega una petición, pero el servidor devuelve 400. Haz otra prueba con cantidad 11 usando la consola:

    fetch('/api/compra?modo=seguro', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({producto: 1, cantidad: 11, precio: 25})
    }).then(async r => console.log(r.status, await r.json()));

    El servidor acepta únicamente enteros de 1 a 10. En el laboratorio este control existe incluso en la compra vulnerable; la vulnerabilidad de precio de la práctica siguiente es independiente.

    Preguntas: ¿quién impidió el primer intento? ¿Quién rechazó el segundo? ¿Cómo distingues la ausencia de envío de una respuesta 400? ¿Cambiar input a text obliga al servidor a aceptar cualquier dato?

    Resultado esperado: eliminar atributos cambia el comportamiento del formulario, pero no el contrato de la API. Las reglas del cliente ayudan a introducir datos; las del servidor establecen qué se acepta.

    Entrega: compara tres intentos con cantidad 0, 1 y 11, indicando si hubo petición y cuál fue el estado. No des por segura toda la aplicación porque una regla se valide correctamente.

    10 Práctica 6 Manipular una compra simulada

    Objetivo: detectar confianza indebida en un importe proporcionado por el cliente. Tiempo: 35 minutos.

    Establecer la referencia

    Inicia sesión y compra una unidad sin modificar nada. En Network observa POST /api/compra, el JSON enviado y el total devuelto. El producto 1 cuesta 25 y la cantidad es 1.

    Inspecciona #precioEnviado. Es un input oculto cuyo valor es 25. Cambia solo el texto visible del precio a 1 € y compra otra vez: el total sigue siendo 25 porque el campo enviado no ha cambiado. Ahora cambia el valor del input oculto a 1 y repite: la versión vulnerable devuelve total 1.

    Repetir una petición controlada

    En Firefox utiliza Editar y reenviar sobre la petición de compra. En Chrome utiliza Edit and resend as fetch o Copy as fetch y edita el cuerpo en la consola. Los nombres dependen de la versión; también puedes ejecutar directamente:

    fetch('/api/compra', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({producto: 1, cantidad: 2, precio: 1})
    }).then(async r => console.log(r.status, await r.json()));

    Se espera estado 200 y total 2. El laboratorio no crea pedidos persistentes ni pagos: el fallo confirmado es que el cálculo del servidor usa el precio enviado en esta simulación.

    Verificar la corrección

    Repite exactamente el cuerpo contra /api/compra?modo=seguro.

    fetch('/api/compra?modo=seguro', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({producto: 1, cantidad: 2, precio: 1})
    }).then(async r => console.log(r.status, await r.json()));

    Ahora el total debe ser 50: el servidor toma el precio del catálogo. Esta corrección no consiste en ocultar mejor el input ni en ofuscar JavaScript. En una aplicación real también comprobaría promociones, impuestos, stock y condiciones de la operación en el servidor.

    Evidencia: cuerpo y respuesta de ambas variantes. En el informe escribe “el endpoint vulnerable calcula la simulación con un importe controlado por el cliente”. Evita afirmar que has modificado una base de datos o ejecutado un pago que este laboratorio no implementa.

    11 Práctica 7 Examinar almacenamiento y cookies

    Objetivo: distinguir persistencia, contexto de acceso y atributos de cookies. Tiempo: 35 minutos.

    Local Storage y Session Storage

    Abre Application > Local Storage en Chrome o Almacenamiento > Almacenamiento local en Firefox. Selecciona el origen de la tienda. Debe existir rol_demo=alumno. En Session Storage verás visita_demo=activa.

    localStorage.getItem('rol_demo');
    sessionStorage.getItem('visita_demo');
    localStorage.setItem('rol_demo', 'admin');

    Consulta ahora /api/me y /api/admin. El rol del servidor sigue siendo alumno y el acceso administrativo continúa denegado. La clave de almacenamiento es únicamente una demostración local y nunca participa en la autorización del servidor.

    Local Storage persiste más allá de una recarga y normalmente de una sesión del navegador hasta que se elimina. Session Storage se asocia al origen y a la sesión de la pestaña; su comportamiento al duplicar o restaurar pestañas tiene matices. Comprueba recarga y cierre de una pestaña, sin asumir que toda apertura desde otra pestaña parte necesariamente de un almacén vacío. Ninguno de estos mecanismos es un almacén de secretos frente a JavaScript del mismo origen.

    Analizar las cookies de aula

    Inicia sesión y abre el apartado Cookies. Localiza sesion y preferencia. Revisa nombre, valor, dominio o host, ruta, HttpOnly, Secure y SameSite.

    Ejecuta:

    document.cookie

    Debe aparecer preferencia, pero no sesion. La cookie sesion es HttpOnly y la API document.cookie no la expone. Eso no significa que el propietario del navegador no pueda verla en DevTools. Tampoco impide que el navegador la adjunte a peticiones donde corresponda.

    Pulsa Consultar sesión y revisa la cabecera Cookie de la petición. En la respuesta de login observa los dos Set-Cookie. No confundas la visibilidad de la cookie en la interfaz con la posibilidad de leerla desde un script de la página.

    Restricción del laboratorio HTTP

    El taller utiliza HTTP local y no configura Secure en sus cookies. Esto facilita observarlas, pero no constituye una recomendación para una sesión en producción. En un servicio HTTPS se debe evaluar una configuración de sesión como:

    Set-Cookie: sesion=VALOR_ALEATORIO; Path=/; HttpOnly; Secure; SameSite=Lax

    La política SameSite debe elegirse según el flujo de la aplicación. No añadas SameSite=None como arreglo genérico: requiere Secure y tiene implicaciones de envío entre sites. No configures Domain si no necesitas compartir la cookie con subdominios. Los puertos no aíslan cookies.

    Comprobar la sesión en el servidor

    Cierra sesión mediante el botón y consulta /api/me: debe devolver 401. El servidor invalida el identificador además de pedir que se elimine la cookie. Borrar solo el almacenamiento local o cambiar una etiqueta de la interfaz no equivale a revocar una sesión del servidor.

    Entrega: tabla comparativa entre Local Storage, Session Storage y cookies, con persistencia, acceso JavaScript y envío automático al servidor. No incluyas valores reales de cookies en informes fuera del laboratorio.

    12 Práctica 8 Comprobar autorización e IDOR

    Objetivo: reconocer acceso indebido a objetos aunque exista autenticación. Tiempo: 30 minutos.

    IDOR describe referencias directas a objetos cuyo acceso no se comprueba correctamente. En API también se habla de fallos de autorización a nivel de objeto. Un identificador predecible ayuda a localizar un objeto, pero el fallo central es la ausencia de control de permisos sobre él.

    Inicia sesión y consulta el pedido propio:

    fetch('/api/pedido?id=1001').then(r => r.json()).then(console.log);

    La respuesta identifica como propietario a alumno. Cambia el identificador al segundo pedido ficticio:

    fetch('/api/pedido?id=1002').then(async r => console.log(r.status, await r.json()));

    La variante vulnerable devuelve estado 200 y propietario otra_persona. El servidor ha comprobado que hay una sesión, pero no que le corresponda ese pedido.

    Repite ambas consultas con modo seguro:

    fetch('/api/pedido?id=1001&modo=seguro')
      .then(async r => console.log(r.status, await r.json()));
    fetch('/api/pedido?id=1002&modo=seguro')
      .then(async r => console.log(r.status, await r.json()));

    Se esperan 200 para el propio y 403 para el ajeno. En una aplicación real podría elegirse 404 para ocultar la existencia de un recurso; la respuesta concreta depende del diseño. El requisito es impedir el acceso.

    Corrección razonada: el servidor obtiene la identidad de la sesión y compara propiedad o permisos del objeto. No debe confiar en un usuario enviado por JSON, un rol en Local Storage o un campo oculto. Sustituir números por UUID puede dificultar adivinar referencias, pero no sustituye la autorización.

    Evidencia: cuatro respuestas y una frase que separe autenticación de autorización. No necesitas recorrer rangos de identificadores: los dos casos conocidos bastan para demostrar el comportamiento docente.

    13 Práctica 9 Encontrar y corregir un DOM XSS

    Objetivo: seguir una entrada controlada hasta un punto que interpreta HTML. Tiempo: 40 minutos.

    Empezar por la interpretación de HTML

    En Saludo introduce Ada. Después prueba <strong>Ada</strong>. En la versión vulnerable se mostrará en negrita. Esto demuestra que el contenido se interpreta como HTML; aún no demuestra ejecución de JavaScript.

    En Sources localiza la lectura del parámetro q y la asignación a #saludo. El flujo es:

    • Fuente de entrada: parámetro q de la URL.
    • Lectura: URLSearchParams.
    • Destino: propiedad innerHTML del elemento.
    • Consecuencia: el navegador interpreta el texto como marcado.

    El servidor entrega el HTML estático. La inyección se produce al renderizar en el navegador, por eso aquí se trata de DOM XSS.

    Demostración inocua de ejecución

    En el formulario de la página principal pega este contenido y pulsa Mostrar:

    <img src="/imagen-inexistente" onerror="document.getElementById('marca').textContent='XSS demostrado en el laboratorio'">

    La imagen inexistente provoca un error y el manejador modifica una marca visible. No extrae cookies, no envía datos fuera del equipo y no solicita recursos externos. En Network verás el 404 de la imagen; en Elements podrás encontrar el atributo onerror.

    Resultado esperado: el texto Marca XSS sin activar cambia a XSS demostrado en el laboratorio. Has probado ejecución en el contexto de la página. No has demostrado acceso a archivos del sistema ni ejecución en el servidor.

    Un <script> insertado mediante innerHTML no se ejecuta de la misma forma que durante el parseado inicial del documento. Si esa prueba no produce nada, no concluyas que innerHTML es seguro: existen otros vectores, como el manejador del ejemplo.

    Comprobar el uso de texto

    Prueba de nuevo conservando la entrada y añadiendo modo=seguro a la URL. Para evitar errores al codificarla puedes preparar la navegación desde Console:

    const pruebaDOM = new URL(location.href);
    pruebaDOM.searchParams.set('modo', 'seguro');
    location.href = pruebaDOM.href;

    Ahora el contenido debe verse literalmente y la marca no cambia. La variante segura utiliza textContent. Si pulsas Mostrar en el formulario se generará una URL nueva que solo contiene q: comprueba de nuevo el parámetro modo o usa el botón Mostrar entrada como texto para comparar la representación actual. Después de una ejecución ya producida, cambiar a texto no deshace los efectos anteriores: recarga en modo seguro para verificar desde cero.

    Qué corregir en una aplicación real

    Cuando el requisito sea mostrar texto, utiliza textContent o APIs de creación de nodos. Si necesitas permitir HTML, define una política y sanitiza con una solución mantenida adecuada al contexto. No construyas un filtro artesanal basado en borrar la palabra script. Un escape válido para texto HTML no sirve automáticamente para una URL, un atributo o código JavaScript.

    CSP añade restricciones útiles, pero corregir el punto de inserción sigue siendo necesario. HttpOnly reduce la exposición de ciertas cookies; un XSS todavía podría ejecutar acciones con la sesión o leer datos a los que acceda la página.

    Entrega: identifica fuente y destino, captura la marca antes y después, y demuestra la misma entrada como texto en la variante segura.

    14 Práctica 10 Evaluar CSP y protección frente a marcos

    Objetivo: interpretar una política entregada por el servidor y verificar su alcance. Tiempo: 30 minutos.

    Comparación con y sin política

    Repite la entrada XSS anterior en /csp.html. Esta página utiliza el mismo JavaScript vulnerable, pero la respuesta del documento incluye Content-Security-Policy. La política permite scripts del propio origen y no permite manejadores inline como onerror.

    En Network selecciona el documento csp.html, abre Response Headers y lee Content-Security-Policy. Después revisa Console. Debe aparecer una infracción de política y la marca XSS no debe cambiar. El elemento img sí puede haberse insertado: impedir ese manejador no equivale a sanitizar el HTML.

    La política del laboratorio es:

    Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self' http://localhost:8001; object-src 'none'; base-uri 'none'; frame-ancestors 'none'

    script-src 'self' permite cargar app.js del mismo origen. connect-src controla conexiones como fetch. object-src 'none' impide objetos embebidos. base-uri 'none' limita el uso de base. frame-ancestors 'none' impide que otros documentos enmarquen la página. Esta última directiva no hereda default-src y debe enviarse como cabecera; una etiqueta meta no la sustituye.

    Esta política es didáctica, no una receta universal. Un despliegue real debe inventariar recursos, decidir nonces o hashes según su arquitectura y probar las rutas necesarias. No añadas unsafe-inline solo para silenciar el mensaje: estarías relajando precisamente el control que se está demostrando.

    Report Only

    Repite la misma entrada en /reporte.html. La cabecera es Content-Security-Policy-Report-Only. El navegador informa de la infracción, pero la política no bloquea la ejecución. La marca debe cambiar. El laboratorio no configura un colector de reportes; la observación se realiza en la consola.

    Conclusión: la presencia del nombre de una cabecera no demuestra que esté aplicando bloqueo. Hay que comprobar el tipo de política y el efecto observado.

    Marcos y clickjacking

    Abre http://localhost:8001/marco.html. Encontrarás dos marcos: uno intenta cargar una página sin protección y otro una con CSP frame-ancestors none y X-Frame-Options DENY. El primero debe cargar; el segundo debe bloquearse y generar un mensaje.

    La demostración prueba protección frente al enmarcado de estos documentos. No implementa una interfaz engañosa de clickjacking. En una aplicación real valorarías qué páginas necesitan ser embebidas legítimamente antes de elegir el control.

    Entrega: tabla con página, cabecera, resultado del XSS y resultado del marco cuando corresponda. Desactiva overrides antes de medir la política que entrega realmente el servidor.

    15 Práctica 11 Entender CORS y el preflight

    Objetivo: separar envío, lectura y autorización entre orígenes. Tiempo: 40 minutos.

    Realiza los comandos desde la consola de http://localhost:8000. No desde una pestaña vacía ni desde el segundo origen. Trabaja en la página principal, que no impone CSP.

    Leer sin autorización CORS

    fetch('http://localhost:8001/api/publico')
      .then(r => r.json()).then(console.log).catch(console.error);

    El segundo servidor recibe la petición y responde, pero no autoriza que este origen lea la respuesta mediante JavaScript. Console muestra un error CORS. Según el navegador, Network puede mostrar estado 200, estado bloqueado o una combinación de datos y advertencias. El registro del servidor permite confirmar que llegó el GET.

    Abre la misma URL directamente en una pestaña: puedes ver el JSON. Una navegación y una lectura con fetch desde otro origen son operaciones distintas.

    Autorizar una lectura pública concreta

    fetch('http://localhost:8001/api/publico?cors=si')
      .then(r => r.json()).then(console.log).catch(console.error);

    Ahora la respuesta incluye Access-Control-Allow-Origin: http://localhost:8000 y JavaScript puede leerla. El endpoint contiene solo un mensaje público, sin credenciales ni datos privados. No confundas este permiso de lectura con un control de acceso al servidor.

    Observar una petición previa OPTIONS

    fetch('http://localhost:8001/api/publico?cors=si', {
      method: 'POST',
      headers: {'Content-Type': 'application/json', 'X-Taller': 'devtools'},
      body: JSON.stringify({prueba: true})
    }).then(r => r.json()).then(console.log).catch(console.error);

    El tipo JSON y la cabecera personalizada requieren preflight. En Network observa OPTIONS y después POST. La respuesta al OPTIONS permite el origen, el método y las cabeceras utilizadas. Quita cors=si y repite: el servidor rechaza el preflight y el POST no debería enviarse. Una caché de preflight puede reducir OPTIONS en pruebas repetidas; borra la sesión de pruebas o cambia el escenario de forma controlada antes de interpretar su ausencia.

    Demostrar una escritura aunque no se pueda leer

    Antes, abre http://localhost:8001/api/contador en otra pestaña y anota el valor. Desde la consola de la tienda ejecuta:

    fetch('http://localhost:8001/api/registro', {
      method: 'POST',
      headers: {'Content-Type': 'application/x-www-form-urlencoded'},
      body: 'evento=prueba_cors'
    }).then(r => r.json()).then(console.log).catch(console.error);

    El POST utiliza método, cabeceras y tipo de contenido que no requieren preflight en este caso. La respuesta no autoriza su lectura mediante CORS y fetch falla al exponerla. Sin embargo, al recargar la pestaña del contador verás que ha aumentado. El servidor sí procesó el envío.

    No utilices mode: 'no-cors' como arreglo general. La respuesta será opaca y no podrás leer su cuerpo desde JavaScript. Ese modo no proporciona autenticación ni hace desaparecer el modelo de seguridad.

    En escenarios con cookies entre orígenes hay requisitos adicionales: credentials, autorización explícita del origen, Access-Control-Allow-Credentials y las reglas de cookies y SameSite que correspondan. El comodín * no autoriza una lectura con credenciales. Esta práctica pública evita esas variables para centrarse en envío y lectura.

    Entrega: compara cuatro casos: GET sin permiso, GET permitido, POST JSON con OPTIONS y POST de formulario cuyo efecto ocurre aunque la lectura se bloquee.

    16 Práctica 12 Reproducir un CSRF acotado

    Objetivo: observar una acción iniciada desde otro origen con una sesión existente. Tiempo: 40 minutos.

    CSRF consiste en inducir acciones que usan credenciales del usuario sin que la aplicación haya comprobado adecuadamente la intención de esa petición. El taller modifica solo una preferencia visual ficticia.

    Establecer la sesión

    1. Vuelve a http://localhost:8000 e inicia sesión.
    2. Consulta /api/me con el botón. Anota el tema, inicialmente claro.
    3. Abre http://localhost:8001/csrf.html en otra pestaña.
    4. Abre también DevTools en esta segunda pestaña y activa Preserve log en Network.
    5. Pulsa Enviar cambio desde otro origen.
    6. Observa el POST a http://localhost:8000/api/preferencia y la respuesta en el marco.
    7. Regresa a la tienda y pulsa Consultar sesión.

    Resultado esperado: la preferencia pasa a oscuro. El formulario ha enviado una acción con la cookie de sesión disponible. No ha necesitado leer el token CSRF ni la respuesta con fetch.

    Qué demuestra este montaje

    Los puertos distintos crean orígenes distintos, pero siguen siendo same-site y las cookies no se aíslan por puerto. La cookie SameSite=Lax no impide este caso same-site. El segundo servidor es parte del laboratorio; esta simulación no reproduce todas las condiciones de un dominio atacante independiente. Sirve para estudiar controles de origen y de intención sin depender de DNS, HTTPS o excepciones locales.

    Comprobar la variante protegida

    En la página de segundo origen pulsa Repetir sin token contra versión segura. El endpoint /api/preferencia?modo=seguro exige Origin igual al origen de la tienda y un token vinculado a la sesión. La respuesta debe ser 403 y el tema no debe cambiar.

    Desde la consola de la tienda realiza una acción legítima:

    fetch('/api/me').then(r => r.json()).then(sesion => {
      return fetch('/api/preferencia?modo=seguro', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-CSRF-Token': sesion.csrf
        },
        body: JSON.stringify({tema: 'claro'})
      });
    }).then(async r => console.log(r.status, await r.json()));

    Debe devolver 200 y guardar claro. El navegador añade Origin al POST; no lo establezcas manualmente desde fetch.

    Interpretación y corrección

    Para corregir una aplicación real evalúa tokens CSRF de sesión, validación de Origin y políticas SameSite adecuadas; evita efectos de estado con GET. Los controles deben encajar con los clientes y flujos legítimos. La variante docente exige ambos controles para poder verificarlos de forma visible.

    CORS no sustituye a estos controles. Un formulario puede enviar determinadas acciones sin que su origen pueda leer la respuesta mediante JavaScript. Un XSS en el origen autorizado podría obtener el token de esta demostración y actuar como la página: los controles se complementan.

    Evidencia: tema antes y después, Origin de la petición, 403 del formulario protegido y 200 de la acción legítima. No incluyas tokens reales en el informe.

    17 Práctica 13 Analizar archivos y datos expuestos

    Objetivo: distinguir configuración pública de información que debería permanecer en el servidor. Tiempo: 20 minutos.

    1. Abre Sources y busca clave_demo en los recursos cargados. En Chrome suele estar disponible la búsqueda global con Ctrl+Shift+F o Cmd+Option+F; en Firefox busca la opción de búsqueda en todos los archivos del depurador. Si el atajo cambia, usa el menú.
    2. Localiza configuracionPublica en app.js.
    3. Consulta /api/config y revisa su JSON.
    4. Busca usuario, rol, rutas de API y posibles comentarios de depuración.

    El valor PUBLICA_NO_ES_UN_SECRETO es una marca ficticia. El ejercicio muestra que un valor enviado al navegador deja de ser secreto frente a ese usuario. No demuestra que esta marca conceda acceso a un servicio real.

    En una aplicación real clasifica el valor: una URL pública, un identificador público o una clave diseñada para uso público no equivalen a una contraseña privada. Determina qué permisos concede, dónde puede utilizarse y qué restricciones existen antes de valorar el impacto. Una clave con permisos de servidor nunca debe confiar su confidencialidad a un bundle JavaScript.

    Source maps y código minimizado

    El formateo Pretty print hace más legible un archivo minimizado; no recupera por sí solo el código fuente original. Un source map puede asociar código generado con fuentes originales si estas están disponibles o embebidas. El laboratorio entrega app.js legible y no incluye un mapa: no verás un archivo .map de ejemplo salvo que añadas tu propio proyecto compilado.

    Puedes ampliar la actividad con una aplicación propia Angular o similar. Revisa si sus mapas contienen código o comentarios que no querías publicar. Publicar mapas no es automáticamente una vulnerabilidad; el problema es qué información exponen y qué permisos o capacidad permiten obtener. No introduzcas secretos en el frontend aunque se desactiven los mapas.

    Datos de más en una respuesta

    En una API propia, compara el JSON recibido con los campos que realmente usa la pantalla. Ocultar una propiedad con CSS o no pintarla no evita que el usuario lea la respuesta. El servidor debe seleccionar los campos que puede recibir esa identidad.

    Entrega: una tabla con dato encontrado, ubicación, si es público por diseño, permiso que concede y corrección si corresponde. No declares una fuga de credenciales por la mera presencia de la palabra key.

    18 Práctica 14 Simular fallos con bloqueos y overrides

    Objetivo: probar la interfaz cuando faltan recursos y entender la sustitución local. Tiempo: 25 minutos.

    Bloquear una petición

    En Network selecciona /api/productos. Busca la opción para bloquear esa URL o patrón de petición. En algunas versiones de Chrome estas funciones aparecen en Request blocking o Request conditions; usa la opción equivalente disponible. Firefox también permite bloquear solicitudes desde el monitor de red.

    Pulsa Cargar catálogo y revisa Console y Network. La página puede mostrar un error poco útil o mantener datos antiguos: eso es un problema de experiencia y gestión de errores que debes describir. Distingue un bloqueo impuesto por tu herramienta de una respuesta 500 enviada por el servidor.

    Desactiva el bloqueo y vuelve a comprobar que carga. Una configuración olvidada puede hacer que el resto del taller parezca roto.

    Sustituir una respuesta

    En Chrome usa Override content o Local Overrides sobre el GET de catálogo. Autoriza una carpeta vacía de pruebas y guarda una respuesta JSON como esta:

    [{"id": 1, "nombre": "Figura de prueba local", "precio": 999}]

    Repite la carga y busca el indicador de respuesta sustituida. En Firefox utiliza Override response o la función equivalente y selecciona un archivo JSON local; consulta R5 para el flujo de tu versión. Si el aula utiliza una versión antigua sin la función, realiza este apartado con Chrome.

    El catálogo mostrado cambia en ese navegador, pero el catálogo del servidor no cambia. En una compra segura el servidor sigue tomando el precio 25 del producto 1. Para una comprobación limpia desactiva primero el override y vuelve a pedir catálogo, o utiliza otro navegador sin esa configuración.

    Los overrides ayudan a probar errores, datos límite y estados de interfaz antes de que exista un backend completo. No deben presentarse como prueba de que se ha alterado una respuesta del servidor para el resto de usuarios. Algunas cabeceras o respuestas tienen restricciones de sustitución; comprueba el indicador y el comportamiento en tu versión.

    Entrega: respuesta original, respuesta local, configuración usada y comprobación de restauración. Elimina los overrides al terminar.

    19 Práctica 15 Rendimiento accesibilidad y adaptación

    Objetivo: relacionar la calidad de desarrollo con evidencias observables. Tiempo: 25 minutos.

    Red lenta y desconexión

    Selecciona una simulación de red lenta en Network. Carga catálogo y observa cuánto tarda y si hay indicación de espera. Activa Offline y repite. Un sitio bien diseñado debe comunicar el problema, permitir reintentar y evitar operaciones duplicadas. El laboratorio permite observar un manejo básico que convendría mejorar.

    Restaura No throttling o Sin limitación y conexión normal. Si la velocidad no cambia en una petición local, comprueba el preset y la caché; la simulación no reproduce de forma perfecta todas las propiedades de una red real.

    Medir ejecución en el hilo principal

    Abre Performance o la herramienta de perfilado equivalente. Inicia una grabación breve, pulsa Bloquear el hilo durante 250 ms y detén la grabación. Busca la tarea asociada al manejador del clic. El bucle consume aproximadamente ese tiempo y puede retrasar respuesta visual y entrada del usuario.

    La duración está acotada para la demostración. No hace falta usar bucles ilimitados ni saturar CPU. En una aplicación real podrías dividir trabajo, reducir su coste o mover tareas apropiadas a un worker. La medición debe compararse bajo condiciones similares, sin generalizar una única traza a todos los dispositivos.

    Accesibilidad y adaptación

    Activa la vista de dispositivo o diseño adaptable. Prueba aproximadamente 390 px de ancho y después un tamaño de escritorio. Comprueba desbordamientos, legibilidad de campos y controles. Esta emulación no reproduce todo un teléfono real, su teclado, sus sensores o su rendimiento.

    Selecciona un input y revisa el nombre accesible: debe estar relacionado con una etiqueta. Navega usando Tab y activa un botón con teclado. El atributo aria-live de la salida intenta anunciar actualizaciones, pero un resultado concreto depende de tecnología asistiva y contexto. La inspección ayuda a detectar problemas; no equivale a una auditoría completa ni sustituye probar con usuarios y lectores de pantalla.

    Entrega: una mejora de manejo de error, una observación de rendimiento con traza y una revisión de navegación por teclado. Si propones Lighthouse como ampliación, ejecútalo en un contexto de tu proyecto que lo admita; su puntuación no certifica la seguridad ni detecta por sí sola un IDOR.

    20 Ampliación HTTPS y controles de despliegue

    Este bloque es observacional. El laboratorio local utiliza HTTP, no entrega certificados TLS y no demuestra contenido mixto real. Para estudiar transporte utiliza una aplicación HTTPS propia del profesor con autorización y datos ficticios, o limita la actividad a observar la información pública de conexión de una página sin enviar pruebas activas.

    Guion de observación

    1. Abre el panel Security o Privacy and security de Chrome, según la versión. Si no aparece, usa Más herramientas o el menú de comandos.
    2. En Firefox examina la información de conexión y la sección de seguridad de una petición HTTPS cuando esté disponible.
    3. Identifica protocolo de la URL, host, emisor del certificado, periodo de validez y nombres cubiertos.
    4. Revisa si existen recursos solicitados por HTTP desde el documento HTTPS.
    5. Comprueba las advertencias del navegador y la URL realmente solicitada: puede haber bloqueo o actualización automática de recursos, no una carga insegura efectiva.

    HTTPS protege el transporte; Network puede mostrar los datos descifrados porque el navegador es uno de los extremos. Que DevTools permita leer la respuesta no demuestra que cualquiera en la red pueda hacerlo.

    Revisión razonada de cabeceras

    CabeceraPregunta de revisión
    Content-Security-Policy¿Restringe los recursos necesarios y bloquea lo que pretende bloquear?
    Content-Security-Policy-Report-Only¿Solo informa o se esperaba que aplicase bloqueo?
    Strict-Transport-Security¿Existe una política HTTPS adecuada al dominio y a sus subdominios?
    X-Content-Type-Options¿Se evita interpretar contenido con un tipo distinto del declarado?
    Referrer-Policy¿Se limita la información de referencia de acuerdo con los flujos del sitio?
    Permissions-Policy¿Se limitan funciones del navegador que la aplicación no necesita?
    Cache-Control¿Se evita almacenar respuestas sensibles donde no deben quedar?
    CSP frame-ancestors o X-Frame-Options¿El documento puede ser enmarcado cuando no debería?

    HSTS se aprende por conexiones HTTPS válidas, con los matices de preload; añadirlo a la respuesta HTTP del taller no demuestra su protección. No utilices includeSubDomains o preload sin comprobar el impacto en todos los servicios implicados. La ausencia de una cabecera concreta es una observación para analizar en contexto; no basta para declarar una vulnerabilidad crítica.

    21 Reto final y evidencias

    Situación

    El equipo de Loot Lab quiere publicar una tienda y te pide una revisión previa. Dispones del laboratorio y de 55 minutos. Debes documentar tres hallazgos confirmados y dos controles que funcionan. Puedes usar esta guía y fuentes oficiales.

    Selecciona hallazgos diferentes: precio confiado al cliente, acceso a un pedido ajeno, DOM XSS o cambio de preferencia sin control de intención. Como controles válidos puedes verificar la compra segura, la autorización del pedido seguro, el rechazo administrativo, la variante CSRF protegida o CSP aplicada. No contabilices dos veces la misma causa porque cambies de navegador.

    Plantilla para cada hallazgo

    Identificador y título:
    URL y variante examinada:
    Precondiciones y sesión utilizada:
    Pasos mínimos de reproducción:
    Datos enviados:
    Respuesta o cambio observado:
    Impacto demostrado en el laboratorio:
    Límite de la evidencia:
    Causa en cliente o servidor:
    Corrección propuesta:
    Prueba de la variante corregida:

    Una evidencia útil permite a otra persona repetir el caso. Incluye la petición y la respuesta relevantes, no veinte capturas sin contexto. Oculta cookies y tokens si has reutilizado la plantilla en otro entorno. Separa observación de interpretación.

    Ejemplo de redacción

    “El endpoint de compra vulnerable devuelve total 2 cuando una sesión alumno envía producto 1, cantidad 2 y precio 1. El servidor usa el importe proporcionado por el cliente en esta simulación. La variante segura devuelve total 50 para el mismo cuerpo al obtener el precio del catálogo. El taller no persiste pedidos ni procesa pagos.”

    Distribución por parejas

    Una persona reproduce y la otra toma notas y verifica las respuestas. Intercambiad funciones después del primer hallazgo. Al terminar, una tercera pareja debe poder repetir al menos un caso siguiendo únicamente el informe.

    22 Prueba competencial y rúbrica

    Parte en papel sin recursos

    Tiempo recomendado de 15 minutos. La finalidad es comprobar comprensión de la base.

    1. Cambias un precio con Elements. ¿Qué has modificado y qué no puedes afirmar todavía?
    2. Explica la diferencia entre Cookie y Set-Cookie.
    3. ¿Por qué una cookie HttpOnly puede verse en DevTools y no en document.cookie?
    4. Un alumno autenticado obtiene un pedido ajeno cambiando su id. ¿Qué comprobación falta?
    5. ¿Por qué dos inputs con 2 y 3 pueden producir 23 en JavaScript?
    6. Un fetch falla por CORS. ¿Permite afirmar que el servidor no recibió nada? Justifica.
    7. Explica una diferencia entre CSP aplicada y Report Only.
    8. ¿Por qué ocultar un botón de administración no constituye autorización?
    9. En el taller, ¿8000 y 8001 son el mismo origen? ¿Son necesariamente distintos sites?
    10. Propón una forma de mostrar una entrada de usuario como texto sin interpretarla como HTML.

    Parte práctica con recursos

    Asigna una de estas variantes por alumno o pareja: compra manipulada, pedido ajeno, XSS, comparación de CSP, CORS con contador o preferencia CSRF. Debe reproducir el caso, explicar su causa y comprobar una corrección o un control relacionado. No basta con copiar un comando si no puede explicar qué demuestra.

    Rúbrica de observación

    CriterioBásicoAdecuadoConsolidado
    Manejo de herramientasNecesita guía continuaSelecciona paneles y filtros apropiadosCombina red y depuración para seguir el flujo
    EvidenciaSolo muestra una pantallaIncluye petición o cambio y resultadoPermite reproducción independiente y controla el estado
    Comprensión de seguridadConfunde interfaz y servidorDistingue el control ausenteExplica alcance y límites sin exagerar
    CorrecciónPropone ocultar o bloquear en el clienteSitúa el control en el componente correctoVerifica caso legítimo y caso rechazado
    ComunicaciónDescribe acciones sueltasRelaciona causa con efectoRedacta un hallazgo concreto con verificación

    Puedes utilizarla como lista de seguimiento sin calificación numérica. Si necesitas nota, asigna 0, 1 o 2 por criterio y explica de antemano cómo se interpreta cada nivel.

    23 Problemas frecuentes y glosario

    Resolución de incidencias

    SíntomaComprobación y solución
    Aparece HTML pero no funcionan las APIComprueba que arrancaste servidor.py y abriste localhost:8000, no file o Live Server
    No se puede abrir el puertoCierra la aplicación que ocupa 8000 o 8001 y vuelve a arrancar
    API devuelve 401Inicia sesión de nuevo; reiniciar Python invalida las sesiones previas
    El botón administrativo devuelve 403Es el resultado esperado para alumno
    El catálogo no cargaDesactiva Offline, bloqueos y overrides, limpia filtros y mira Console
    No veo la peticiónAbre Network antes de pulsar, activa grabación y elimina filtros
    La prueba XSS no se ejecutaComprueba q, la variante de la página y Console; csp.html la bloquea por diseño
    La marca sigue activada al pulsar texto seguroEsa acción no deshace una ejecución anterior; recarga con modo=seguro
    La consola muestra una promesaEspera o usa then; la petición es asíncrona
    No puedo definir Origin o Cookie en fetchSon cabeceras controladas por el navegador; analiza las que envía realmente
    El CSRF devuelve 401Inicia sesión en localhost, sin mezclarlo con 127.0.0.1
    Dos puertos comparten cookiesEs esperable: las cookies no se aíslan por puerto
    Un POST CORS parece fallar pero el contador subeEl envío se procesó y la lectura de respuesta se bloqueó
    Mis cambios persisten tras recargarRevisa si hay un override o si editaste el archivo original
    Firefox no muestra una función del ejemploRevisa la versión, el menú equivalente o utiliza Chrome para ese apartado

    Glosario breve

    DOM: representación del documento que puede modificar JavaScript. Breakpoint: punto en el que se detiene la ejecución para inspeccionarla. Payload: datos de una petición; su significado depende del contexto. Endpoint: ruta que ofrece una operación o recurso. Fetch: API del navegador para realizar solicitudes. HAR: formato de registro de actividad HTTP. Override: sustitución local de una respuesta o recurso para pruebas. XSS: ejecución de contenido de script no deseado en el contexto de una aplicación. IDOR: acceso a objetos por referencias sin autorización adecuada. CSRF: acción inducida que aprovecha credenciales disponibles sin una comprobación adecuada de intención. CORS: mecanismo de autorización de lectura entre orígenes para determinadas operaciones del navegador. CSP: política de restricciones de carga y ejecución entregada al navegador. Source map: correspondencia entre código generado y fuentes originales. Self-XSS: engaño que induce a una persona a ejecutar código contra su propia sesión, a menudo pegándolo en la consola.

    24 Referencias oficiales

    Las prácticas, el código y las situaciones de Loot Lab son material docente original. Estas referencias fundamentan el comportamiento de las herramientas y los controles. Consulta la documentación de tu versión si cambia un menú. Fecha de consulta: 20 de octubre de 2026.

    • R1 Chrome DevTools Overview. Paneles, DOM, consola y depuración. https://developer.chrome.com/docs/devtools/overview
    • R2 Chrome Network reference. Captura, filtros, reenvío, exportación y condiciones de red. https://developer.chrome.com/docs/devtools/network/reference
    • R3 Chrome Breakpoints. Tipos de pausas y depuración. https://developer.chrome.com/docs/devtools/javascript/breakpoints
    • R4 Firefox Network request details. Cabeceras, cuerpos y edición de peticiones. https://firefox-source-docs.mozilla.org/devtools-user/network_monitor/request_details/
    • R5 Firefox Network Overrides. Sustitución local de respuestas; disponible desde Firefox 137. https://firefox-source-docs.mozilla.org/devtools-user/network_monitor/network_overrides/index.html
    • R6 MDN Same origin policy. Límites entre orígenes. https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
    • R7 MDN CORS. Autorización de lectura, preflight y credenciales. https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
    • R8 MDN Set Cookie. Atributos y reglas de cookies. https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie
    • R9 Chrome Local Overrides. Sustitución local de contenido y cabeceras. https://developer.chrome.com/docs/devtools/overrides
    • R10 MDN Cross site scripting. Contextos de inyección y defensas. https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/XSS
    • R11 MDN Content Security Policy. Política de carga y ejecución. https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
    • R12 MDN Content Security Policy header. Directivas y alcance. https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy
    • R13 MDN Cross site request forgery. Tokens, origen y SameSite. https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF
    • R14 Chrome Privacy and security. Inspección HTTPS y cookies de terceros. https://developer.chrome.com/docs/devtools/security
    • R15 MDN Secure cookie configuration. Configuración de cookies de sesión. https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Cookies
  • MongoDB — El Ministerio de las Promesas Imposibles

    MongoDB — El Ministerio de las Promesas Imposibles

    1. Presentación de la práctica

    Bienvenidos al Ministerio de las Promesas Imposibles, una institución ficticia donde los políticos registran sus promesas electorales y el presupuesto necesario para cumplirlas.

    El problema es que el Ministerio ha perdido el control de sus datos. Hay promesas que se repiten, presupuestos disparatados, proyectos que nadie recuerda haber aprobado y políticos que llevan años prometiendo lo mismo.

    El Gobierno ha decidido contratar a un grupo de administradores de sistemas para modernizar la base de datos.

    Vuestra misión será construir y gestionar esa base de datos utilizando MongoDB.

    A lo largo de la práctica aprenderéis a crear documentos, consultar información, modificar promesas incumplidas y eliminar registros obsoletos.

    Todo ello mediante la terminal y sin necesidad de desarrollar una aplicación web.

    Objetivos de aprendizaje

    Al finalizar, el alumno deberá ser capaz de:

    • Comprender qué es MongoDB y en qué se diferencia de MySQL.
    • Crear y administrar una base de datos NoSQL.
    • Comprender las colecciones y los documentos JSON/BSON.
    • Insertar uno o varios documentos.
    • Realizar consultas con filtros y operadores.
    • Modificar documentos existentes.
    • Eliminar registros.
    • Realizar consultas sencillas de ordenación y estadísticas.
    • Comprender el significado de CRUD.

    2. ¿Qué es MongoDB?

    MongoDB es una base de datos NoSQL orientada a documentos.

    En una base de datos tradicional como MySQL almacenamos información en tablas formadas por filas y columnas. En MongoDB trabajamos con colecciones de documentos que tienen una estructura similar a JSON.

    MySQLTablas y filas

    MongoDBColecciones y documentos

    MySQLMongoDB
    Base de datosBase de datos
    TablaColección
    FilaDocumento
    ColumnaCampo
    Clave primaria_id
    SQLAPI de consultas MongoDB

    Por ejemplo, una promesa política podría almacenarse así:

    
    {
      "politico": "Pepe Promesas",
      "partido": "Partido del Unicornio",
      "promesa": "WiFi gratis hasta en los ascensores",
      "presupuesto": 250000,
      "cumplida": false,
      "etiquetas": ["tecnologia", "internet"]
    }
    

    Observad que tenemos cadenas de texto, números, valores booleanos y arrays dentro de un mismo documento.

    MongoDB almacena los documentos internamente en BSON, una representación binaria que admite tipos adicionales a los de JSON.

    3. Preparación del laboratorio

    Utilizaremos Docker para no tener que instalar directamente el servidor MongoDB en cada máquina virtual.

    Los comandos funcionarán en Linux y también en Windows desde PowerShell, siempre que Docker esté instalado y en ejecución.

    Paso 1. Comprobar Docker

    Abrimos la terminal y ejecutamos:

    
    docker --version
    docker ps
    

    Si Docker está funcionando, podremos consultar la versión y los contenedores activos.

    Paso 2. Descargar MongoDB

    
    docker pull mongo:8.0
    

    Usaremos la imagen oficial de MongoDB 8.0, evitando que una actualización automática de latest cambie la versión durante la práctica.

    Paso 3. Crear nuestro contenedor

    
    docker run -d \
      --name ministerio-mongo \
      -v ministerio_datos:/data/db \
      mongo:8.0
    

    En este comando:

    • -d ejecuta el contenedor en segundo plano.
    • --name identifica nuestro contenedor.
    • -v crea un volumen persistente para almacenar la base de datos.
    • mongo:8.0 es la imagen que estamos utilizando.

    Para esta práctica accederemos a MongoDB directamente desde dentro del contenedor. No necesitamos publicar el puerto 27017 ni dejar la base de datos accesible desde la red.

    Importante: el servidor se utiliza sin autenticación únicamente dentro de este laboratorio aislado. No es una configuración adecuada para producción.

    Paso 4. Comprobar el funcionamiento

    
    docker ps
    

    Deberíamos observar un contenedor llamado ministerio-mongo en estado Up.

    Paso 5. Entrar en MongoDB

    
    docker exec -it ministerio-mongo mongosh
    

    Se abrirá la consola interactiva de MongoDB, llamada mongosh.

    A partir de ahora, los comandos que escribamos dentro de ella son instrucciones de MongoDB, no comandos Bash.

    Podemos comprobar la conexión:

    
    db.runCommand({ ping: 1 })
    

    El resultado debería incluir:

    
    { "ok": 1 }
    

    Referencia: Documentación de la imagen oficial de MongoDB. Docker Hub+1

    4. Crear nuestra primera base de datos

    El Ministerio necesita una base de datos donde registrar sus promesas.

    Escribimos dentro de mongosh:

    
    use ministerio
    

    Podemos comprobar qué base de datos estamos utilizando:

    
    db.getName()
    

    Resultado:

    ministerio

    Ahora creamos una colección llamada promesas.

    
    db.createCollection("promesas")
    

    Comprobamos las colecciones:

    
    show collections
    

    Y para visualizar las bases de datos que realmente tienen datos almacenados:

    
    show dbs
    

    Concepto importante: en MongoDB, seleccionar una base de datos con use no significa que ya esté creada físicamente. Se materializa cuando almacenamos información, como al crear nuestra primera colección.

    5. CREATE — Insertar documentos

    Primera operación CRUD: CREATE

    El Ministro de Transformación Digital, Pepe Promesas, ha anunciado su primera medida electoral:

    *«Todos los ciudadanos tendrán WiFi gratuito, incluso en los ascensores y en las cuevas». *

    Necesitamos registrar esta promesa en nuestra base de datos.

    Paso 1. Insertar un documento

    
    db.promesas.insertOne({
      politico: "Pepe Promesas",
      partido: "Partido del Unicornio",
      promesa: "WiFi gratis hasta en los ascensores",
      categoria: "Tecnologia",
      presupuesto: 250000,
      votos: 1200,
      cumplida: false
    })
    

    El resultado mostrará una confirmación similar a esta:

    {
      acknowledged: true,
      insertedId: ObjectId('...')
    }

    El campo insertedId contiene el identificador único asignado al documento.

    MongoDB genera automáticamente _id cuando no proporcionamos uno.

    Paso 2. Insertar varios documentos

    El Ministerio acaba de celebrar una reunión extraordinaria. Como era de esperar, han aparecido numerosas promesas nuevas.

    Vamos a introducirlas todas de golpe.

    
    db.promesas.insertMany([
      {
        politico: "Lola Subsidios",
        partido: "Union del Bocadillo",
        promesa: "Bocadillos gratis todos los viernes",
        categoria: "Alimentacion",
        presupuesto: 800000,
        votos: 4500,
        cumplida: false
      },
      {
        politico: "Paco Impuestos",
        partido: "Partido del Unicornio",
        promesa: "Eliminar todos los impuestos los lunes",
        categoria: "Economia",
        presupuesto: 5000000,
        votos: 9000,
        cumplida: false
      },
      {
        politico: "Rosa Aeropuertos",
        partido: "Frente de las Rotondas",
        promesa: "Construir un aeropuerto en cada pueblo",
        categoria: "Infraestructuras",
        presupuesto: 15000000,
        votos: 3200,
        cumplida: false
      },
      {
        politico: "Manolo Digital",
        partido: "Union del Bocadillo",
        promesa: "Sustituir funcionarios por robots",
        categoria: "Tecnologia",
        presupuesto: 3000000,
        votos: 11000,
        cumplida: true
      },
      {
        politico: "Carmen Futuro",
        partido: "Frente de las Rotondas",
        promesa: "Construir un carril bici hasta la Luna",
        categoria: "Infraestructuras",
        presupuesto: 50000000,
        votos: 1500,
        cumplida: false
      },
      {
        politico: "Toni Fiestas",
        partido: "Partido del Unicornio",
        promesa: "Declarar los lunes festivos",
        categoria: "Trabajo",
        presupuesto: 2000000,
        votos: 18000,
        cumplida: true
      },
      {
        politico: "Susana Pantallas",
        partido: "Union del Bocadillo",
        promesa: "Una pantalla gigante en cada rotonda",
        categoria: "Tecnologia",
        presupuesto: 650000,
        votos: 2700,
        cumplida: false
      }
    ])
    

    Ya tenemos ocho promesas electorales registradas, contando la primera.

    Paso 3. Contar los documentos

    
    db.promesas.countDocuments({})
    

    Resultado esperado:

    8

    Comprueba que lo has entendido

    ¿Qué diferencia existe entre insertOne() e insertMany()? ¿Qué ocurre si ejecutamos dos veces la misma inserción sin especificar un identificador?

    La segunda inserción generaría nuevos documentos con identificadores diferentes, creando registros duplicados en cuanto a contenido.

    6. READ — Consultar documentos

    Segunda operación CRUD: READ

    El Ministerio necesita consultar sus promesas. Los políticos empiezan a preguntar qué habían prometido durante la campaña.

    Paso 1. Mostrar todas las promesas

    
    db.promesas.find()
    

    El método find() devuelve los documentos de una colección.

    También podemos utilizar:

    
    db.promesas.find().toArray()
    

    Esto permite ver los resultados materializados como un array en la consola, algo cómodo para conjuntos pequeños.

    Paso 2. Buscar un político concreto

    Queremos saber qué prometió Paco Impuestos:

    
    db.promesas.find({
      politico: "Paco Impuestos"
    })
    

    El resultado será el documento correspondiente a Paco.

    Paso 3. Buscar promesas incumplidas

    
    db.promesas.find({
      cumplida: false
    })
    

    Deberían aparecer seis promesas.

    Ahora buscamos las cumplidas:

    
    db.promesas.find({
      cumplida: true
    })
    

    Deberían aparecer dos.

    Paso 4. Consultar por presupuestos

    El Tribunal de Cuentas ha solicitado todas las promesas cuyo presupuesto supera un millón de euros.

    Utilizaremos el operador $gt, que significa greater than (mayor que).

    
    db.promesas.find({
      presupuesto: { $gt: 1000000 }
    })
    

    Otros operadores importantes:

    OperadorSignificado
    $gtMayor que
    $gteMayor o igual que
    $ltMenor que
    $lteMenor o igual que
    $eqIgual que
    $neDistinto de
    $inIncluido en una lista

    Paso 5. Combinar condiciones

    Queremos conocer las promesas de tecnología que siguen incumplidas.

    
    db.promesas.find({
      categoria: "Tecnologia",
      cumplida: false
    })
    

    Cuando incluimos varias condiciones en un mismo filtro, MongoDB aplica un AND implícito.

    En este caso aparecerán las promesas de Pepe Promesas y Susana Pantallas.

    Paso 6. Utilizar OR

    El Ministerio quiere localizar las promesas que pertenezcan a las categorías de Economía o Trabajo.

    
    db.promesas.find({
      $or: [
        { categoria: "Economia" },
        { categoria: "Trabajo" }
      ]
    })
    

    Paso 7. Mostrar solamente algunos campos

    No siempre necesitamos visualizar todos los campos de cada documento.

    Queremos mostrar solamente el nombre del político, su promesa y el presupuesto.

    
    db.promesas.find(
      {},
      {
        _id: 0,
        politico: 1,
        promesa: 1,
        presupuesto: 1
      }
    )
    

    El 1 indica que queremos incluir un campo. El 0 indica que deseamos excluirlo.

    En este ejemplo también ocultamos _id, que se muestra por defecto.

    Paso 8. Ordenar los resultados

    Queremos descubrir cuáles son las promesas más caras.

    
    db.promesas.find(
      {},
      { _id: 0, politico: 1, presupuesto: 1 }
    ).sort({ presupuesto: -1 })
    

    En la ordenación:

    • 1 significa ascendente.
    • -1 significa descendente.

    Paso 9. Limitar resultados

    El Ministerio solamente quiere las tres promesas más caras.

    
    db.promesas.find(
      {},
      { _id: 0, politico: 1, presupuesto: 1 }
    ).sort({ presupuesto: -1 }).limit(3)
    

    El resultado esperado es:

    PolíticoPresupuesto
    Carmen Futuro50.000.000 €
    Rosa Aeropuertos15.000.000 €
    Paco Impuestos5.000.000 €

    7. UPDATE — Modificar documentos

    Tercera operación CRUD: UPDATE

    Han pasado varios meses y el Ministerio necesita actualizar la situación de sus promesas.

    Paso 1. Modificar un documento

    Pepe Promesas asegura que el WiFi ya funciona en todos los ascensores.

    Aunque nadie lo ha podido comprobar, el Ministerio decide marcar la promesa como cumplida.

    
    db.promesas.updateOne(
      { politico: "Pepe Promesas" },
      { $set: { cumplida: true } }
    )
    

    El operador $set permite cambiar uno o varios campos.

    La respuesta habitual incluye:

    matchedCount: 1
    modifiedCount: 1

    matchedCount indica cuántos documentos coincidieron con el filtro, y modifiedCount cuántos fueron modificados.

    Para comprobarlo:

    
    db.promesas.findOne({
      politico: "Pepe Promesas"
    })
    

    Paso 2. Modificar varios campos

    El Gobierno descubre que el carril bici lunar tendrá un coste mayor del previsto.

    
    db.promesas.updateOne(
      { politico: "Carmen Futuro" },
      {
        $set: {
          presupuesto: 75000000,
          cumplida: false
        }
      }
    )
    

    Paso 3. Incrementar un valor numérico

    Durante una manifestación política, Paco Impuestos ha ganado 500 votos adicionales.

    En lugar de consultar los votos actuales y escribir el nuevo total, utilizamos $inc.

    
    db.promesas.updateOne(
      { politico: "Paco Impuestos" },
      { $inc: { votos: 500 } }
    )
    

    Si tenía 9.000 votos, ahora tendrá 9.500.

    También podemos restar:

    
    db.promesas.updateOne(
      { politico: "Paco Impuestos" },
      { $inc: { votos: -200 } }
    )
    

    Ahora tendrá 9.300.

    Paso 4. Actualizar varios documentos

    El Ministerio decide incrementar en un 10 % el presupuesto de todas las promesas tecnológicas.

    
    db.promesas.updateMany(
      { categoria: "Tecnologia" },
      { $mul: { presupuesto: 1.10 } }
    )
    

    updateMany() modifica todos los documentos que cumplan la condición. $mul multiplica el valor numérico por el factor indicado.

    Paso 5. Incorporar un campo nuevo

    El Ministerio ha creado un departamento de excusas.

    Ahora cada promesa necesita un campo llamado excusa.

    
    db.promesas.updateOne(
      { politico: "Rosa Aeropuertos" },
      {
        $set: {
          excusa: "No encontramos suficientes pilotos"
        }
      }
    )
    

    Este campo no existía anteriormente en el documento. MongoDB permite añadirlo sin alterar los demás documentos de la colección.

    Este ejemplo ilustra una de las características de MongoDB: su modelo flexible de documentos.

    8. DELETE — Eliminar documentos

    Cuarta operación CRUD: DELETE

    Tras una auditoría interna, el Ministerio decide eliminar algunas promesas de su registro.

    Paso 1. Eliminar una promesa

    Se ha decidido cancelar el proyecto de las pantallas en las rotondas.

    
    db.promesas.deleteOne({
      politico: "Susana Pantallas"
    })
    

    El resultado esperado incluye:

    deletedCount: 1

    Comprobamos que ha desaparecido:

    
    db.promesas.find({
      politico: "Susana Pantallas"
    })
    

    No debería devolver ningún documento.

    Paso 2. Eliminar varios documentos

    El Ministerio ha decidido eliminar todas las promesas con presupuestos inferiores a 300.000 €.

    Primero consultaremos los documentos afectados:

    
    db.promesas.find({
      presupuesto: { $lt: 300000 }
    })
    

    En este punto de nuestra práctica, la única promesa que cumple esa condición es la de Pepe Promesas, cuyo presupuesto se incrementó hasta 275.000 €.

    Ahora sí, ejecutamos:

    
    db.promesas.deleteMany({
      presupuesto: { $lt: 300000 }
    })
    

    Advertencia: una operación deleteMany({}) sin filtros eliminaría todos los documentos de la colección. Debemos comprobar siempre la condición antes de ejecutarla.

    Paso 3. Comprobar el resultado

    
    db.promesas.countDocuments({})
    

    Si hemos seguido todos los pasos en orden, tendremos seis documentos.

    9. Resumen visual de CRUD

    10. Consultas adicionales — El escándalo de los presupuestos

    Hasta ahora hemos aprendido las cuatro operaciones fundamentales. Pero MongoDB también permite realizar operaciones de análisis.

    El Ministerio quiere conocer algunos datos estadísticos sobre sus promesas.

    ¿Cuánto dinero ha prometido el Gobierno?

    Utilizaremos una operación de agregación:

    
    db.promesas.aggregate([
      {
        $group: {
          _id: null,
          presupuestoTotal: { $sum: "$presupuesto" }
        }
      }
    ])
    

    $group agrupa documentos y $sum suma los valores del campo indicado.

    ¿Cuántas promesas tiene cada partido?

    
    db.promesas.aggregate([
      {
        $group: {
          _id: "$partido",
          totalPromesas: { $sum: 1 }
        }
      },
      {
        $sort: { totalPromesas: -1 }
      }
    ])
    

    Esto es una introducción a las aggregation pipelines. No necesitamos profundizar en ellas todavía, pero sí comprender que MongoDB puede hacer algo más que buscar documentos.

    11. Reto individual — La campaña electoral del alumno

    Misión: Operación Elecciones 404

    Actividad autónoma · 30–40 minutos · Sin soluciones

    El Ministerio ha decidido convocar nuevas elecciones. Cada alumno deberá crear su propio partido político ficticio y registrar sus promesas absurdas utilizando MongoDB.

    Primera parte: creación del partido

    El alumno debe crear una nueva colección llamada elecciones.

    En ella registrará al menos cinco promesas inventadas por él. Cada documento deberá contener:

    • Nombre del político.
    • Partido político.
    • Promesa electoral.
    • Categoría.
    • Presupuesto.
    • Número de votos.
    • Estado de cumplimiento.
    • Un campo adicional de su elección.

    Los nombres y promesas deben ser ficticios y utilizar el humor o la sátira.

    Segunda parte: operaciones obligatorias

    Lista de comprobación

    0 de 13Crear la colección elecciones.Insertar cinco promesas con insertMany().Mostrar todas las promesas.Buscar las promesas de un partido concreto.Encontrar las promesas cuyo presupuesto supere 1.000.000 €.Mostrar solamente los campos político, promesa y presupuesto.Ordenar las promesas de mayor a menor presupuesto.Mostrar únicamente las dos promesas más votadas.Modificar una promesa para marcarla como cumplida.Incrementar los votos de un político en 1.000.Añadir un campo llamado excusa a un documento.Eliminar una promesa utilizando deleteOne().Mostrar cuántos documentos quedan en la colección.

    Reiniciar comprobación

    Tercera parte: reto adicional

    Los alumnos que terminen antes podrán intentar tres operaciones más sin recibir los comandos directamente:

    1. Calcular el presupuesto total de todas las promesas.
    2. Averiguar qué partido tiene más promesas registradas.
    3. Mostrar únicamente las promesas incumplidas y ordenarlas por número de votos.

    12. Entrega del trabajo

    El alumno deberá preparar un archivo README.md que incluya una breve explicación de la práctica, los comandos utilizados y las capturas de las operaciones principales.

    No es necesario entregar una aplicación web. El objetivo es demostrar que sabe trabajar directamente con MongoDB desde la consola.

    Criterios de evaluación

    ApartadoPuntos
    Creación de BD y colecciones1
    Inserción de documentos2
    Consultas y filtros3
    Actualización de documentos2
    Eliminación de documentos1
    Documentación y explicaciones1
    Total10

    13. Cierre y limpieza del laboratorio

    Para salir de la consola de MongoDB:

    
    exit
    

    Ya en la terminal Linux o PowerShell, podemos detener el contenedor:

    
    docker stop ministerio-mongo
    

    Para volver a utilizarlo otro día:

    
    docker start ministerio-mongo
    docker exec -it ministerio-mongo mongosh
    

    Gracias al volumen ministerio_datos, nuestra información seguirá almacenada aunque detengamos el contenedor.

    Si queremos eliminar el contenedor al terminar:

    
    docker rm -f ministerio-mongo
    

    El volumen seguirá existiendo. Para eliminar también todos los datos, de forma irreversible:

    
    docker volume rm ministerio_datos
    

    14. Preguntas finales para reflexionar

    Antes de entregar, cada alumno deberá contestar brevemente:

    1. ¿Qué diferencia existe entre una tabla de MySQL y una colección de MongoDB?
    2. ¿Para qué sirve el campo _id?
    3. ¿Qué diferencia hay entre find() y findOne()?
    4. ¿Cuándo utilizarías updateOne() en lugar de updateMany()?
    5. ¿Qué ocurriría si ejecutases deleteMany({})?
    6. ¿Por qué puede resultar útil que dos documentos de una colección tengan campos diferentes?
    7. ¿Para qué nos sirve utilizar un volumen Docker en esta práctica?
  • Operación MONCLOA.GIT — El Ministerio de las Promesas Imposibles

    Operación MONCLOA.GIT — El Ministerio de las Promesas Imposibles

    Introducción: bienvenidos al ministerio

    El Gobierno de la República de Gitlandia acaba de crear el Ministerio de las Promesas Imposibles, una institución encargada de desarrollar un programa electoral que cambiará constantemente.

    El problema es que cada vez que un político cambia de opinión, los funcionarios sobrescriben los documentos anteriores. Nadie sabe qué prometieron ayer, quién modificó una propuesta o cuándo desapareció una medida electoral.

    El ministro de Tecnología ha decidido contratar a un administrador de sistemas para solucionar el problema: tú.

    Tu misión será poner en funcionamiento un sistema de control de versiones que permita conservar todas las promesas electorales, identificar sus modificaciones, recuperar propuestas anteriores y publicar el programa en Internet.

    A lo largo de la práctica se producirán varios incidentes: promesas incumplidas, presupuestos disparatados, borrados accidentales y hasta un intento de manipulación del programa electoral.

    Toda la actividad es ficticia. La sátira se dirige a situaciones políticas generales, sin necesidad de utilizar partidos o políticos reales.

    Objetivos de aprendizaje

    Al terminar, cada alumno deberá ser capaz de:

    1. Instalar y configurar Git desde la terminal.
    2. Crear e inicializar un repositorio local.
    3. Comprender el directorio de trabajo, el área de preparación (staging) y los commits.
    4. Añadir, modificar, eliminar y recuperar archivos.
    5. Consultar el historial y comparar versiones.
    6. Utilizar .gitignore para excluir determinados archivos.
    7. Crear y utilizar ramas individualmente.
    8. Trabajar con un repositorio remoto en GitHub.
    9. Subir, descargar y sincronizar cambios.
    10. Clonar un repositorio y continuar trabajando sobre él.

    En esta práctica cada estudiante trabaja individualmente. Las colaboraciones, los conflictos entre usuarios y las pull requests quedarán para la segunda práctica.

    Antes de comenzar: comprender Git

    Git es un sistema de control de versiones distribuido. Permite registrar el estado de los archivos de un proyecto en diferentes momentos y recuperar su historial.

    Git no es lo mismo que GitHub: Git funciona localmente y no necesita Internet. GitHub es un servicio que puede alojar repositorios remotos y facilitar el intercambio de proyectos.

    Esquema conceptual simplificado: primero se modifican archivos, después se seleccionan los cambios y finalmente se registran mediante un commit.

    Fase 1. Preparación del entorno

    Paso 1. Comprobar si Git está instalado

    Abre una terminal de tu máquina virtual Ubuntu y ejecuta:

    git --version

    Si está instalado, aparecerá algo parecido a:

    git version 2.x.x

    Si aparece command not found, instala Git:

    sudo apt update
    sudo apt install git -y

    Después, verifica nuevamente su versión.

    Pregunta para el alumno: ¿Por qué crees que Git puede funcionar sin conexión a Internet?

    Paso 2. Configurar nuestra identidad

    Cada vez que realicemos un commit, Git registrará la identidad del autor.

    Configura tu nombre y correo electrónico:

    git config --global user.name "Tu Nombre"
    git config --global user.email "tu-correo@example.com"

    Utiliza un correo propio o el correo privado que GitHub te proporcione para los commits.

    Consulta tu configuración:

    git config --global --list

    Y configura la rama inicial predeterminada:

    git config --global init.defaultBranch main

    Importante: esta configuración es global para tu usuario de Linux. No es necesario repetirla para cada proyecto.

    Paso 3. Crear nuestro espacio de trabajo

    Crearemos una carpeta donde almacenar nuestros proyectos.

    mkdir -p ~/proyectos-git
    cd ~/proyectos-git
    
    mkdir ministerio-promesas
    cd ministerio-promesas

    Comprueba dónde estás:

    pwd

    Visualiza el contenido:

    ls -la

    El directorio debería estar vacío.

    Paso 4. Inicializar un repositorio

    Ejecuta:

    git init -b main

    Acabamos de convertir una carpeta normal en un repositorio Git.

    Compruébalo:

    ls -la

    Verás una carpeta oculta llamada .git.

    Esta carpeta contiene la información necesaria para que Git pueda gestionar el repositorio: objetos, referencias, configuración e historial.

    No es necesario modificar manualmente su contenido.

    Ahora ejecuta:

    git status

    Debería aparecer un mensaje parecido a:

    On branch main
    
    No commits yet
    
    nothing to commit

    Primer reto: ¿Qué sucedería si eliminásemos la carpeta .git? ¿Desaparecerían los archivos de nuestro proyecto? ¿Y el historial?

    Fase 2. Primer programa electoral

    Paso 5. Crear el documento oficial

    El presidente de Gitlandia ha anunciado el primer programa electoral:

    «Prometemos que nadie volverá a quedarse sin batería en el móvil, que todos los lunes serán festivos y que los exámenes se resolverán por votación popular».

    Vamos a escribir estas propuestas en un archivo Markdown.

    Ejecuta:

    nano programa.md

    Introduce el siguiente contenido:

    # Programa Electoral de Gitlandia
    
    ## Elecciones Generales
    
    Presidente: Don Commit Permanente
    
    ## Promesas oficiales
    
    1. WiFi gratis en todo el país.
    2. Los lunes serán festivos.
    3. Cada ciudadano tendrá un robot asistente.
    4. Los exámenes serán voluntarios.
    5. Las impresoras funcionarán siempre.
    
    ## Lema
    
    "Prometemos hasta que se llene el disco duro".

    Guarda con Ctrl + O, pulsa Intro y cierra con Ctrl + X.

    Comprueba el contenido:

    cat programa.md

    Paso 6. Consultar el estado del repositorio

    Ejecuta:

    git status

    Aparecerá el archivo programa.md como archivo no seguido (untracked).

    Esto significa que Git todavía no está controlando sus versiones.

    Ahora ejecuta:

    git add programa.md
    git status

    El archivo aparecerá preparado para incluirse en un commit.

    Acabas de añadirlo al área de preparación, también llamada staging area.

    Paso 7. Registrar el primer commit

    Un commit es un registro de una versión del proyecto, acompañado de información sobre su autor, fecha y mensaje.

    Ejecuta:

    git commit -m "Primer programa electoral de Gitlandia"

    Consulta el historial:

    git log

    Verás una entrada con un identificador, autor, fecha y mensaje.

    También puedes obtener una versión resumida:

    git log --oneline

    Comprobación: el profesor debe poder identificar el primer commit y comprobar que el repositorio está limpio mediante git status.

    Fase 3. Primer escándalo: las promesas cambian

    Paso 8. Modificar una promesa

    El ministro de Economía acaba de descubrir que los lunes festivos son caros.

    El Gobierno ha decidido modificar la segunda promesa:

    Antes: Los lunes serán festivos.

    Después: Los lunes serán festivos únicamente si no hay trabajo pendiente.

    Edita el archivo:

    nano programa.md

    Modifica esa línea, guarda el archivo y ejecuta:

    git diff

    Git mostrará los cambios respecto de la versión preparada o registrada anteriormente.

    Normalmente, las líneas eliminadas aparecen con - y las nuevas con +.

    Ejemplo de comparación

    – 2. Los lunes serán festivos.

    + 2. Los lunes serán festivos únicamente si no hay trabajo pendiente.

    Ahora registra la modificación:

    git add programa.md
    
    git commit -m "Economía limita los lunes festivos"

    Comprueba:

    git log --oneline

    Ya deberías tener dos commits.

    Paso 9. Segunda modificación: llega la oposición

    La oposición acusa al Gobierno de hacer promesas imposibles. El presidente responde anunciando nuevas medidas.

    Añade al final de programa.md:

    ## Nuevas medidas
    
    6. Un unicornio eléctrico para cada familia.
    7. Internet gratuito incluso dentro de los túneles.
    8. Los atascos serán declarados ilegales.
    9. Se eliminarán los errores 404 por decreto.

    Antes de confirmar, ejecuta:

    git status
    git diff

    Añade el archivo al staging:

    git add programa.md

    Ahora observa qué se va a registrar:

    git diff --staged

    Finalmente:

    git commit -m "Ampliación del programa con medidas revolucionarias"

    Reto: interpreta lo que estás viendo

    Responde en tu documentación: ¿qué diferencia existe entre git diff y git diff --staged? ¿Por qué puede interesarnos seleccionar los cambios antes de confirmarlos?

    Fase 4. El presupuesto secreto

    Paso 10. Crear varios documentos

    El Ministerio comienza a tener departamentos.

    Crea la estructura:

    mkdir -p economia comunicacion secretos

    Crea un archivo de presupuesto:

    cat > economia/presupuesto.txt <<'EOF'
    PRESUPUESTO GENERAL DE GITLANDIA
    
    WiFi nacional: 500 millones
    Unicornios eléctricos: 800 millones
    Robot por ciudadano: 2000 millones
    Publicidad institucional: 9000 millones
    
    Total: pendiente de calcular
    EOF

    Ahora un comunicado oficial:

    cat > comunicacion/nota.txt <<'EOF'
    COMUNICADO DEL GOBIERNO
    
    El presupuesto está perfectamente controlado.
    Las calculadoras de la oposición están manipuladas.
    EOF

    Y un documento que no debería publicarse:

    echo "Clave ficticia de demostración: VOTO123" > secretos/claves.txt

    Paso 11. Evitar archivos innecesarios con .gitignore

    Los archivos excluidos por .gitignore no se añadirán automáticamente al control de versiones cuando todavía no están siendo seguidos.

    Crea:

    nano .gitignore

    Introduce:

    # Documentos internos ficticios
    secretos/
    
    # Archivos temporales
    *.tmp
    
    # Copias de seguridad
    *.bak
    
    # Configuraciones locales
    .env

    Comprueba:

    git status

    El archivo dentro de secretos/ no aparecerá como pendiente de seguimiento.

    Puedes verificar la regla:

    git check-ignore -v secretos/claves.txt

    Ahora registra los documentos públicos y las reglas de exclusión:

    git add .gitignore economia/ comunicacion/
    
    git status
    
    git commit -m "Creación de departamentos y presupuesto oficial"

    Advertencia: .gitignore no convierte una contraseña en segura. No debe utilizarse Git para publicar secretos. Si alguna credencial real se ha incluido en un commit, ignorarla posteriormente no elimina el secreto del historial.

    Fase 5. Crisis institucional: recuperar los errores

    Esta fase es especialmente importante: aprenderemos a solucionar problemas sin destruir nuestro trabajo.

    Paso 12. Recuperar un archivo modificado

    Un funcionario decide cambiar el presupuesto utilizando un procedimiento bastante cuestionable:

    echo "TODO EL PRESUPUESTO PARA HELADOS" > economia/presupuesto.txt

    Comprueba el resultado:

    cat economia/presupuesto.txt
    git diff

    El ministro entra en pánico.

    Necesitamos recuperar la última versión confirmada del archivo.

    Ejecuta:

    git restore economia/presupuesto.txt

    Vuelve a comprobar:

    cat economia/presupuesto.txt

    El presupuesto original debería haber regresado.

    git restore ha descartado la modificación no registrada. Si hubiésemos querido conservarla, primero tendríamos que haberla guardado en otro lugar.

    Paso 13. Recuperar un archivo eliminado

    Un nuevo ministro decide que la mejor manera de evitar las críticas es eliminar los comunicados.

    Ejecuta:

    rm comunicacion/nota.txt

    Comprueba:

    git status

    El archivo aparecerá como eliminado.

    Recupéralo:

    git restore comunicacion/nota.txt

    Comprueba que existe nuevamente:

    ls comunicacion/
    cat comunicacion/nota.txt

    Paso 14. Quitar un cambio del staging

    En esta ocasión, el funcionario ha preparado accidentalmente un cambio.

    echo "Impuesto por respirar aire premium" >> economia/presupuesto.txt
    
    git add economia/presupuesto.txt
    git status

    El archivo ya está en staging.

    Para retirarlo sin perder la modificación:

    git restore --staged economia/presupuesto.txt

    Comprueba:

    git status
    cat economia/presupuesto.txt

    La modificación continúa en el archivo, pero ya no está preparada para un commit.

    Finalmente, descarta el experimento:

    git restore economia/presupuesto.txt

    Diferencia esencial

    ComandoResultado
    git restore archivoDescarta los cambios locales no preparados en ese archivo
    git restore --staged archivoLo retira del staging, conservando el contenido
    git add archivoPrepara cambios para el próximo commit
    git commitRegistra un nuevo punto del historial

    Fase 6. La hemeroteca: Git nunca olvida

    Paso 15. Investigar el historial de promesas

    Un periodista solicita el programa electoral original para comprobar qué decía el Gobierno antes de modificar sus promesas.

    Ejecuta:

    git log --oneline

    El resultado será parecido a este (los identificadores serán diferentes):

    e3a45b1 Creación de departamentos y presupuesto oficial
    b729c81 Ampliación del programa con medidas revolucionarias
    a16d729 Economía limita los lunes festivos
    c871a93 Primer programa electoral de Gitlandia

    Cada línea representa un commit.

    Consulta los detalles del último:

    git show HEAD

    HEAD indica el commit actual de nuestra rama.

    Consulta la versión anterior:

    git show HEAD~1

    También podemos investigar los cambios de un archivo:

    git log --oneline -- programa.md

    Y comprobar cómo era en el primer commit. En nuestro caso, hemos realizado cuatro commits, por lo que:

    git show HEAD~3:programa.md

    Atención: HEAD~3 significa retroceder tres commits desde el actual, no necesariamente acceder siempre al primero. En esta práctica funciona si se ha seguido exactamente la secuencia.

    Paso 16. Comparar dos momentos de la historia

    Consulta el historial para localizar los identificadores de dos commits.

    git log --oneline

    Después ejecuta, sustituyendo los identificadores:

    git diff ID_ANTIGUO ID_NUEVO -- programa.md

    Por ejemplo, si los identificadores fueran c871a93 y b729c81:

    git diff c871a93 b729c81 -- programa.md

    Con este procedimiento puedes demostrar que el Gobierno cambió sus promesas.

    Investigación periodística

    Elabora un pequeño informe respondiendo: ¿cuál fue la promesa modificada? ¿En qué commit ocurrió? ¿Qué mensaje acompañaba al cambio? ¿Cómo puedes recuperar el contenido de la versión original sin modificar el proyecto actual?

    Fase 7. El Gobierno comete un error histórico

    Ahora vamos a ver otra de las funciones más útiles de Git: revertir un commit completo.

    Paso 17. Registrar una medida equivocada

    El presidente firma accidentalmente un decreto que establece:

    «Todos los ciudadanos deberán pagar una cuota de 100 euros por utilizar el teclado de su ordenador».

    Añade esta línea al programa electoral:

    echo "10. Impuesto de 100 euros por utilizar el teclado." >> programa.md

    Registra el cambio:

    git add programa.md
    git commit -m "Aprobado nuevo impuesto sobre los teclados"

    La medida provoca una crisis nacional. El Gobierno quiere anularla, pero conservar la evidencia histórica de que fue aprobada.

    Ejecuta:

    git revert --no-edit HEAD

    Observa el historial:

    git log --oneline

    Deberían aparecer tanto el commit que introdujo el impuesto como otro posterior que lo revierte.

    ¿Por qué es importante? Porque Git no ha borrado el commit anterior. Ha registrado una nueva operación que anula su efecto.

    Comprueba que la medida ya no aparece:

    cat programa.md

    Así queda la historia del impuesto

    No confundas git revert con git restore. El primero crea un commit que deshace otro commit; el segundo permite recuperar archivos o quitar cambios del staging sin registrar necesariamente un nuevo commit. git Documentation+1

    Fase 8. Crear una rama: el programa electoral alternativo

    Aunque todavía no vamos a trabajar en equipo, es importante comprender las ramas. Un desarrollador individual puede utilizarlas para experimentar sin modificar su versión principal.

    Paso 18. Crear el programa de la oposición

    El partido de la oposición, llamado Partido del Código Libre (PCL), propone sustituir algunas medidas por otras todavía más absurdas.

    Desde main, crea una rama:

    git switch -c oposicion

    Comprueba:

    git branch

    Debería aparecer:

      main
    * oposicion

    El asterisco identifica la rama actual.

    Ahora crea un archivo nuevo:

    cat > oposicion.md <<'EOF'
    # Programa de la oposición
    
    Partido del Código Libre
    
    1. Prohibir las reuniones que pudieron ser un correo.
    2. Obligar a que todos los políticos usen Linux.
    3. Convertir el Congreso en un servidor de Minecraft.
    4. Que los ministros aprueben un examen de informática.
    5. Sustituir los discursos por mensajes de 140 caracteres.
    EOF

    Registra el documento:

    git add oposicion.md
    
    git commit -m "Programa alternativo de la oposición"

    Comprueba:

    ls
    git log --oneline

    Paso 19. Volver al Gobierno

    Regresa a la rama principal:

    git switch main

    Lista los archivos:

    ls

    Observa que oposicion.md ya no aparece.

    Esto ocurre porque el archivo se creó y se registró en otra rama.

    Vuelve a la oposición:

    git switch oposicion

    El archivo vuelve a aparecer.

    Paso 20. Fusionar la oposición con el Gobierno

    El presidente anuncia un pacto histórico: incorporará las propuestas de la oposición al repositorio oficial.

    Regresa a la rama principal:

    git switch main

    Fusiona la rama de la oposición:

    git merge oposicion

    Comprueba:

    ls
    git log --oneline --graph --all --decorate

    El archivo oposicion.md debe aparecer en la rama principal.

    En esta situación lo normal es que Git pueda realizar una fusión de tipo fast-forward, porque main no tenía nuevos commits desde que se creó la rama.

    No siempre aparece un commit específico de fusión. Es importante que los alumnos entiendan este comportamiento.

    Puedes eliminar la rama individual una vez integrada:

    git branch -d oposicion

    El contenido fusionado no desaparece.

    Fase 9. Llega la revolución: publicar en GitHub

    Hasta este momento hemos trabajado completamente en local. El repositorio existe únicamente dentro de nuestra máquina virtual.

    Ahora vamos a publicarlo en un servicio remoto.

    Paso 21. Crear una cuenta en GitHub

    Entra en GitHub y crea una cuenta si no tienes una.

    Después, crea un nuevo repositorio desde el botón de creación de repositorios.

    Configúralo con estos datos:

    CampoValor
    Repository nameministerio-promesas
    DescriptionPrograma electoral ficticio de Gitlandia
    VisibilidadPúblico, o privado si así lo indica el profesor
    Add READMEDesactivado
    Add .gitignoreDesactivado
    Choose a licenseNinguna por ahora

    Es importante no inicializar el repositorio remoto con archivos, porque ya tenemos un proyecto local con su propio historial. GitHub Docs

    Paso 22. Vincular Git local con GitHub

    Copia la dirección HTTPS del repositorio que acabas de crear.

    Será similar a:

    https://github.com/TU_USUARIO/ministerio-promesas.git

    Vuelve a la terminal, dentro de ministerio-promesas.

    Ejecuta:

    git remote add origin https://github.com/TU_USUARIO/ministerio-promesas.git

    Sustituye TU_USUARIO por tu nombre de usuario de GitHub.

    Consulta los remotos:

    git remote -v

    Verás una dirección de envío (push) y otra de descarga (fetch).

    origin es simplemente el nombre que hemos asignado al repositorio remoto. No es una palabra reservada que tenga que utilizarse obligatoriamente.

    Paso 23. Publicar el primer repositorio

    Ejecuta:

    git push -u origin main

    La opción -u establece la relación de seguimiento entre nuestra rama local main y la rama remota correspondiente.

    A partir de ese momento, los siguientes envíos podrán hacerse simplemente mediante:

    git push

    Importante: autenticación

    GitHub no admite utilizar la contraseña normal de tu cuenta como contraseña de Git para operaciones HTTPS. Tendrás que usar un método de autenticación compatible, como Git Credential Manager, GitHub CLI o un token de acceso personal con los permisos adecuados.

    Una opción especialmente cómoda para un entorno de prácticas es utilizar GitHub CLI:

    gh auth login

    Este comando requiere tener gh instalado. En el asistente selecciona GitHub.com, HTTPS y sigue las instrucciones de autenticación.

    Nunca escribas tokens ni contraseñas reales dentro de los archivos de la práctica.

    Paso 24. Comprobar la publicación

    Vuelve al navegador, abre tu repositorio de GitHub y actualiza la página.

    Deberían aparecer programa.md, oposicion.md, los directorios públicos, .gitignore y el historial.

    Comprueba especialmente que secretos/claves.txt no se ha publicado.

    Paso 25. Crear un README para los visitantes

    Un repositorio debería explicar qué contiene y cómo utilizarlo.

    Crea:

    nano README.md

    Añade:

    # Ministerio de las Promesas Imposibles
    
    Bienvenido al repositorio oficial del Gobierno
    de Gitlandia.
    
    ## Objetivo
    
    Aprender Git mediante un programa electoral
    ficticio que cambia constantemente.
    
    ## Archivos
    
    - programa.md: Programa electoral oficial.
    - oposicion.md: Propuestas de la oposición.
    - economia/: Presupuestos.
    - comunicacion/: Comunicados oficiales.
    
    ## Tecnología
    
    - Linux
    - Git
    - GitHub
    - Markdown
    
    ## Advertencia
    
    Todos los personajes y propuestas son ficticios.
    
    Proyecto educativo individual.

    Registra y publica los cambios:

    git add README.md
    
    git commit -m "Documentación oficial del repositorio"
    
    git push

    Actualiza la página de GitHub.

    El contenido del README debería mostrarse automáticamente en la página del proyecto.

    Fase 10. Sincronización y clonación

    Paso 26. Simular una modificación desde otro ordenador

    Queremos comprobar qué sucede cuando el repositorio remoto tiene cambios que aún no existen en nuestra máquina virtual.

    Abre el archivo README.md directamente en GitHub y utiliza su opción de edición.

    Añade al final:

    ## Comunicado de última hora
    
    El ministro anuncia que se estudiará
    la posibilidad de cumplir alguna promesa.

    Guarda la modificación mediante un commit desde GitHub.

    Acabamos de crear una versión nueva que existe en GitHub, pero todavía no en nuestra máquina virtual.

    Paso 27. Descargar los cambios

    Vuelve a la terminal de Ubuntu.

    Ejecuta:

    git fetch origin

    Este comando descarga información y objetos del repositorio remoto, pero no integra automáticamente los cambios en tu rama local.

    Comprueba las diferencias:

    git log --oneline HEAD..origin/main

    Después ejecuta:

    git pull --ff-only

    En este caso, como no hemos realizado nuevos commits locales después de la publicación, Git debería poder actualizar mediante fast-forward.

    Comprueba el README:

    cat README.md

    La modificación introducida desde GitHub debería estar disponible localmente.

    Pregunta: ¿Qué diferencia existe entre git fetch, git pull y git push?

    Paso 28. Clonar el proyecto en otro directorio

    Vamos a simular que hemos adquirido un nuevo ordenador o que necesitamos restaurar el proyecto después de perder la máquina virtual.

    Sal del directorio de trabajo:

    cd ~/proyectos-git

    Crea un directorio nuevo:

    mkdir ordenador-nuevo
    cd ordenador-nuevo

    Clona tu repositorio:

    git clone https://github.com/TU_USUARIO/ministerio-promesas.git

    Entra en él:

    cd ministerio-promesas

    Comprueba:

    git status
    git remote -v
    git log --oneline
    ls -la

    El repositorio clonado tendrá los archivos publicados y su historial Git.

    No contendrá los archivos locales ignorados que no se subieron a GitHub.

    Última misión

    Desde este nuevo clon, añade a README.md una sección titulada «El Ministerio sobrevive al cambio de ordenador».

    Después:

    git add README.md
    
    git commit -m "El Ministerio sobrevive al cambio de ordenador"
    
    git push

    Regresa al repositorio original:

    cd ~/proyectos-git/ministerio-promesas

    Y actualízalo:

    git pull --ff-only

    Comprueba que el cambio realizado en el nuevo clon aparece también en el original.

    Hemos trabajado sobre el mismo proyecto desde dos directorios diferentes, sin necesitar un trabajo colaborativo entre personas.

    Fase 11. El examen del ministro

    En esta última fase, los alumnos realizarán pequeñas operaciones sin copiar exactamente la secuencia anterior. Deben identificar los comandos necesarios y justificar su elección.

    Misiones finales

    Marca las completadas

    0 de 7 misiones

    0%Crear un archivo ministros.md con tres ministros ficticios y registrar un commit.Añadir una cuarta promesa al programa electoral, identificar el cambio con git diff y registrarlo.Modificar accidentalmente economia/presupuesto.txt y recuperar la versión confirmada.Crear un archivo temporal discurso.tmp y comprobar que Git lo ignora.Crear una rama llamada reforma, añadir una propuesta, registrarla y fusionarla con main.Publicar todos los cambios en GitHub y comprobar que el repositorio local está sincronizado.Mostrar el historial completo con un gráfico de los commits.

    Comandos que deben dominar

    ComandoFunción
    git init -b mainInicializar repositorio
    git statusConsultar estado
    git add archivoPreparar cambios
    git commit -m "mensaje"Registrar versión
    git log --onelineConsultar historial
    git diffComparar cambios
    git show HEADInspeccionar último commit
    git restore archivoRecuperar contenido
    git restore --staged archivoRetirar del staging
    git revert HEADRevertir un commit
    git switch -c ramaCrear y cambiar de rama
    git merge ramaFusionar ramas
    git remote -vConsultar remotos
    git pushEnviar commits
    git fetchDescargar información remota
    git pullDescargar e integrar cambios
    git clone URLClonar repositorio

    Adaptación para Windows

    En Windows pueden instalar Git for Windows y utilizar Git Bash, que permite ejecutar prácticamente todos los comandos Linux de esta práctica.

    Para quienes utilicen PowerShell, las principales diferencias están en los comandos del sistema, no en Git.

    OperaciónBash (Linux y Git Bash)PowerShell
    Directorio actualpwdpwd
    Listar archivosls -laGet-ChildItem -Force
    Crear carpetamkdir proyectomkdir proyecto
    Mostrar archivocat archivo.txtGet-Content archivo.txt
    Eliminar archivorm archivo.txtRemove-Item archivo.txt
    Editar archivonano archivo.txtnotepad archivo.txt
    Consultar Gitgit statusgit status

    Para mantener la práctica sencilla, recomiendo que todos trabajen con la terminal Linux de su máquina virtual, usando Windows solamente como demostración adicional.

    Entrega de la práctica

    Cada alumno deberá entregar un archivo README.md o informe.md con las respuestas a las preguntas, las explicaciones de los incidentes y las capturas necesarias.

    Además, deberá facilitar el enlace de su repositorio en GitHub y demostrar que contiene el historial de commits.

  • Práctica Arduino: Detector de obstáculos con sensor ultrasónico y servomotor

    Vamos a montar un sistema que detecte obstáculos mediante un sensor ultrasónico HC-SR04 y utilice un servomotor SG90 para señalar la dirección que está libre.

    La idea es construir un pequeño radar inteligente: cuando detecte un obstáculo delante, el sensor mirará hacia la izquierda y hacia la derecha, comparará las distancias y el servo terminará apuntando hacia el lado con más espacio disponible.

    1. Material necesario

    • Arduino UNO o Arduino Mega 2560.
    • Sensor ultrasónico HC-SR04.
    • Servomotor SG90 de 180°.
    • Cables Dupont.
    • Protoboard (opcional).
    • Cable USB para programar el Arduino.

    2. Conexiones

    Sensor ultrasónico HC-SR04

    Pin del sensorArduino
    VCC5V
    GNDGND
    TRIGDigital 9
    ECHODigital 10

    Servomotor SG90

    Cable del servoArduino
    Marrón (GND)GND
    Rojo (alimentación)5V
    Naranja (señal)Digital 6

    Para una prueba sencilla, un SG90 sin carga puede funcionar alimentado por el Arduino. Si hay reinicios, movimientos erráticos o el servo soporta peso, utiliza una fuente externa de 5 V adecuada, compartiendo GND con Arduino. No conectes el servo directamente a una fuente de 12 V.

    3. Funcionamiento del proyecto

    Ejemplo: el sistema detecta un obstáculo frontal y elige la izquierda porque mide una distancia mayor.

    El programa seguirá esta secuencia:

    1. El servo coloca el sensor mirando al frente (90°).
    2. El HC-SR04 mide la distancia.
    3. Si el obstáculo está a menos de 20 cm, comienza la exploración.
    4. El servo gira a 150° para medir la izquierda y a 30° para medir la derecha.
    5. Compara las dos distancias.
    6. El servo se queda apuntando hacia la dirección con más espacio libre durante dos segundos y vuelve al centro.

    4. Código completo de Arduino

    Puedes copiar directamente este programa en Arduino IDE. No necesitas instalar bibliotecas adicionales: Servo.h viene incluida normalmente con el entorno de placas Arduino AVR.

    
    #include <Servo.h>
    
    // Pines del sensor ultrasónico
    const int TRIG = 9;
    const int ECHO = 10;
    
    // Pin del servomotor
    const int PIN_SERVO = 6;
    
    // Ángulos del servo
    const int CENTRO = 90;
    const int IZQUIERDA = 150;
    const int DERECHA = 30;
    
    // Distancia mínima de seguridad (cm)
    const int DISTANCIA_ALERTA = 20;
    
    Servo radar;
    
    // Función para medir distancia en centímetros
    long medirDistancia() {
      digitalWrite(TRIG, LOW);
      delayMicroseconds(2);
    
      digitalWrite(TRIG, HIGH);
      delayMicroseconds(10);
      digitalWrite(TRIG, LOW);
    
      // Esperar respuesta como máximo 30 ms
      unsigned long duracion = pulseIn(ECHO, HIGH, 30000UL);
    
      // Sin eco: consideramos distancia máxima
      if (duracion == 0) {
        return 400;
      }
    
      return duracion / 58;
    }
    
    // Orientar sensor y realizar medición
    long mirar(int angulo) {
      radar.write(angulo);
      delay(600);  // Esperar al movimiento del servo
      return medirDistancia();
    }
    
    void setup() {
      Serial.begin(9600);
    
      pinMode(TRIG, OUTPUT);
      pinMode(ECHO, INPUT);
    
      radar.attach(PIN_SERVO);
      radar.write(CENTRO);
    
      delay(1000);
    
      Serial.println("Sistema detector de obstaculos");
    }
    
    void loop() {
    
      // 1. Miramos hacia delante
      long frente = mirar(CENTRO);
    
      Serial.print("Distancia frontal: ");
      Serial.print(frente);
      Serial.println(" cm");
    
      // 2. Si hay un obstáculo cercano
      if (frente < DISTANCIA_ALERTA) {
    
        Serial.println("OBSTACULO DETECTADO");
    
        // 3. Explorar izquierda
        long distanciaIzquierda = mirar(IZQUIERDA);
    
        Serial.print("Izquierda: ");
        Serial.print(distanciaIzquierda);
        Serial.println(" cm");
    
        // 4. Explorar derecha
        long distanciaDerecha = mirar(DERECHA);
    
        Serial.print("Derecha: ");
        Serial.print(distanciaDerecha);
        Serial.println(" cm");
    
        // 5. Elegir dirección con más espacio
        if (distanciaIzquierda > distanciaDerecha) {
    
          Serial.println("Direccion recomendada: IZQUIERDA");
          radar.write(IZQUIERDA);
    
        } else if (distanciaDerecha > distanciaIzquierda) {
    
          Serial.println("Direccion recomendada: DERECHA");
          radar.write(DERECHA);
    
        } else {
    
          Serial.println("Distancias iguales");
          radar.write(CENTRO);
        }
    
        // 6. Mantener la dirección indicada
        delay(2000);
    
        // Volver al centro
        radar.write(CENTRO);
        delay(600);
    
      } else {
        Serial.println("Camino frontal despejado");
      }
    
      delay(300);
    }
    

    5. Prueba del funcionamiento

    Abre el Monitor Serie del Arduino IDE y selecciona una velocidad de 9600 baudios.

    Coloca un objeto a unos 10 cm del sensor. Deberías ver algo parecido a esto:

    Distancia frontal: 12 cm
    OBSTACULO DETECTADO
    Izquierda: 85 cm
    Derecha: 30 cm
    Direccion recomendada: IZQUIERDA

    El servo apuntará hacia la izquierda durante dos segundos y después regresará a su posición central.

    Puedes modificar la sensibilidad cambiando esta línea:

    const int DISTANCIA_ALERTA = 20;

    Por ejemplo, puedes cambiar 20 por 40 para que comience a buscar caminos alternativos cuando detecte algo a menos de 40 cm.

    6. Montaje físico

    Es importante que el sensor HC-SR04 esté sujeto a la parte móvil del servo. De esta manera, cuando el motor gira, también gira el sensor y puede medir las distancias en diferentes direcciones.

    Ten en cuenta que los ángulos de izquierda y derecha pueden estar invertidos según cómo coloques físicamente el servomotor. Si ocurre, intercambia los valores 150 y 30.

    El código considera una ausencia de eco como 400 cm. Esto es útil para una demostración, pero no demuestra que exista un camino libre: también puede significar que el sensor no ha recibido una reflexión válida.

    7. Actividad adicional para los alumnos

    Una vez terminado el montaje, puedes pedirles que mejoren el proyecto incorporando tres condiciones diferentes:

    • Verde: no hay obstáculos a menos de 20 cm.
    • Amarillo: hay un obstáculo, pero se encuentra una dirección alternativa.
    • Rojo: las dos direcciones exploradas tienen obstáculos a menos de 20 cm.
  • Motor Shield L293D [Componente]

    Motor Shield L293D [Componente]

    Placa controladora: Arduino Mega 2560

    Módulo: Motor Shield L293D V1.0

    Nivel: Iniciación a robótica y programación Arduino

    Objetivo: Controlar motores de corriente continua, variar su velocidad, invertir su giro y realizar un pequeño proyecto de vehículo robot.

    1. ¿Qué placa tenemos?

    La placa de tu fotografía es una Motor Shield L293D V1.0, compatible con el diseño Adafruit Motor Shield V1.

    Se conecta directamente sobre el Arduino Mega 2560 y permite controlar motores sin tener que construir manualmente el circuito electrónico de potencia.

    La compatibilidad con Arduino Mega 2560 está documentada para este diseño de shield.

    Características principales

    ElementoCapacidad
    Motores DCHasta 4 independientes
    Motores paso a pasoHasta 2
    Servomotores2 conectores
    Control de velocidadPWM, valores de 0 a 255
    Inversión de giroSí
    Controladores principales2 × L293D
    Corriente por canal600 mA continuos, 1,2 A de pico
    BibliotecaAFMotor

    Importante: aunque el circuito L293D admite diferentes tensiones, la alimentación de los motores debe elegirse según los motores utilizados. Además, los L293D producen una caída de tensión apreciable y no son adecuados para motores de gran consumo.

    2. Identificar los componentes de la placa

    Driving DC Motors with L293D and Arduino
 – Robocraze

    robocraze.com

    En la placa de tu fotografía encontramos:

    ComponenteFunción
    M1, M2, M3 y M4Bornes de conexión para motores DC
    EXT_PWREntrada de alimentación externa de motores
    SERVO1, SERVO2Conexiones para servomotores
    Dos circuitos L293DControl de potencia, velocidad y sentido de giro
    Circuito 74HC595Gestión de señales de dirección
    PWR jumperSelección de alimentación compartida o independiente
    RESETReinicio del Arduino
    LEDIndicador de alimentación de motores

    El shield utiliza determinados pines digitales del Arduino Mega para gestionar internamente los motores. Por ello, no debemos asignar libremente esos mismos pines a otros dispositivos.

    3. Material necesario

    Para la primera prueba necesitaremos:

    • Un Arduino Mega 2560.
    • La placa Motor Shield L293D V1.0.
    • Un motor DC pequeño, preferiblemente de 6 V.
    • Una fuente de alimentación adecuada al motor.
    • Un cable USB para programar el Arduino.
    • Un ordenador con Arduino IDE.
    • Un destornillador pequeño para los bornes.

    Para posteriores prácticas podremos incorporar otros tres motores, ruedas, un chasis, servomotores y sensores.

    4. Montaje sobre el Arduino Mega 2560

    Motor Control Shield L293D

    1. Desconectamos el Arduino del ordenador y de cualquier alimentación.
    2. Colocamos el Motor Shield encima del Mega, haciendo coincidir las filas de pines del shield con los conectores correspondientes del Arduino.
    3. Comprobamos la orientación y que no haya ningún pin desplazado.
    4. Presionamos suavemente hasta que los conectores estén completamente introducidos.
    5. Verificamos que ningún terminal metálico esté produciendo un cortocircuito.

    No debemos intentar conectar el shield a todos los conectores del Mega: este modelo utiliza principalmente la distribución de pines del Arduino UNO, que también está presente en el Mega.

    5. Alimentación de los motores

    Esta es una de las partes más importantes del montaje.

    El Arduino Mega necesita energía para ejecutar el programa, mientras que los motores necesitan energía para moverse.

    La mejor opción para las prácticas es utilizar alimentaciones independientes.

    Esquema conceptual de alimentación independiente.

    Alimentación recomendada para las pruebas

    • Arduino Mega conectado al ordenador mediante USB.
    • Fuente externa para los motores conectada a EXT_PWR.
    • Jumper de alimentación PWR retirado.
    • Cable positivo de la fuente conectado a + y negativo conectado a − del bloque de alimentación.

    Precaución: verifica la polaridad impresa en tu placa antes de conectar la fuente. No utilices la salida de 5 V del Arduino para alimentar directamente los motores. Tampoco utilices una pila rectangular de 9 V como solución habitual para varios motores, porque suele entregar poca corriente.

    Si se conectan motores pequeños de 6 V, una fuente regulada de 6 V con corriente suficiente puede ser apropiada, teniendo en cuenta que la tensión efectiva en el motor será algo menor debido al L293D.

    La alimentación de los servomotores se trata por separado porque los conectores SERVO toman normalmente sus 5 V del Arduino.

    6. Instalación de Arduino IDE

    Descargamos Arduino IDE desde la web oficial:Descargar Arduino IDE

    Conectamos el Arduino Mega al ordenador y abrimos el programa.

    En el menú de placas seleccionamos:

    Herramientas → Placa → Arduino AVR Boards → Arduino Mega or Mega 2560

    Y seleccionamos el procesador:

    ATmega2560

    Finalmente seleccionamos el puerto USB correspondiente.

    Para comprobar la comunicación podemos cargar el ejemplo:

    Archivo → Ejemplos → 01.Basics → Blink

    Si el LED integrado parpadea, el Arduino está correctamente configurado.

    7. Instalar la biblioteca AFMotor

    Esta placa utiliza la biblioteca AFMotor, correspondiente a los Motor Shield V1.

    No debemos confundirla con Adafruit Motor Shield V2 Library, que está diseñada para otro controlador.

    Descargamos la biblioteca:Biblioteca AFMotor — GitHub

    En GitHub pulsamos Code → Download ZIP.

    Después, en Arduino IDE:

    Programa → Incluir librería → Añadir biblioteca .ZIP

    Seleccionamos el archivo descargado.

    Una vez instalada, comprobamos que podemos incluirla en un programa:

    #include <AFMotor.h>

    El Arduino Mega 2560 está contemplado expresamente en el código de esta biblioteca.

    blog.adafruit.com

    +1

    Parte práctica: nuestros primeros programas

    8. Práctica 1. Hacer girar un motor

    Objetivo: conseguir que un motor conectado a M1 funcione durante tres segundos y después se detenga.

    Conexiones

    1. Con el circuito desconectado, conectamos los dos cables del motor al borne M1.
    2. Retiramos el jumper PWR.
    3. Conectamos una fuente adecuada a EXT_PWR.
    4. Conectamos el Arduino Mega mediante USB.
    5. Cargamos el programa.

    Código completo

    
    #include <AFMotor.h>
    
    // Motor conectado al borne M1
    AF_DCMotor motor1(1);
    
    void setup() {
      // Velocidad entre 0 y 255
      motor1.setSpeed(150);
    }
    
    void loop() {
      // Girar hacia delante
      motor1.run(FORWARD);
      delay(3000);
    
      // Detener el motor
      motor1.run(RELEASE);
      delay(2000);
    }
    

    ¿Qué hace el programa?

    AF_DCMotor motor1(1); crea un objeto que representa el motor conectado a M1.

    setSpeed(150) establece un valor de velocidad PWM. No significa exactamente 150 revoluciones por minuto, sino un nivel de control dentro de la escala 0–255.

    run(FORWARD) ordena girar al motor en uno de los sentidos.

    run(RELEASE) deja de alimentar el motor y permite que se detenga libremente. No aplica un frenado activo.

    delay(3000) mantiene el programa esperando tres segundos.

    Resultado esperado: el motor gira durante tres segundos, se detiene dos segundos y repite el proceso.

    9. Práctica 2. Invertir el sentido de giro

    Vamos a conseguir que el mismo motor gire alternativamente en ambos sentidos.

    
    #include <AFMotor.h>
    
    AF_DCMotor motor1(1);
    
    void setup() {
      motor1.setSpeed(180);
    }
    
    void loop() {
      motor1.run(FORWARD);
      delay(2000);
    
      motor1.run(RELEASE);
      delay(1000);
    
      motor1.run(BACKWARD);
      delay(2000);
    
      motor1.run(RELEASE);
      delay(1000);
    }
    

    El motor gira dos segundos hacia delante, descansa uno, gira dos segundos en sentido contrario y vuelve a descansar.

    Ejercicio: modifica los tiempos para que cada giro dure cinco segundos y las pausas solo medio segundo.

    10. Práctica 3. Regular la velocidad

    En esta ocasión aumentaremos la velocidad progresivamente.

    
    #include <AFMotor.h>
    
    AF_DCMotor motor1(1);
    
    void setup() {
      motor1.setSpeed(0);
      motor1.run(FORWARD);
    }
    
    void loop() {
    
      // Aceleracion progresiva
      for (int velocidad = 0; velocidad <= 255; velocidad += 5) {
        motor1.setSpeed(velocidad);
        delay(100);
      }
    
      delay(1000);
    
      // Desaceleracion progresiva
      for (int velocidad = 255; velocidad >= 0; velocidad -= 5) {
        motor1.setSpeed(velocidad);
        delay(100);
      }
    
      motor1.run(RELEASE);
      delay(1000);
    }
    

    El motor acelera hasta alcanzar el máximo valor PWM, comienza a reducir su velocidad y finalmente se detiene.

    A velocidades muy bajas es posible que el motor no se mueva, aunque reciba una señal PWM. Esto depende de la fricción y de las características del motor.

    11. Práctica 4. Controlar cuatro motores simultáneamente

    Conectamos cuatro motores, cada uno a su correspondiente borne M1, M2, M3 y M4.

    
    #include <AFMotor.h>
    
    AF_DCMotor motor1(1);
    AF_DCMotor motor2(2);
    AF_DCMotor motor3(3);
    AF_DCMotor motor4(4);
    
    void setup() {
      motor1.setSpeed(180);
      motor2.setSpeed(180);
      motor3.setSpeed(180);
      motor4.setSpeed(180);
    }
    
    void loop() {
      // Arrancar los cuatro motores
      motor1.run(FORWARD);
      motor2.run(FORWARD);
      motor3.run(FORWARD);
      motor4.run(FORWARD);
    
      delay(3000);
    
      // Detener los cuatro motores
      motor1.run(RELEASE);
      motor2.run(RELEASE);
      motor3.run(RELEASE);
      motor4.run(RELEASE);
    
      delay(2000);
    }
    

    Resultado esperado: los cuatro motores giran simultáneamente durante tres segundos y se detienen durante dos.

    La etiqueta FORWARD no garantiza que todas las ruedas giren hacia delante físicamente, porque depende de cómo estén colocados los motores y de la polaridad de sus cables.

    Antes de utilizar los cuatro motores, debemos comprobar que el consumo individual y total es compatible con el shield y con la fuente de alimentación.

    12. Práctica 5. Controlar un servomotor

    La placa dispone de dos conexiones para servomotores. Podemos utilizarlas, por ejemplo, para girar un sensor de ultrasonidos instalado en un coche robot.

    TowerPro SG90 Micro Servo Motor – 180° Rotation for Arduino & Robotics
 – REES52

    Conectaremos un servomotor SG90 a SERVO1, respetando el orden de señal, positivo y negativo indicado en la placa.

    El conector SERVO1 corresponde al pin digital 9 y SERVO2 al pin 10.

    Adafruit Learning System

    +1

    #include <Servo.h>
    
    Servo servo1;
    
    void setup() {
      servo1.attach(9);
    }
    
    void loop() {
      servo1.write(0);
      delay(1000);
    
      servo1.write(90);
      delay(1000);
    
      servo1.write(180);
      delay(1000);
    }

    Resultado esperado: el servomotor se mueve a tres posiciones, esperando un segundo entre ellas.

    Los servomotores reciben alimentación de 5 V desde el Arduino en la configuración habitual del shield. Si se utilizan varios servos o modelos de elevado consumo, habrá que estudiar una alimentación independiente apropiada para evitar reinicios y sobrecargas.

    13. Pines utilizados por el Motor Shield

    Esta información resulta especialmente importante cuando queramos incorporar sensores al Arduino Mega.

    Pines digitalesUtilización
    11Velocidad M1
    3Velocidad M2
    5Velocidad M3
    6Velocidad M4
    4, 7, 8, 12Control interno de dirección
    9SERVO1
    10SERVO2
    2 y 13Disponibles

    Los pines indicados corresponden al diseño V1. Los demás pines libres del Mega pueden aprovecharse para sensores, siempre comprobando que no exista conflicto con otros módulos.

    Adafruit Learning System

    14. Proyecto final: coche robot de cuatro motores

    Una vez comprobado el funcionamiento de la placa, podremos construir un vehículo utilizando cuatro motores DC.

    4WD car using L293D motor shield — codemahal

    codemahal.com

    La distribución de motores será:

    MotorPosición del vehículo
    M1Delantero izquierdo
    M2Trasero izquierdo
    M3Delantero derecho
    M4Trasero derecho

    La siguiente programación permite avanzar, retroceder, girar a izquierda y derecha y detener el vehículo.

    
    #include <AFMotor.h>
    
    AF_DCMotor motor1(1);
    AF_DCMotor motor2(2);
    AF_DCMotor motor3(3);
    AF_DCMotor motor4(4);
    
    void detener() {
      motor1.run(RELEASE);
      motor2.run(RELEASE);
      motor3.run(RELEASE);
      motor4.run(RELEASE);
    }
    
    void avanzar() {
      motor1.run(FORWARD);
      motor2.run(FORWARD);
      motor3.run(FORWARD);
      motor4.run(FORWARD);
    }
    
    void retroceder() {
      motor1.run(BACKWARD);
      motor2.run(BACKWARD);
      motor3.run(BACKWARD);
      motor4.run(BACKWARD);
    }
    
    void izquierda() {
      motor1.run(BACKWARD);
      motor2.run(BACKWARD);
      motor3.run(FORWARD);
      motor4.run(FORWARD);
    }
    
    void derecha() {
      motor1.run(FORWARD);
      motor2.run(FORWARD);
      motor3.run(BACKWARD);
      motor4.run(BACKWARD);
    }
    
    void setup() {
      motor1.setSpeed(180);
      motor2.setSpeed(180);
      motor3.setSpeed(180);
      motor4.setSpeed(180);
    }
    
    void loop() {
      avanzar();
      delay(2000);
    
      detener();
      delay(500);
    
      retroceder();
      delay(2000);
    
      detener();
      delay(500);
    
      izquierda();
      delay(1000);
    
      detener();
      delay(500);
    
      derecha();
      delay(1000);
    
      detener();
      delay(2000);
    }
    

    Importante: este ejemplo supone que los motores están conectados de forma que FORWARD mueve todas las ruedas hacia delante. Habrá que invertir los cables o ajustar el programa de aquellos motores que giren en sentido contrario. Es preferible hacer la primera prueba con el vehículo levantado, sin que las ruedas toquen el suelo.

    Además, comprueba la corriente de bloqueo de cada motor. Los motores TT de algunos kits pueden superar los límites del L293D al arrancar o quedarse atascados.

    15. Solución de problemas

    ProblemaPosible solución
    El código no compila porque falta AFMotor.hInstalar la biblioteca AFMotor V1
    El motor no giraComprobar fuente externa, LED indicador y conexiones
    El motor gira al revésIntercambiar sus dos cables o invertir el comando
    El Arduino se reinicia al arrancar el motorRevisar alimentación y consumo
    El motor no gira con PWM bajoAumentar el valor de setSpeed()
    El L293D se calienta demasiadoDetener el ensayo y comprobar consumo y posibles bloqueos
    Los cuatro motores no funcionan simultáneamenteComprobar fuente, conexiones y corriente de cada motor

    16. Actividades de evaluación

    Para comprobar que se ha comprendido el funcionamiento, los alumnos deberán resolver estas actividades:

    1. Hacer que M1 gire cinco segundos hacia delante y otros cinco hacia atrás.
    2. Conseguir que M1 y M2 giren a distintas velocidades.
    3. Programar una aceleración progresiva de 0 a 255.
    4. Poner en marcha los cuatro motores y detenerlos en orden inverso.
    5. Crear las funciones avanzar(), retroceder(), izquierda(), derecha() y detener().
    6. Diseñar un recorrido para que un coche describa aproximadamente un cuadrado, ajustando experimentalmente los tiempos de giro.
    7. Incorporar un servomotor que mire alternativamente a izquierda, centro y derecha.

    17. Recursos oficiales

    Siguiente paso recomendado: comenzar probando un único motor DC en M1 y, una vez verificada la alimentación y el funcionamiento, conectar los cuatro motores.

    Para el proyecto del coche con Arduino Mega, cuatro motores y sensor de ultrasonidos sobre un servomotor, esta placa es una buena opción didáctica siempre que los motores no superen su capacidad eléctrica.

  • Práctica guiada: Análisis de tráfico de red con Wireshark

    Práctica guiada: Análisis de tráfico de red con Wireshark

    1. Introducción

    En esta práctica aprenderemos a utilizar Wireshark, una herramienta de análisis de protocolos que permite examinar los paquetes de datos que circulan por una red.

    Trabajaremos tanto con tráfico generado por nosotros como con archivos de captura previamente guardados, de extensión .pcap o .pcapng.

    Al finalizar podremos identificar direcciones IP, puertos, protocolos, consultas DNS, conexiones TCP y parte del contenido de determinadas comunicaciones.

    La práctica se realizará únicamente sobre tráfico propio, del laboratorio o expresamente autorizado.

    2. Objetivos

    Al terminar la actividad, el alumno deberá saber:

    • Instalar e iniciar Wireshark.
    • Seleccionar una interfaz de red.
    • Capturar y guardar paquetes.
    • Interpretar las columnas de una captura.
    • Aplicar filtros de visualización.
    • Identificar tráfico ICMP, DNS, TCP, HTTP y TLS.
    • Examinar conversaciones entre dos equipos.
    • Seguir una comunicación TCP.
    • Analizar un archivo PCAP y documentar los resultados obtenidos.

    3. Preparación del laboratorio

    Explore and Customize Wireshark Interface | LabEx

    8

    Paso 1. Instalar Wireshark

    Descargar Wireshark desde su página oficial:

    https://www.wireshark.org/download.html

    En Windows, durante la instalación, aceptar también la instalación de Npcap, que permite capturar paquetes de red.

    En Kali Linux, comprobar si ya está instalado:

    wireshark --version

    Si no aparece, instalarlo:

    sudo apt update
    sudo apt install wireshark -y

    Si se pregunta si los usuarios no administradores pueden capturar paquetes, seguir la configuración autorizada por el profesor. En instalaciones Debian/Kali, puede configurarse el grupo wireshark para permitir la captura sin ejecutar toda la aplicación como root.

    Paso 2. Conocer la interfaz

    Al ejecutar Wireshark, veremos las interfaces de red disponibles. Por ejemplo:

    • Ethernet: conexión por cable.
    • Wi-Fi: conexión inalámbrica.
    • Adaptadores virtuales: interfaces de VirtualBox, VMware o VPN.

    Una vez abierta una captura, Wireshark muestra tres zonas fundamentales:

    Esquema didáctico de las tres zonas de análisis de Wireshark.

    ZonaFunción
    1. Lista de paquetesPresenta los paquetes capturados
    2. DetallesDescompone cada paquete en protocolos y campos
    3. BytesMuestra los datos en hexadecimal y representación ASCII

    4. Primera captura de tráfico

    Examine a captured packet using Wireshark | TechTarget

    Paso 3. Iniciar la captura

    1. Seleccionar la interfaz conectada a la red.
    2. Hacer doble clic sobre ella.
    3. Comprobar que empiezan a aparecer paquetes.
    4. Dejar Wireshark capturando.

    Comprobación: el contador de paquetes debe aumentar cuando existe actividad de red.

    Paso 4. Generar tráfico ICMP

    Abrir una terminal y ejecutar:

    ping -c 4 1.1.1.1

    En Windows:

    ping -n 4 1.1.1.1

    Esto envía cuatro solicitudes ICMP Echo Request, habitualmente utilizadas para comprobar conectividad.

    Volver a Wireshark y detener la captura mediante el botón rojo.

    En la barra de filtros, escribir:

    icmp

    Pulsar Intro.

    Si la red permite las respuestas ICMP y la captura se ha realizado en la interfaz correcta, aparecerán paquetes de solicitud y respuesta.

    Preguntas para el alumno: ¿Cuál es la dirección IP de origen? ¿Cuál es la de destino? ¿Qué diferencia existe entre Echo Request y Echo Reply?

    5. Aprender a filtrar paquetes

    Los filtros de visualización permiten ocultar temporalmente los paquetes que no cumplen determinadas condiciones, sin eliminarlos del archivo.

    Consultor de filtros de WiresharkSelecciona qué tráfico quieres examinar

    Solo peticiones HTTP

    Filtro Copiar

    http.requestSolicitudes HTTP detectadas

    El alumno debe probar los filtros y observar cómo cambia la lista de paquetes.

    Importante: tcp.port == 80 es un filtro de visualización, no una orden de captura ni una instrucción para abrir un puerto.

    6. Analizar consultas DNS

    Paso 5. Generar una consulta DNS

    DNS permite traducir nombres de dominio a direcciones IP.

    En la terminal, ejecutar:

    nslookup example.com

    Si estamos capturando tráfico en Wireshark, aplicamos el filtro:

    dns

    Seleccionar uno de los paquetes de la consulta.

    En el panel de detalles, desplegar Domain Name System y buscar:

    • Transaction ID.
    • Queries.
    • Nombre de dominio solicitado.
    • Tipo de registro, por ejemplo A o AAAA.
    • Respuestas recibidas, cuando existan.

    Si no aparecen consultas, comprobar qué servidor DNS está utilizando el equipo y si el sistema utiliza DNS cifrado. Otra posibilidad es que la consulta se haya resuelto previamente desde caché.

    Actividad: identificar el dominio consultado y las direcciones IP devueltas. Anotar también la dirección del servidor DNS que respondió.

    7. Analizar una comunicación HTTP

    En esta sección crearemos nuestro propio servidor web local para observar cómo viajan los datos cuando se utiliza HTTP sin cifrado.

    Paso 6. Crear una página web sencilla

    En Linux o Kali:

    mkdir practica-wireshark
    cd practica-wireshark
    nano index.html

    Introducir el siguiente contenido:

    <!DOCTYPE html>
    <html lang="es">
    <head>
        <meta charset="UTF-8">
        <title>Laboratorio Wireshark</title>
    </head>
    <body>
        <h1>Laboratorio de análisis de red</h1>
        <p>Esta página se transmite mediante HTTP.</p>
        <p>Mensaje de prueba: ASIR2026</p>
    </body>
    </html>

    Guardar el archivo.

    Paso 7. Iniciar un servidor HTTP

    Desde el mismo directorio, ejecutar:

    python3 -m http.server 8000

    El servidor quedará escuchando en el puerto 8000.

    Ahora abriremos Wireshark y seleccionaremos la interfaz de loopback, normalmente lo en Linux, para capturar las conexiones locales.

    Comenzar la captura y, desde otra terminal, ejecutar:

    curl http://127.0.0.1:8000/

    Esta petición evitará que tengamos que depender del comportamiento del navegador.

    En Wireshark, introducir:

    tcp.port == 8000

    Deberán aparecer los segmentos TCP de la conexión.

    Paso 8. Examinar la comunicación

    Seleccionar un paquete TCP de la conexión y utilizar:

    Clic derecho → Follow → TCP Stream

    Wireshark reconstruirá los datos intercambiados en esa conexión.

    Ejemplo simplificado de una conversación HTTP

    CLIENTE → SERVIDOR

    GET / HTTP/1.1
    Host: 127.0.0.1:8000
    User-Agent: curl

    SERVIDOR → CLIENTE

    HTTP/1.0 200 OK
    Content-Type: text/html
    
    <h1>Laboratorio de análisis de red</h1>
    <p>Esta página se transmite mediante HTTP.</p>

    Representación resumida. Las cabeceras y el contenido exacto dependerán de la versión de Python y de la petición realizada.

    Buscar el mensaje ASIR2026.

    ¿Por qué podemos leerlo? Porque HTTP, en este ejemplo, transmite los datos sin cifrado. Cualquier persona que tenga acceso legítimo a una captura de esa comunicación podrá interpretar su contenido.

    8. Comprender el funcionamiento de TCP

    TCP permite establecer conexiones y transmitir información de forma ordenada y fiable.

    En nuestra captura anterior, buscar el establecimiento de conexión.

    Establecimiento de conexión TCP: Three-way handshake

    El establecimiento de la conexión se realiza normalmente mediante tres segmentos:

    1. SYN: el cliente solicita establecer una conexión.
    2. SYN, ACK: el servidor acepta y confirma la solicitud.
    3. ACK: el cliente confirma, completando el establecimiento.

    Utilizar el filtro:

    tcp.flags.syn == 1

    Examinar los paquetes y localizar los segmentos SYN y SYN, ACK. Para identificar más fácilmente el inicio de una conexión concreta, también puede emplearse:

    tcp.flags.syn == 1 && tcp.flags.ack == 0

    Actividad: localizar los tres segmentos del establecimiento de la conexión HTTP y anotar los puertos de origen y destino.

    9. Comparar HTTP con HTTPS

    Paso 9. Visitar una página HTTPS

    Iniciar otra captura en la interfaz de red utilizada para acceder a Internet.

    Desde una terminal, ejecutar:

    curl -v https://example.com/

    Detener la captura y aplicar:

    tls

    Cuando la conexión utilice TLS sobre TCP y Wireshark lo reconozca, se podrán examinar mensajes del protocolo TLS.

    En una comunicación HTTPS normal podemos observar las direcciones IP, puertos y determinada información de la negociación, pero no leer directamente el contenido de las peticiones y respuestas HTTP cifradas.

    No todas las comunicaciones HTTPS utilizan TCP: algunas emplean HTTP/3 sobre QUIC, basado en UDP. En esta práctica usamos curl para facilitar la observación de una conexión concreta.

    Actividad: comparar lo que puede verse de la página web local HTTP con lo que se puede observar de una conexión HTTPS.

    10. Guardar y abrir archivos PCAP

    Paso 10. Guardar una captura

    En Wireshark, seleccionar:

    File → Save As

    Guardar con el nombre:

    practica-wireshark.pcapng

    El formato PCAPNG permite almacenar paquetes y determinados metadatos adicionales de la captura.

    Para volver a abrirla:

    File → Open → practica-wireshark.pcapng

    Paso 11. Examinar las estadísticas

    Dentro de Wireshark, abrir las siguientes opciones:

    MenúQué permite analizar
    Statistics → Protocol HierarchyDistribución de protocolos detectados
    Statistics → ConversationsComunicaciones entre pares de direcciones y puertos
    Statistics → EndpointsEquipos y extremos presentes en la captura
    Statistics → I/O GraphsEvolución del tráfico a lo largo del tiempo

    Comprobar qué protocolos aparecen y cuáles generan mayor cantidad de tráfico.

    11. Ejercicio final: análisis de un PCAP desconocido

    Realiza el anális del archivo pcap:

    Deberá responder:

    1. ¿Cuántos paquetes contiene la captura?
    2. ¿Qué protocolos aparecen?
    3. ¿Qué direcciones IP participan?
    4. ¿Se observan consultas DNS? ¿Qué nombres de dominio se consultan?
    5. ¿Existen conexiones TCP? ¿Qué puertos utilizan?
    6. ¿Hay tráfico HTTP sin cifrar? ¿Es posible identificar alguna página o recurso?
    7. ¿Se puede reconstruir alguna conversación TCP?
    8. ¿Qué información relevante puede obtenerse de la captura y qué información no es posible determinar?

    No debe suponerse que todos los archivos contendrán tráfico de todos los protocolos. El alumno debe distinguir entre evidencias observadas e hipótesis.

  • 2. Primeros conceptos de programación

    2. Primeros conceptos de programación

    2.1 — Algoritmos y pensamiento computacional

    1. ¿Qué es programar?

    Programar consiste en escribir una serie de instrucciones que un ordenador puede interpretar y ejecutar para resolver un problema o realizar una tarea.

    Un programa puede ser tan sencillo como mostrar un mensaje en pantalla o tan complejo como gestionar una tienda online, controlar un robot o ejecutar un videojuego.

    Antes de escribir código, debemos saber qué problema queremos resolver y qué pasos son necesarios.

    2. ¿Qué es un algoritmo?

    Un algoritmo es una secuencia ordenada y finita de instrucciones que permite resolver un problema.

    Los algoritmos no son exclusivos de la informática. Los utilizamos continuamente en nuestra vida cotidiana.

    Por ejemplo, para preparar un café:

    1. Preparar una taza.
    2. Calentar agua.
    3. Añadir café.
    4. Verter el agua.
    5. Remover.
    6. Servir.

    Estas instrucciones forman un algoritmo sencillo.

    Características de un algoritmo

    • Debe tener un objetivo definido.
    • Sus instrucciones deben ser claras.
    • Los pasos deben seguir un orden lógico.
    • Debe terminar después de un número finito de pasos.
    • Debe producir un resultado esperado cuando recibe datos válidos.

    3. Pensamiento computacional

    El pensamiento computacional es una forma de analizar problemas y diseñar soluciones que pueden ser ejecutadas por una persona o un ordenador.

    Se suele dividir en cuatro conceptos fundamentales.

    Descomposición

    Dividir un problema complejo en problemas más pequeños y sencillos.

    Reconocimiento de patrones

    Identificar similitudes entre problemas y soluciones conocidas.

    Abstracción

    Centrarse en la información importante y dejar de lado los detalles innecesarios.

    Diseño de algoritmos

    Establecer una secuencia de pasos para obtener la solución.

    4. Representar algoritmos

    Podemos representar algoritmos de diferentes maneras.

    Lenguaje natural

    Explicamos los pasos utilizando palabras.

    Ejemplo: calcular el precio de tres productos.

    1. Obtener el precio de cada producto.
    2. Sumar los tres precios.
    3. Mostrar el resultado.

    Pseudocódigo

    El pseudocódigo utiliza una escritura parecida a un lenguaje de programación, pero sin las reglas estrictas de Java.

    INICIO
    
        precio1 = 10
        precio2 = 20
        precio3 = 15
    
        total = precio1 + precio2 + precio3
    
        MOSTRAR total
    
    FIN

    Diagrama de flujo

    El algoritmo convertido a Java

    public class PrimerAlgoritmo {
    
        public static void main(String[] args) {
    
            int precio1 = 10;
            int precio2 = 20;
            int precio3 = 15;
    
            int total = precio1 + precio2 + precio3;
    
            System.out.println("Total: " + total);
        }
    }

    Resultado:

    Total: 45

    Todavía no es necesario comprender cada instrucción. Lo importante es observar que el código Java reproduce los pasos que hemos diseñado anteriormente.

    5. Actividad para los alumnos

    Diseña un algoritmo que calcule cuántos minutos hay en un determinado número de horas.

    Primero escribe los pasos en lenguaje natural, después en pseudocódigo y finalmente intenta representarlos mediante un diagrama de flujo.

    Explicar qué es un algoritmo, resolver el ejemplo de las tres compras y mostrar cómo se transforma el pseudocódigo en código Java.

    2.2 — Instrucciones y flujo de ejecución

    1. ¿Qué es una instrucción?

    Una instrucción es una orden que escribimos en un lenguaje de programación para indicar al ordenador qué debe hacer.

    En Java, muchas instrucciones terminan con punto y coma (;).

    int edad = 20;

    Esta instrucción declara una variable llamada edad y le asigna el valor 20.

    System.out.println("Hola mundo");

    Esta instrucción muestra un mensaje en la consola.

    2. Estructura básica de un programa Java

    public class MiPrograma {
    
        public static void main(String[] args) {
    
            System.out.println("Hola mundo");
    
        }
    }

    Vamos a identificar sus elementos:

    ElementoFunción
    public class MiProgramaDefine una clase llamada MiPrograma
    { }Delimitan bloques de código
    public static void main(String[] args)Método desde el que comienza la ejecución de este programa
    System.out.println()Muestra información por consola
    ;Finaliza una instrucción

    En este momento del curso no necesitamos profundizar en los conceptos de clases, métodos o programación orientada a objetos. Los estudiaremos posteriormente.

    3. Flujo secuencial

    Por defecto, Java ejecuta las instrucciones de un método de forma secuencial: desde la primera hasta la última, respetando el orden escrito.

    public class Secuencia {
    
        public static void main(String[] args) {
    
            System.out.println("Primera instrucción");
            System.out.println("Segunda instrucción");
            System.out.println("Tercera instrucción");
    
        }
    }

    Primera instrucción

    Segunda instrucción

    Tercera instrucción

    4. El orden de las instrucciones importa

    Observa este ejemplo:

    int numero = 10;
    
    System.out.println(numero);
    
    numero = 25;
    
    System.out.println(numero);

    Resultado:

    10
    25

    La primera salida muestra 10 porque todavía no se ha modificado la variable.

    La segunda salida muestra 25 porque hemos cambiado su contenido.

    5. Operaciones aritméticas básicas

    Java permite realizar operaciones matemáticas utilizando operadores.

    OperadorOperaciónEjemploResultado
    +Suma10 + 515
    -Resta10 - 55
    *Multiplicación10 * 550
    /División10 / 52
    %Resto10 % 31

    Ejemplo:

    int a = 10;
    int b = 3;
    
    System.out.println(a + b);
    System.out.println(a - b);
    System.out.println(a * b);
    System.out.println(a / b);
    System.out.println(a % b);

    Resultado:

    13
    7
    30
    3
    1

    Observa que 10 / 3 produce 3, no 3.3333. Esto ocurre porque ambos operandos son enteros. Veremos este comportamiento con más detalle al estudiar los tipos de datos.

    6. Actividad

    Crea un programa llamado OperacionesBasicas que declare dos números y muestre su suma, resta, multiplicación y división.

    Antes de ejecutarlo, escribe en papel qué resultados esperas obtener.

    2.3 — Datos, memoria y variables

    1. ¿Qué es una variable?

    Una variable es un elemento identificado por un nombre que utilizamos para almacenar y manejar un valor durante la ejecución de un programa.

    Por ejemplo:

    int edad = 25;

    Esta instrucción significa:

    • int: tipo de dato.
    • edad: nombre de la variable.
    • =: operador de asignación.
    • 25: valor que se almacena.

    2. Comprender las variables visualmente

    REPRESENTACIÓN CONCEPTUAL DE UNA VARIABLE

    La variable conserva su nombre, pero su valor puede cambiar.

    Esta es una representación simplificada para aprender. La organización real de la memoria de Java depende de la JVM y del tipo de dato.

    3. Declaración, inicialización y asignación

    Declaración

    int edad;

    Creamos una variable local, pero todavía no le hemos asignado un valor.

    Inicialización

    edad = 20;

    Le asignamos su primer valor.

    Declaración e inicialización juntas

    int edad = 20;

    Modificación del valor

    edad = 30;

    No escribimos int nuevamente porque la variable ya está declarada.

    4. Ejemplo práctico

    public class Variables {
    
        public static void main(String[] args) {
    
            int vidas = 3;
    
            System.out.println("Vidas iniciales: " + vidas);
    
            vidas = vidas - 1;
    
            System.out.println("Vidas restantes: " + vidas);
    
            vidas = vidas + 2;
    
            System.out.println("Vidas finales: " + vidas);
        }
    }

    Resultado:

    Vidas iniciales: 3
    Vidas restantes: 2
    Vidas finales: 4

    5. Reglas para nombrar variables

    En Java debemos respetar ciertas reglas:

    • No pueden comenzar por un número.
    • No pueden contener espacios.
    • No pueden utilizar palabras reservadas como class o int.
    • Distinguen entre mayúsculas y minúsculas.
    • Pueden contener letras, números, _ y $, aunque estos dos últimos caracteres no suelen utilizarse en nombres normales.

    Además, se recomienda utilizar la convención camelCase.

    int edadAlumno = 20;
    double precioProducto = 15.50;
    int numeroDeIntentos = 3;

    Evita nombres que no expliquen su significado:

    int x = 20;
    int a1 = 15;

    6. Actividad

    Crea un programa para un videojuego que tenga las variables nombreJugador, vidas, puntuacion y nivel.

    Asigna valores iniciales, muestra el estado del jugador y modifica su puntuación y sus vidas.

    2.4 — Tipos de datos primitivos en Java

    Java es un lenguaje de tipado estático. Esto significa que cada variable tiene un tipo definido que determina qué valores puede almacenar y qué operaciones admite.

    1. Los ocho tipos primitivos

    TipoTamañoUso habitual
    byte8 bitsEnteros pequeños
    short16 bitsEnteros de rango reducido
    int32 bitsEnteros habituales
    long64 bitsEnteros grandes
    float32 bitsDecimales de precisión simple
    double64 bitsDecimales de precisión doble
    char16 bitsUnidad de código UTF-16
    booleanNo fijado en bytes por JavaValores true o false

    2. Números enteros

    byte edad = 20;
    short temperatura = -150;
    int habitantes = 500000;
    long distancia = 9000000000L;

    La letra L indica que el literal numérico es de tipo long.

    Para la mayoría de los ejercicios iniciales utilizaremos int.

    3. Números decimales

    float altura = 1.75f;
    double precio = 19.99;

    La letra f indica que el literal decimal es de tipo float.

    Habitualmente utilizaremos double para los cálculos decimales de aprendizaje. Para cantidades monetarias que exijan exactitud decimal, Java dispone de BigDecimal, que veremos en un nivel posterior.

    4. Caracteres

    char letra = 'A';
    char respuesta = 'S';

    Los caracteres se escriben entre comillas simples.

    5. Booleanos

    boolean encendido = true;
    boolean terminado = false;

    Solo admiten los valores true y false.

    6. ¿Y String?

    Aunque lo utilizamos constantemente, String no es un tipo primitivo, sino una clase de Java que representa cadenas de texto.

    String nombre = "Carlos";
    String ciudad = "Madrid";

    La diferencia entre char y String es importante:

    char inicial = 'C';
    String nombre = "Carlos";

    7. Programa de ejemplo

    public class TiposDatos {
    
        public static void main(String[] args) {
    
            String nombre = "Laura";
            int edad = 19;
            double altura = 1.68;
            char grupo = 'A';
            boolean matriculado = true;
    
            System.out.println("Nombre: " + nombre);
            System.out.println("Edad: " + edad);
            System.out.println("Altura: " + altura);
            System.out.println("Grupo: " + grupo);
            System.out.println("Matriculado: " + matriculado);
        }
    }

    8. Actividad

    Representa los datos de un ordenador mediante variables: fabricante, memoria RAM en GB, precio, letra de unidad y estado de encendido.

    Escoge el tipo adecuado para cada dato.

    2.5 — Constantes

    1. ¿Qué es una constante?

    En programación llamamos constante a un valor que no debe cambiar durante la ejecución.

    En Java utilizamos la palabra reservada final para impedir que una variable reciba otra asignación después de inicializarse.

    final double IVA = 0.21;

    Si intentamos modificarla:

    IVA = 0.10;

    Eclipse mostrará un error de compilación.

    2. ¿Por qué utilizar constantes?

    Las constantes permiten evitar números repetidos en el código y facilitan su mantenimiento.

    Ejemplo:

    public class CalcularIVA {
    
        public static void main(String[] args) {
    
            final double IVA = 0.21;
    
            double precio = 100;
            double impuesto = precio * IVA;
            double total = precio + impuesto;
    
            System.out.println("Precio: " + precio);
            System.out.println("IVA: " + impuesto);
            System.out.println("Total: " + total);
        }
    }

    Resultado:

    Precio: 100.0
    IVA: 21.0
    Total: 121.0

    Por convención, los nombres de constantes suelen escribirse en mayúsculas, separando las palabras mediante guiones bajos.

    final int MAX_INTENTOS = 3;
    final double DESCUENTO_MAXIMO = 0.25;

    3. Actividad

    Crea un programa que calcule el área de un círculo utilizando una constante PI y una variable radio.

    Fórmula:

    2.6 — Conversión de tipos

    1. ¿Qué significa convertir un dato?

    Convertir un tipo consiste en transformar un valor para utilizarlo como otro tipo de dato.

    En Java existen conversiones automáticas y conversiones explícitas.

    2. Conversión automática

    Java puede realizar algunas conversiones cuando el tipo de destino admite el tipo de origen.

    int numero = 25;
    double decimal = numero;
    
    System.out.println(decimal);

    Resultado:

    25.0

    Aunque estas conversiones están permitidas, no todas garantizan conservar exactamente el valor. Por ejemplo, convertir un long muy grande a float puede perder precisión.

    3. Conversión explícita o casting

    Para realizar determinadas conversiones debemos indicar el tipo deseado.

    double precio = 19.95;
    
    int entero = (int) precio;
    
    System.out.println(entero);

    Resultado:

    19

    El casting a int elimina la parte decimal. No redondea.

    4. División de enteros y decimales

    int a = 10;
    int b = 4;
    
    double resultado1 = a / b;
    double resultado2 = (double) a / b;
    
    System.out.println(resultado1);
    System.out.println(resultado2);

    Resultado:

    2.0
    2.5

    En la primera división, Java opera con enteros y después convierte el resultado a double.

    En la segunda, convertimos uno de los operandos antes de realizar la división.

    5. Convertir texto en números

    Es frecuente recibir datos como texto y necesitar transformarlos.

    String texto = "125";
    
    int numero = Integer.parseInt(texto);
    
    System.out.println(numero + 10);

    Resultado:

    135

    Para números decimales:

    String texto = "12.5";
    
    double numero = Double.parseDouble(texto);

    Si el texto no representa un número válido, Java lanzará una excepción NumberFormatException.

    6. Convertir números en texto

    int edad = 25;
    
    String texto = String.valueOf(edad);

    7. Actividad

    Declara dos variables enteras, divide una entre otra y muestra la diferencia entre la división entera y la división decimal.

    2.7 — Salida de datos por consola

    La consola permite mostrar información durante la ejecución de nuestros programas.

    En Eclipse aparece normalmente en la vista Console.

    1. System.out.print()

    Muestra información sin añadir un salto de línea al final.

    System.out.print("Hola ");
    System.out.print("mundo");

    Resultado:

    Hola mundo

    2. System.out.println()

    Muestra información y añade un salto de línea.

    System.out.println("Hola");
    System.out.println("Mundo");

    Resultado:

    Hola
    Mundo

    3. System.out.printf()

    Permite mostrar información con un formato determinado.

    String nombre = "Ana";
    int edad = 20;
    double nota = 8.75;
    
    System.out.printf(
        "Nombre: %s, Edad: %d, Nota: %.2f%n",
        nombre, edad, nota
    );

    Especificadores habituales:

    EspecificadorSignificado
    %sTexto
    %dEntero
    %fDecimal
    %.2fDecimal con dos posiciones
    %nSalto de línea

    El separador decimal mostrado por printf depende de la configuración regional del entorno.

    4. Secuencias de escape

    System.out.println("Primera línea\nSegunda línea");
    System.out.println("Nombre:\tCarlos");
    System.out.println("Mensaje: \"Hola\"");
    System.out.println("Ruta: C:\\Java");
    SecuenciaFunción
    \nSalto de línea
    \tTabulación
    \"Comillas dobles
    \\Barra invertida

    5. Actividad

    Crea un programa que muestre una factura sencilla con el nombre del producto, precio, cantidad y total.

    Utiliza printf para mostrar los importes con dos decimales.

    2.8 — Entrada de datos por teclado con Scanner

    Hasta ahora hemos trabajado con datos escritos directamente en el código.

    Pero un programa resulta mucho más interesante cuando puede solicitar información al usuario.

    Para ello utilizaremos la clase Scanner.

    1. Importar Scanner

    import java.util.Scanner;

    Esta instrucción debe escribirse antes de la declaración de la clase.

    2. Crear un Scanner

    Scanner teclado = new Scanner(System.in);

    Este objeto nos permite leer información introducida desde la entrada estándar.

    3. Leer un número entero

    import java.util.Scanner;
    
    public class LeerEdad {
    
        public static void main(String[] args) {
    
            Scanner teclado = new Scanner(System.in);
    
            System.out.print("Introduce tu edad: ");
    
            int edad = teclado.nextInt();
    
            System.out.println("Tienes " + edad + " años");
    
            teclado.close();
        }
    }

    4. Métodos principales

    MétodoQué lee
    nextInt()Un entero
    nextDouble()Un decimal
    nextFloat()Un float
    next()Una palabra
    nextLine()Una línea completa
    nextBoolean()Un booleano

    5. Leer texto

    Scanner teclado = new Scanner(System.in);
    
    System.out.print("Introduce tu nombre: ");
    
    String nombre = teclado.nextLine();
    
    System.out.println("Hola " + nombre);
    
    teclado.close();

    6. Leer varios datos

    import java.util.Scanner;
    
    public class DatosAlumno {
    
        public static void main(String[] args) {
    
            Scanner teclado = new Scanner(System.in);
    
            System.out.print("Nombre: ");
            String nombre = teclado.nextLine();
    
            System.out.print("Edad: ");
            int edad = teclado.nextInt();
    
            System.out.print("Nota: ");
            double nota = teclado.nextDouble();
    
            System.out.println("----- DATOS -----");
            System.out.println("Nombre: " + nombre);
            System.out.println("Edad: " + edad);
            System.out.println("Nota: " + nota);
    
            teclado.close();
        }
    }

    nextDouble() interpreta el separador decimal según la configuración regional del Scanner. En un entorno español puede esperar una coma en lugar de un punto.

    7. Problema frecuente: nextInt() y nextLine()

    Observa este código:

    int edad = teclado.nextInt();
    String nombre = teclado.nextLine();

    Puede ocurrir que nextLine() lea el salto de línea que quedó pendiente tras introducir la edad, en lugar de esperar un nombre.

    Una solución sencilla es consumir ese salto de línea:

    int edad = teclado.nextInt();
    
    teclado.nextLine(); // Consumir el salto de línea pendiente
    
    String nombre = teclado.nextLine();

    Otra opción es leer todos los datos mediante nextLine() y convertir los valores numéricos con Integer.parseInt() o Double.parseDouble().

    8. Actividad

    Crea un programa que solicite el nombre del alumno y tres notas de examen.

    El programa debe calcular y mostrar la nota media.

    Vídeo propuesto — 2.8

    12–15 min

    Demostración en Eclipse: crear un proyecto, importar Scanner, leer datos de distintos tipos, calcular una media y solucionar el problema del salto de línea pendiente.

    2.9 — Comentarios y buenas prácticas iniciales

    1. ¿Qué son los comentarios?

    Los comentarios permiten añadir explicaciones al código fuente. El compilador los ignora.

    Comentario de una línea

    // Declaramos la edad del alumno
    int edad = 20;

    Comentario de varias líneas

    /*
     Este programa calcula
     el precio total de una compra.
    */

    Comentarios de documentación

    Java también dispone de comentarios Javadoc.

    /**
     * Programa de ejemplo para calcular una suma.
     */
    public class Suma {
    }

    2. Buenas prácticas

    Un programa no solo debe funcionar: también debe ser fácil de leer, comprender y mantener.

    Recomendaciones iniciales:

    • Utilizar nombres descriptivos.
    • Mantener una indentación uniforme.
    • Evitar instrucciones innecesariamente largas.
    • Dejar espacios alrededor de operadores.
    • Comentar el propósito de operaciones que no resulten evidentes.
    • No repetir código sin necesidad.
    • Utilizar constantes para valores fijos significativos.
    • Probar los programas con diferentes datos.

    Código poco legible

    int a=10;int b=20;int c=a+b;System.out.println(c);

    Código mejor organizado

    int primerNumero = 10;
    int segundoNumero = 20;
    
    int suma = primerNumero + segundoNumero;
    
    System.out.println("Resultado: " + suma);

    3. Formatear código en Eclipse

    Eclipse dispone de una función para organizar automáticamente la indentación y el formato del código.

    Ctrl + Shift + F

    Windows y Linux

    ⌘ + Shift + F

    macOS, con atajos predeterminados

    También puede accederse desde el menú Source → Format.

    4. Actividad

    Reescribe el siguiente código aplicando las buenas prácticas explicadas.

    public class prueba{
    public static void main(String[]args){
    int a=5;int b=8;int c=a*b;
    System.out.println(c);
    }}

    2.10 — Errores comunes al empezar a programar

    Al aprender programación es normal cometer errores. Lo importante es aprender a identificarlos, comprender sus causas y corregirlos.

    1. Tipos de errores

    Errores de compilación

    Impiden que Java compile el programa.

    int edad = 20

    Falta el punto y coma.

    Errores de ejecución

    Aparecen mientras el programa está funcionando.

    int numero = 10;
    int divisor = 0;
    
    System.out.println(numero / divisor);

    Provoca una ArithmeticException porque intentamos dividir un entero entre cero.

    Errores lógicos

    El programa se ejecuta, pero produce un resultado incorrecto.

    int precio = 100;
    int descuento = 20;
    
    int total = precio + descuento;

    Si queremos aplicar un descuento, deberíamos restarlo, no sumarlo.

    2. Errores frecuentes

    ErrorCausa habitual
    cannot find symbolNombre incorrecto o elemento no declarado
    Syntax errorError en la sintaxis
    Type mismatchTipos incompatibles
    NumberFormatExceptionTexto que no puede convertirse al número solicitado
    InputMismatchExceptionEntrada incompatible con lo esperado por Scanner
    ArithmeticExceptionOperación aritmética no válida, como división entera entre cero

    3. Java distingue mayúsculas y minúsculas

    int edad = 20;
    
    System.out.println(Edad);

    Este código produce un error porque edad y Edad son identificadores distintos.

    4. Confundir = con ==

    El operador = sirve para asignar valores.

    int numero = 10;

    El operador == sirve para comparar valores.

    System.out.println(numero == 10);

    Resultado:

    true

    Estudiaremos las comparaciones con mayor profundidad en el bloque de estructuras condicionales.

    5. Aprender a depurar con Eclipse

    Eclipse permite ejecutar programas paso a paso utilizando el depurador.

    Procedimiento básico:

    1. Abrir el archivo Java.
    2. Hacer doble clic en el margen izquierdo junto a una instrucción para colocar un breakpoint.
    3. Seleccionar Debug As → Java Application.
    4. Observar cómo se detiene la ejecución.
    5. Utilizar Step Over para avanzar instrucción por instrucción.
    6. Examinar los valores de las variables en la vista Variables.

    Vídeo propuesto — 2.10

    10–12 min

    Provocar varios errores intencionadamente, explicar los mensajes de Eclipse y demostrar cómo utilizar breakpoints y la vista de variables.

    Proyecto práctico final del Bloque 2

    Mi primer programa interactivo: ficha de un videojuego

    El objetivo es crear una pequeña aplicación de consola utilizando los conocimientos adquiridos durante el bloque.

    Requisitos

    El programa debe solicitar:

    • Nombre del videojuego.
    • Nombre del jugador.
    • Precio del videojuego.
    • Número de unidades que desea comprar.
    • Edad del jugador.

    El programa calculará el importe total de la compra, aplicará un IVA constante del 21 % (suponiendo que el precio introducido no lo incluye) y mostrará una ficha con los datos y resultados.

    Ejemplo de ejecución

    ===== TIENDA DE VIDEOJUEGOS =====
    
    Nombre del juego: Minecraft
    Nombre del jugador: Carlos
    Precio del juego: 25
    Unidades: 2
    Edad del jugador: 19
    
    ========== RESUMEN ==========
    
    Juego: Minecraft
    Jugador: Carlos
    Edad: 19
    
    Precio unitario: 25.00
    Unidades: 2
    Subtotal: 50.00
    IVA (21%): 10.50
    Total: 60.50

    Solución de referencia

    import java.util.Scanner;
    
    public class TiendaVideojuegos {
    
        public static void main(String[] args) {
    
            Scanner teclado = new Scanner(System.in);
    
            final double IVA = 0.21;
    
            System.out.println("===== TIENDA DE VIDEOJUEGOS =====");
    
            System.out.print("Nombre del juego: ");
            String juego = teclado.nextLine();
    
            System.out.print("Nombre del jugador: ");
            String jugador = teclado.nextLine();
    
            System.out.print("Precio del juego: ");
            double precio = Double.parseDouble(teclado.nextLine());
    
            System.out.print("Unidades: ");
            int unidades = Integer.parseInt(teclado.nextLine());
    
            System.out.print("Edad del jugador: ");
            int edad = Integer.parseInt(teclado.nextLine());
    
            // Calculamos los importes
            double subtotal = precio * unidades;
            double impuesto = subtotal * IVA;
            double total = subtotal + impuesto;
    
            // Mostramos el resumen
            System.out.println();
            System.out.println("========== RESUMEN ==========");
    
            System.out.println("Juego: " + juego);
            System.out.println("Jugador: " + jugador);
            System.out.println("Edad: " + edad);
    
            System.out.printf("Precio unitario: %.2f%n", precio);
            System.out.println("Unidades: " + unidades);
            System.out.printf("Subtotal: %.2f%n", subtotal);
            System.out.printf("IVA (21%%): %.2f%n", impuesto);
            System.out.printf("Total: %.2f%n", total);
    
            teclado.close();
        }
    }

    Para esta primera versión, se supone que el usuario introduce números válidos y cantidades positivas. La validación de entradas se incorporará en los siguientes bloques.

  • Práctica guiada: Docker Desktop, Portainer, Apache, PHP y MySQL

    Práctica guiada: Docker Desktop, Portainer, Apache, PHP y MySQL

    1. Presentación de la práctica

    En esta actividad aprenderemos a utilizar Docker para poner en funcionamiento una pequeña aplicación web formada por dos contenedores independientes.

    El primero tendrá un servidor Apache con PHP. El segundo alojará una base de datos MySQL con información sobre videojuegos de PC.

    No tendremos que programar la aplicación: el profesor facilitará todos los archivos necesarios. Nuestro trabajo será instalar, configurar, conectar, comprobar y publicar los contenedores.

    Arquitectura que vamos a construir

    2. Material facilitado por el profesor

    He preparado un ZIP con todos los archivos necesarios para los alumnos.

    Los alumnos deberán descomprimir el ZIP en una carpeta de Windows llamada practica_docker_juegos.

    Parte 1. Instalación de Docker Desktop

    Paso 1. Descargar Docker Desktop

    1. Accede a Docker Desktop.
    2. Descarga la versión para Windows.
    3. Ejecuta el instalador.
    4. Mantén seleccionada la opción de utilizar WSL 2, si aparece.
    5. Finaliza la instalación y reinicia Windows cuando sea necesario.

    Paso 2. Comprobar el funcionamiento

    Abre Docker Desktop y espera a que el motor de Docker esté funcionando.

    Abre PowerShell y ejecuta:

    docker --version

    Después:

    docker info

    Si este último comando muestra información del servidor, Docker está funcionando.

    Si indica que WSL no está instalado, abre PowerShell como administrador y ejecuta:

    wsl --install

    Reinicia el ordenador cuando termine la instalación y vuelve a abrir Docker Desktop.

    Parte 2. Crear nuestra cuenta de Docker Hub

    Docker Hub es un repositorio en Internet que permite descargar imágenes creadas por otras personas y publicar las nuestras.

    1. Accede a Docker Hub.
    2. Selecciona Sign up.
    3. Registra tu cuenta y verifica el correo electrónico.
    4. Accede a Docker Desktop.
    5. Pulsa Sign in e inicia sesión con la cuenta recién creada.

    Guarda tu nombre de usuario de Docker Hub. Lo necesitaremos al finalizar la práctica.

    Parte 3. Instalar Portainer

    Portainer es una herramienta de administración visual de Docker. Nos permitirá controlar contenedores, imágenes, redes y otros recursos.

    Portainer CE : gérer Docker et Podman visuellement

    Paso 1. Instalar la extensión

    1. Abre Docker Desktop.
    2. Entra en Settings → Extensions y comprueba que estén habilitadas.
    3. Abre Extensions Marketplace.
    4. Busca Portainer.
    5. Selecciona la extensión Portainer CE publicada por Portainer.io.
    6. Pulsa Install o Open in Docker Desktop, según la interfaz.

    También puedes localizarla en Portainer CE para Docker Desktop.

    Paso 2. Acceder a Portainer

    Abre Portainer desde el apartado Extensions. Si solicita crear un usuario administrador, completa el asistente y selecciona el entorno local de Docker.

    Comprueba que puedes acceder al apartado Containers.

    Esta extensión se encuentra disponible oficialmente para Docker Desktop.

    Paso 2. Configuración inicial

    Si Portainer solicita crear una cuenta administradora local:

    1. Introduce un nombre de usuario.
    2. Elige una contraseña.
    3. Completa el asistente.

    Selecciona el entorno Docker local disponible. Según la versión, puede aparecer como local o con un nombre similar.

    Paso 3. Conocer Portainer

    Localiza los apartados principales.

    SecciónUtilidad
    ContainersCrear, iniciar, detener y administrar contenedores
    ImagesDescargar, etiquetar y publicar imágenes
    NetworksCrear redes para comunicar contenedores
    VolumesAdministrar almacenamiento persistente
    EventsConsultar acontecimientos del entorno
    RegistriesConfigurar repositorios como Docker Hub

    Durante esta práctica trabajaremos principalmente en Containers, Images y Networks.

    Comprobación 3: puedes abrir Portainer y acceder al entorno Docker local.

    PARTE 4. NUESTRO PRIMER CONTENEDOR: HELLO WORLD

    Antes de crear nuestra aplicación web, vamos a comprobar cómo funciona Docker utilizando una imagen muy sencilla.

    La imagen hello-world muestra un mensaje y termina su ejecución.

    Paso 1. Descargar la imagen

    Desde Portainer:

    1. Selecciona Images.
    2. Busca el apartado para descargar imágenes (Pull image).
    3. Selecciona Docker Hub como registro.
    4. Introduce:
    hello-world:latest

    5. Pulsa Pull the image.

    Espera a que termine.

    La imagen debería aparecer en el listado de imágenes disponibles.

    Paso 2. Crear el contenedor

    1. Accede a Containers.
    2. Pulsa Add container.
    3. Introduce los siguientes valores:
    CampoValor
    Namemi-hola-mundo
    Imagehello-world:latest
    NetworkDefault
    Restart policyNo
    1. No es necesario publicar ningún puerto.
    2. Pulsa Deploy the container.

    Paso 3. Comprobar el resultado

    Abre la lista de contenedores.

    Localiza mi-hola-mundo.

    Es probable que aparezca con el estado Exited o detenido.

    Esto no significa que haya fallado. El programa ha terminado su ejecución.

    Selecciona el contenedor y entra en Logs.

    Busca el mensaje:

    Hello from Docker!

    Paso 4. Eliminar el contenedor

    Vuelve al listado de contenedores.

    1. Selecciona mi-hola-mundo.
    2. Pulsa Remove.
    3. Confirma la eliminación.

    Después abre Images y comprueba que la imagen hello-world sigue disponible.

    Pregunta de reflexión

    ¿Por qué hemos podido eliminar el contenedor sin eliminar también la imagen?

    PARTE 5. CREAR NUESTRA PRIMERA RED DOCKER

    Vamos a crear una red privada para que Apache y MySQL puedan comunicarse.

    La llamaremos:

    juegos-net

    Paso 1. Crear la red desde Portainer

    1. Entra en Networks.
    2. Pulsa Add network.
    3. Introduce:
    CampoValor
    Namejuegos-net
    Driverbridge
    InternalDesactivado
    Enable IPv6Desactivado
    1. Deja el resto de opciones por defecto.
    2. Pulsa Create the network.

    Paso 2. Verificarla

    Comprueba que juegos-net aparece en el listado.

    Más adelante conectaremos ambos contenedores a esta red.

    PARTE 6. CREAR EL CONTENEDOR APACHE

    Vamos a crear nuestro primer servidor web.

    Utilizaremos una imagen oficial de Apache HTTP Server llamada httpd.

    Paso 1. Descargar Apache

    Abre Images y descarga:

    httpd:2.4-trixie

    Esta variante está basada en Debian, por lo que podremos utilizar apt para instalar programas.

    Paso 2. Crear el contenedor

    Accede a Containers → Add container.

    Introduce los siguientes datos:

    CampoValor
    Nameweb-juegos
    Imagehttpd:2.4-trixie
    Restart policyUnless stopped

    En el apartado de publicación manual de puertos, añade:

    HostContainer
    808080

    En Advanced container settings → Network, selecciona:

    juegos-net

    En Advanced container settings → Env, añade esta variable:

    NameValue
    DB_HOSTbd-juegos

    Esta variable indicará al servidor web cómo localizar la base de datos cuando la creemos.

    Pulsa Deploy the container.

    Paso 3. Verificar el estado

    En el listado de contenedores debería aparecer:

    web-juegos     running

    Paso 4. Abrir nuestra primera página web

    En Windows, abre el navegador:http://localhost:8080

    Debería aparecer el mensaje:

    It works!

    Esto significa que Apache está funcionando.

    ¿Qué hemos conseguido?

    Hemos creado un servidor web Linux dentro de Docker, sin instalar Apache directamente en Windows.

    El puerto 8080 pertenece a nuestro equipo, mientras que Apache escucha en el puerto 80 dentro del contenedor.

    PARTE 7. INSTALAR PHP Y MYSQLI EN APACHE

    Para incorporar PHP al contenedor httpd y conectarlo con tu contenedor MySQL nstalaremos PHP-FPM dentro del mismo contenedor y configuraremos Apache para que le envíe los archivos .php. Apache admite esta integración mediante mod_proxy_fcgi. Apache HTTP Server Version 2.4

    Instalar solamente php y php-mysql no basta: puedes tener funcionando php -v y que Apache todavía no interprete PHP.

    Los siguientes pasos están preparados para un contenedor httpd basado en Debian, con apt.

    1. Abre una consola dentro de tu contenedor Apache

    En Portainer: Containers → tu contenedor Apache → Console → /bin/bash, como usuario root.

    También puedes entrar desde la terminal de tu ordenador, sustituyendo web-juegos por el nombre real:

    docker exec -it web-juegos bash

    2. Instala PHP-FPM y los conectores MySQL

    Dentro del contenedor:

    apt update
    apt install -y php-fpm php-cli php-mysql

    Comprueba la instalación:

    php -v
    php -m | grep -E 'mysqli|pdo_mysql'

    Deberían aparecer mysqli y pdo_mysql.

    3. Configura PHP-FPM

    Vamos a hacer que PHP-FPM escuche en el puerto 9000, únicamente dentro del propio contenedor.

    Primero, guarda la versión instalada en una variable:

    PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')

    Comprueba su valor:

    echo "$PHP_VERSION"

    Cambia la configuración del proceso PHP-FPM:

    sed -i 's|^[[:space:]]*listen[[:space:]]*=.*|listen = 127.0.0.1:9000|' \
    "/etc/php/$PHP_VERSION/fpm/pool.d/www.conf"

    Inícialo:

    service "php${PHP_VERSION}-fpm" start

    No necesitas publicar el puerto 9000 en Docker, porque Apache y PHP-FPM están dentro del mismo contenedor.

    4. Habilita la integración en Apache

    La configuración del Apache de la imagen httpd está normalmente en:

    /usr/local/apache2/conf/httpd.conf

    Haz una copia antes de modificarla:

    cp -n /usr/local/apache2/conf/httpd.conf \
    /usr/local/apache2/conf/httpd.conf.antes-php

    Activa los dos módulos necesarios:

    sed -i \
    's|^#LoadModule proxy_module modules/mod_proxy.so|LoadModule proxy_module modules/mod_proxy.so|' \
    /usr/local/apache2/conf/httpd.conf
    sed -i \
    's|^#LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so|LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so|' \
    /usr/local/apache2/conf/httpd.conf

    Crea un archivo específico para la configuración de PHP:

    cat > /usr/local/apache2/conf/extra/php-fpm.conf <<'EOF'
    DirectoryIndex index.php index.html
    
    <FilesMatch "\.php$">
        SetHandler "proxy:fcgi://127.0.0.1:9000"
    </FilesMatch>
    EOF

    Inclúyelo en la configuración principal, sin duplicarlo si ya existe:

    grep -qxF 'Include conf/extra/php-fpm.conf' /usr/local/apache2/conf/httpd.conf \
    || echo 'Include conf/extra/php-fpm.conf' >> /usr/local/apache2/conf/httpd.conf

    Comprueba la configuración:

    httpd -t

    Si aparece Syntax OK, aplica los cambios:

    httpd -k graceful

    5. Comprueba que Apache interpreta PHP

    Crea una página de prueba:

    cat > /usr/local/apache2/htdocs/prueba.php <<'EOF'
    <?php
    echo "<h1>PHP funciona dentro de mi contenedor httpd</h1>";
    echo "<p>Versión: " . PHP_VERSION . "</p>";
    echo "<p>MySQLi: " . (extension_loaded('mysqli') ? 'disponible' : 'no disponible') . "</p>";
    EOF

    Abre en tu navegador, usando el puerto que tengas publicado:

    http://localhost:puerto/prueba.php

    PARTE 9. CREAR EL CONTENEDOR MYSQL

    Ahora necesitaremos una base de datos donde almacenar nuestros videojuegos.

    Utilizaremos MySQL 8.4.

    Paso 1. Descargar MySQL

    En Portainer:

    1. Abre Images.
    2. Selecciona Docker Hub.
    3. Descarga:
    mysql:8.4

    4. Espera a que aparezca en el listado de imágenes.

    Paso 2. Crear el contenedor

    Accede a Containers → Add container.

    Introduce:

    CampoValor
    Namebd-juegos
    Imagemysql:8.4
    Restart policyUnless stopped

    En la publicación manual de puertos:

    HostContainer
    33073306

    En Advanced container settings → Network, selecciona:

    juegos-net

    Paso 3. Configurar las variables de MySQL

    Busca Advanced container settings → Env.

    Añade una por una las siguientes variables:

    NameValue
    MYSQL_ROOT_PASSWORDRootClase2026
    MYSQL_DATABASEjuegos
    MYSQL_USERalumno
    MYSQL_PASSWORDClaveJuegos2026

    Estas variables permiten inicializar MySQL automáticamente.

    Sus funciones son:

    • MYSQL_ROOT_PASSWORD: contraseña del administrador.
    • MYSQL_DATABASE: base de datos que queremos crear.
    • MYSQL_USER: usuario que utilizará la aplicación PHP.
    • MYSQL_PASSWORD: contraseña de ese usuario.

    Estas credenciales son únicamente de laboratorio. No deberán reutilizarse en servicios reales.

    Paso 4. Crear MySQL

    Pulsa Deploy the container.

    Regresa a Containers.

    Comprueba que aparece bd-juegos.

    Paso 5. Comprobar que MySQL está funcionando

    Selecciona bd-juegos y pulsa Logs.

    Espera a que aparezca un mensaje similar a:

    ready for connections

    Durante el primer arranque MySQL necesita inicializar sus archivos y crear la base de datos.

    Paso 6. Acceder a MySQL

    Dentro de Portainer:

    1. Selecciona bd-juegos.
    2. Accede a Console.
    3. Selecciona /bin/sh.
    4. Pulsa Connect.

    Escribe:

    mysql -u root -p

    Cuando solicite la contraseña, introduce:

    RootClase2026

    La contraseña no se mostrará mientras escribes.

    Una vez dentro, ejecuta:

    SHOW DATABASES;

    Debería aparecer la base de datos juegos.

    Para salir del cliente MySQL:

    exit;

    Comprobación: tenemos un servidor MySQL dentro de Docker, conectado a la misma red que Apache.

    PARTE 10. CREAR LA BASE DE DATOS DE VIDEOJUEGOS

    El profesor proporciona el siguiente código SQL.

    Este código realizará tres operaciones:

    1. Seleccionar la base de datos juegos.
    2. Crear una tabla llamada videojuegos.
    3. Insertar cinco videojuegos de ejemplo.

    Paso 1. Crear el archivo SQL dentro de MySQL

    Desde la consola Linux del contenedor bd-juegos, copia y pega todo este bloque:

    cat > /docker-entrypoint-initdb.d/01-juegos.sql <<'EOF'
    USE juegos;
    
    CREATE TABLE IF NOT EXISTS videojuegos (
        id INT AUTO_INCREMENT PRIMARY KEY,
        titulo VARCHAR(100),
        genero VARCHAR(50),
        plataforma VARCHAR(50)
    );
    
    INSERT INTO videojuegos
    (titulo, genero, plataforma)
    VALUES
    ('Minecraft', 'Sandbox', 'PC'),
    ('Age of Empires II', 'Estrategia', 'PC'),
    ('Portal 2', 'Puzles', 'PC'),
    ('Stardew Valley', 'Simulacion', 'PC'),
    ('Hollow Knight', 'Metroidvania', 'PC');
    EOF

    No es necesario escribir el código línea por línea.

    Selecciona el bloque completo, cópialo y pégalo en la consola.

    Hemos guardado el SQL en /docker-entrypoint-initdb.d/ para que también pueda utilizarse al crear nuevos contenedores a partir de nuestra futura imagen MySQL.

    Paso 2. Ejecutar el SQL

    Ejecuta:

    mysql -u root -p juegos < /docker-entrypoint-initdb.d/01-juegos.sql

    Introduce la contraseña del administrador:

    RootClase2026

    Si no aparece ningún error, el script habrá terminado.

    No vuelvas a ejecutar el script sobre esta misma base de datos, ya que volvería a insertar los registros.

    Paso 3. Verificar la tabla

    Accede nuevamente al cliente MySQL:

    mysql -u root -p

    Introduce la contraseña.

    Ahora ejecuta:

    USE juegos;

    Y después:

    SHOW TABLES;

    Debe aparecer:

    videojuegos

    Paso 4. Comprobar los datos

    SELECT * FROM videojuegos;

    El resultado esperado es:

    idtitulogeneroplataforma
    1MinecraftSandboxPC
    2Age of Empires IIEstrategiaPC
    3Portal 2PuzlesPC
    4Stardew ValleySimulacionPC
    5Hollow KnightMetroidvaniaPC

    Ya tenemos nuestra primera base de datos funcionando dentro de Docker.

    Sal del cliente MySQL escribiendo:

    exit;

    PARTE 11. INSTALAR NUESTRA APLICACIÓN PHP

    Nuestra aplicación será muy sencilla.

    Mostrará el título, el género y la plataforma de los videojuegos almacenados en MySQL.

    No tendremos que desarrollar el código; solamente introducirlo dentro del contenedor Apache.

    Paso 1. Volver al contenedor Apache

    En Portainer:

    1. Entra en Containers.
    2. Selecciona web-juegos.
    3. Pulsa Console.
    4. Abre /bin/sh.

    Paso 2. Crear el archivo PHP

    Copia todo este bloque de código y pégalo directamente en la consola de Portainer:

    cat > /usr/local/apache2/htdocs/index.php <<'EOF'
    <?php
    $conexion = new mysqli(
        getenv("DB_HOST") ?: "bd-juegos",
        "alumno",
        "ClaveJuegos2026",
        "juegos"
    );
    
    if ($conexion->connect_error) {
        die("Error de conexion con MySQL");
    }
    
    $resultado = $conexion->query("SELECT * FROM videojuegos");
    ?>
    
    <!DOCTYPE html>
    <html lang="es">
    <head>
        <meta charset="UTF-8">
        <title>Juegos de PC</title>
    </head>
    <body>
    
    <h1>Catalogo de videojuegos</h1>
    
    <?php
    while ($juego = $resultado->fetch_assoc()) {
        echo "<p>";
        echo htmlspecialchars($juego["titulo"]);
        echo " - ";
        echo htmlspecialchars($juego["genero"]);
        echo " - ";
        echo htmlspecialchars($juego["plataforma"]);
        echo "</p>";
    }
    ?>
    
    </body>
    </html>
    EOF

    El código es sencillo, pero contiene todo lo necesario:

    • Conexión con MySQL.
    • Consulta de los videojuegos.
    • Lectura de resultados.
    • Generación de una página HTML.

    El servidor MySQL se busca utilizando la variable DB_HOST, que configuramos desde Portainer.

    Paso 3. Eliminar la página predeterminada

    El servidor Apache incluye una página llamada index.html.

    Vamos a eliminarla para que se muestre directamente nuestro index.php.

    Desde la consola:

    rm /usr/local/apache2/htdocs/index.html

    Paso 4. Abrir la aplicación

    Accede desde Windows a:http://localhost:8080

    Debería mostrarse una página similar a la siguiente.

    La página real tendrá un diseño HTML mucho más sencillo, pero debe mostrar los mismos cinco videojuegos.

    Paso 5. Comprobar Portainer

    Vuelve a Portainer y verifica que los dos contenedores están activos:

    ContenedorEstadoPuerto Windows
    web-juegosRunning8080
    bd-juegosRunning3307

    Ambos deben pertenecer a juegos-net.

    ¿Cómo funciona la aplicación?

    Cuando escribimos localhost:8080 en el navegador:

    1. Docker dirige la petición al contenedor Apache.
    2. Apache identifica el archivo index.php.
    3. PHP ejecuta el programa.
    4. PHP solicita los videojuegos a MySQL.
    5. MySQL devuelve los registros.
    6. PHP genera la página HTML que vemos en el navegador.

    Hemos conseguido ejecutar una aplicación PHP conectada a una base de datos MySQL utilizando dos contenedores independientes.

    PARTE 12. CREAR NUESTRAS PROPIAS IMÁGENES

    Ahora aprenderemos una de las funciones más interesantes de Docker: crear nuestras propias imágenes a partir de los contenedores que hemos configurado.

    Esto nos permitirá reutilizar las modificaciones realizadas.

    Paso 1. Crear la imagen de Apache + PHP

    En Portainer:

    1. Accede a Containers.
    2. Selecciona web-juegos.
    3. Busca la opción Create image dentro de los detalles del contenedor.
    4. Introduce un nombre para la imagen:
    mi-web-juegos

    Y la etiqueta:

    v1

    Si Portainer muestra un único campo para el nombre y la etiqueta, introduce:

    mi-web-juegos:v1

    5. Confirma la creación de la imagen.

    Es posible que tarde un poco, ya que hemos instalado nuevos paquetes dentro del contenedor.

    Paso 2. Verificar la imagen

    Entra en Images.

    Debería aparecer:

    mi-web-juegos:v1

    Esta imagen contendrá Apache, PHP, el conector MySQL, nuestra configuración CGI y la página index.php.

    La configuración de arranque que hemos utilizado permite que Apache siga iniciándose con su comando predeterminado.

    Paso 3. Crear la imagen de MySQL

    Ahora repetiremos el procedimiento.

    1. Accede a Containers.
    2. Selecciona bd-juegos.
    3. Pulsa Create image.
    4. Introduce:
    mi-bd-juegos:v1

    5. Confirma la operación.

    Paso 4. Verificar las dos imágenes

    Abre Images.

    Deberías encontrar:

    mi-web-juegos:v1
    mi-bd-juegos:v1

    Importante sobre MySQL: las imágenes creadas desde contenedores no incorporan los datos guardados en volúmenes. Lo que hemos incluido en nuestra imagen MySQL es el archivo 01-juegos.sql, que permitirá reconstruir la tabla y sus datos al iniciar una base de datos nueva.

    PARTE 13. PUBLICAR NUESTRAS IMÁGENES EN DOCKER HUB

    Ahora compartiremos nuestras imágenes en Docker Hub.

    Para ello utilizaremos Portainer.

    Paso 1. Configurar el acceso a Docker Hub

    En Portainer, entra en el apartado Registries.

    Si Docker Hub aparece configurado, comprueba que permite utilizar tus credenciales. En caso contrario:

    1. Pulsa Add registry.
    2. Selecciona Docker Hub.
    3. Introduce tus datos de acceso. Si solicita un token de acceso, puedes crearlo desde la configuración de seguridad de tu cuenta de Docker Hub.
    4. Guarda la configuración.

    No publiques las contraseñas utilizadas para acceder a tu cuenta.

    Paso 2. Etiquetar nuestra primera imagen

    Abre Images y localiza:

    mi-web-juegos:v1

    Selecciona la opción Tag.

    Supongamos que tu usuario de Docker Hub es alumnoasir.

    Asigna la etiqueta:

    alumnoasir/mi-web-juegos:v1

    Sustituye alumnoasir por tu nombre real de usuario de Docker Hub.

    Paso 3. Publicar la imagen web

    En el listado de imágenes localiza la nueva etiqueta y selecciona Push, normalmente identificada por una flecha hacia arriba.

    Selecciona Docker Hub si Portainer solicita elegir un registro.

    Espera a que termine la subida.

    Paso 4. Publicar la imagen MySQL

    Repite el mismo procedimiento con:

    mi-bd-juegos:v1

    Asignándole:

    alumnoasir/mi-bd-juegos:v1

    Pulsa Push.

    La documentación de Portainer describe el proceso de etiquetado y publicación de imágenes desde su interfaz.

    Paso 5. Verificar Docker Hub

    Entra en:Docker Hub

    Dentro de tus repositorios deberías encontrar:

    RepositorioEtiqueta
    mi-web-juegosv1
    mi-bd-juegosv1

    Para esta práctica, crea los repositorios como privados si tu cuenta lo permite.

    Recuerda que la imagen web contiene credenciales de ejemplo y que Docker Hub no debe utilizarse para publicar secretos reales.

    Comprobación: hemos creado y publicado dos imágenes propias sin utilizar comandos Docker.

    PARTE 14. CREAR UNA SEGUNDA INSTALACIÓN CON OTROS PUERTOS

    En esta parte final vamos a demostrar que nuestras imágenes pueden utilizarse para crear otros contenedores.

    No eliminaremos los originales.

    Crearemos una segunda instalación de la aplicación.

    ElementoInstalación originalSegunda instalación
    Contenedor Apacheweb-juegosweb-juegos-copia
    Puerto web Windows80808081
    Contenedor MySQLbd-juegosbd-juegos-copia
    Puerto MySQL Windows33073308
    Red Dockerjuegos-netjuegos-copia-net

    Paso 1. Crear una segunda red

    Desde Portainer:

    1. Accede a Networks.
    2. Pulsa Add network.
    3. Nombre:
    juegos-copia-net
    1. Driver: bridge.
    2. Pulsa Create the network.

    Paso 2. Descargar nuestra imagen MySQL

    Accede a Images.

    Selecciona Docker Hub y busca:

    alumnoasir/mi-bd-juegos:v1

    Sustituye el nombre de usuario.

    Pulsa Pull the image.

    Aunque la imagen ya exista localmente, este paso permite comprobar que puede recuperarse desde Docker Hub.

    Paso 3. Crear el nuevo MySQL

    Accede a Containers → Add container.

    Introduce:

    CampoValor
    Namebd-juegos-copia
    Imagealumnoasir/mi-bd-juegos:v1
    Host port3308
    Container port3306
    Networkjuegos-copia-net

    En Advanced container settings → Env, configura:

    NameValue
    MYSQL_ROOT_PASSWORDRootClase2026
    MYSQL_DATABASEjuegos
    MYSQL_USERalumno
    MYSQL_PASSWORDClaveJuegos2026

    Deja los volúmenes con su configuración predeterminada y no reutilices el volumen de datos de la primera base de datos.

    Pulsa Deploy the container.

    Paso 4. Verificar la segunda base de datos

    Abre los registros de bd-juegos-copia.

    Espera a que MySQL termine de inicializarse.

    El archivo SQL que habíamos incluido en la imagen deberá ejecutarse durante la inicialización de esta base de datos nueva.

    Para comprobarlo, abre Console dentro de bd-juegos-copia y entra en MySQL:

    mysql -u root -p

    Introduce la contraseña:

    RootClase2026

    Ejecuta:

    USE juegos;
    SELECT * FROM videojuegos;

    Deben aparecer los cinco videojuegos.

    Sal de MySQL:

    exit;

    Paso 5. Descargar nuestra imagen Apache + PHP

    En Portainer, abre Images.

    Busca:

    alumnoasir/mi-web-juegos:v1

    Pulsa Pull the image.

    Paso 6. Crear el segundo contenedor Apache

    Entra en Containers → Add container.

    Introduce:

    CampoValor
    Nameweb-juegos-copia
    Imagealumnoasir/mi-web-juegos:v1
    Host port8081
    Container port80
    Networkjuegos-copia-net

    En Advanced container settings → Env añade:

    NameValue
    DB_HOSTbd-juegos-copia

    Esta variable es importante porque la segunda página web debe conectarse al segundo contenedor MySQL, no al primero.

    Pulsa Deploy the container.

    Paso 7. Comprobar las dos páginas

    Ahora abre las siguientes direcciones:

    Primera instalación. http://localhost:8080

    Apache y MySQL originales

    Segunda instalación. http://localhost:8081

    Contenedores creados a partir de nuestras imágenes

    Ambas páginas deberán mostrar los mismos cinco videojuegos.

    Hemos conseguido crear dos instalaciones independientes a partir de nuestras imágenes.

    Paso 8. Comprobación final desde Portainer

    Accede a Containers.

    Deberías tener cuatro contenedores activos:

    ContenedorEstado esperado
    web-juegosRunning
    bd-juegosRunning
    web-juegos-copiaRunning
    bd-juegos-copiaRunning

    PARTE 15. PEQUEÑAS COMPROBACIONES PRÁCTICAS

    Estas actividades permiten comprobar que hemos entendido el funcionamiento de los contenedores.

    Ejercicio 1. Detener Apache

    Desde Portainer, detén el contenedor web-juegos.

    Intenta abrir:

    http://localhost:8080

    ¿Qué sucede?

    Ahora vuelve a iniciar el contenedor desde Portainer y comprueba que la página funciona nuevamente.

    Ejercicio 2. Detener MySQL

    Detén bd-juegos.

    Vuelve a abrir la primera página web.

    ¿Qué sucede cuando PHP intenta consultar la base de datos?

    Inicia nuevamente MySQL y comprueba que la web recupera su funcionamiento.

    Ejercicio 3. Comprobar la independencia de las instalaciones

    Detén web-juegos y bd-juegos, pero deja funcionando los dos contenedores de la segunda instalación.

    Comprueba si sigue funcionando:

    http://localhost:8081

    Explica por qué ocurre.

    Vuelve a iniciar los contenedores originales cuando termines.

    Preguntas de comprensión

    Responde brevemente a las siguientes cuestiones:

    • ¿Por qué no debemos considerar una imagen Docker como una copia de seguridad de la base de datos?
    • ¿Qué es una imagen Docker?
    • ¿Qué es un contenedor?
    • ¿Por qué Hello World aparece detenido después de ejecutarse?
    • ¿Qué utilidad tiene Portainer?
    • ¿Para qué sirve una red Docker?
    • ¿Por qué hemos creado dos contenedores diferentes?
    • ¿Qué significa publicar el puerto 8080:80?
    • ¿Para qué utilizamos las variables de entorno de MySQL?
    • ¿Qué función tiene el conector MySQLi de PHP?
    • ¿Qué diferencia existe entre detener y eliminar un contenedor?
    • ¿Para qué sirve crear una imagen de un contenedor que hemos configurado?
    • ¿Qué ventajas tiene subir nuestra imagen a Docker Hub?
    • ¿Por qué podemos ejecutar dos servidores Apache utilizando los puertos 8080 y 8081?

    PARTE 17. CONCLUSIONES

    Con esta práctica hemos aprendido a utilizar Docker de manera visual a través de Portainer.

    Hemos instalado y configurado dos servicios independientes, Apache con PHP y MySQL, y hemos conseguido que se comuniquen mediante una red privada.

    También hemos creado nuestras propias imágenes, las hemos publicado en Docker Hub y las hemos reutilizado para desplegar una segunda instalación sin repetir manualmente la instalación de los programas.

    Lo más importante es comprender que una imagen permite crear contenedores, mientras que un contenedor es una instancia en ejecución de esa imagen.

    En una próxima práctica aprenderemos a realizar estas mismas operaciones desde la terminal y, posteriormente, a utilizar Docker Compose para automatizar el despliegue de varios servicios.

  • Proyecto Loot Figures — Parte 1: implantación en XAMPP y Ubuntu Server

    Proyecto Loot Figures — Parte 1: implantación en XAMPP y Ubuntu Server

    1. Presentación del proyecto

    En esta actividad vamos a implantar Loot Figures, una tienda web de figuras de plástico inspiradas en personajes de videojuegos.

    La aplicación ya está desarrollada. Nuestro trabajo consistirá en examinar sus archivos, preparar los entornos necesarios, configurar la base de datos y conseguir que funcione en dos instalaciones diferentes:

    1. En nuestro ordenador, utilizando XAMPP.
    2. En la máquina virtual Ubuntu Server que hemos preparado en clase.

    Además de conseguir que la tienda se abra en el navegador, tendremos que comprobar sus funciones y documentar el procedimiento.

    Implantar una aplicación no consiste únicamente en copiar archivos. También debemos identificar sus requisitos, configurar los servicios, preparar la base de datos, ajustar permisos y comprobar que todo funciona correctamente.


    2. Objetivos de aprendizaje

    Al terminar esta primera parte, deberás ser capaz de:

    • Identificar los archivos y las carpetas de una aplicación PHP.
    • Abrir un proyecto completo en VS Code o PhpStorm.
    • Reconocer la función de los modelos, los DAO y los archivos que reciben datos.
    • Diferenciar el código PHP del código que ejecuta el navegador.
    • Preparar un entorno con Apache, PHP y MySQL o MariaDB.
    • Crear una base de datos e importar su estructura.
    • Configurar la conexión entre PHP y la base de datos.
    • Ejecutar un script de carga de datos.
    • Configurar Apache para servir la carpeta pública del proyecto.
    • Implantar la misma aplicación en Windows y Ubuntu Server.
    • Comprobar su funcionamiento y resolver incidencias.
    • Elaborar documentación técnica que permita repetir la instalación.

    3. Material necesario

    Para realizar la actividad necesitarás:

    • El archivo del proyecto: loot-figures-php-clase.zip.
    • Un ordenador con XAMPP.
    • VS Code o PhpStorm.
    • Un navegador web.
    • La máquina virtual Ubuntu Server utilizada en clase.
    • Un usuario de Ubuntu con permisos para ejecutar sudo.
    • Conexión entre tu ordenador y la máquina virtual.

    La aplicación utiliza PHP 8.x, una base de datos MySQL o MariaDB y las extensiones PHP necesarias para trabajar con mysqli, sesiones y archivos.

    En esta primera parte no utilizaremos Docker. Vamos a preparar los servicios directamente para comprender qué necesita la aplicación.

    4. Resultado que debes conseguir

    Al finalizar, tendrás dos instalaciones independientes:

    EntornoAplicaciónBase de datosAcceso
    Ordenador con XAMPPCopia local del proyectoBase de datos de XAMPPhttp://localhost:8080
    Máquina virtual Ubuntu ServerCopia del proyecto en el servidorBase de datos de Ubuntuhttp://IP_DEL_SERVIDOR:8080

    Cada instalación tendrá su propia base de datos.

    Los cambios realizados en una instalación no aparecerán automáticamente en la otra.

    Utilizaremos el puerto 8080 para la tienda. De esta forma, podemos conservar las páginas o servicios que ya estén utilizando el puerto 80.


    Bloque A. Examinar el proyecto antes de instalarlo

    5. Descargar y descomprimir los archivos

    Descarga el ZIP proporcionado por el profesor y descomprímelo.

    La carpeta principal del proyecto se llama:

    loot-figures-clase

    Comprueba que dentro aparecen, entre otras, estas carpetas:

    app
    config
    database
    docs
    public
    scripts

    No comiences modificando archivos. Primero vamos a identificar qué contiene el proyecto.

    6. Abrir el proyecto en VS Code o PhpStorm

    Opción A: VS Code

    1. Abre VS Code.
    2. Selecciona Archivo → Abrir carpeta.
    3. Elige la carpeta principal loot-figures-clase.
    4. Comprueba que aparecen todas las carpetas del proyecto en el explorador lateral.
    5. Abre el archivo README.md.

    Opción B: PhpStorm

    1. Abre PhpStorm.
    2. Selecciona Open / Abrir.
    3. Elige la carpeta principal loot-figures-clase.
    4. Espera a que termine de analizar el proyecto.
    5. Abre el archivo README.md.

    Debes abrir la carpeta completa, no únicamente un archivo PHP ni solo la carpeta public.

    El editor nos permite consultar y modificar el código. Para ejecutar la aplicación necesitaremos un servidor PHP.

    7. Reconocer las carpetas del proyecto

    Examina la estructura y completa una tabla como esta en tu documentación:

    Carpeta o archivo¿Para qué sirve?
    app/modelosClases que representan clientes, productos, pedidos y mensajes
    app/daoClases que realizan las consultas a la base de datos
    app/Conexion.phpCódigo que abre la conexión con MySQL mediante mysqli
    configConfiguración de la aplicación
    database/schema.sqlInstrucciones SQL para crear las tablas
    publicArchivos que debe servir Apache al navegador
    public/apiArchivos PHP que reciben peticiones y devuelven datos
    public/partialsPartes comunes: cabecera, navegación y pie
    public/assetsJavaScript, CSS, logo e imágenes
    public/uploadsImágenes subidas desde la administración
    scripts/seed.phpScript que introduce los datos de ejemplo
    docsDocumentación del proyecto

    Preguntas que debes responder

    1. ¿Qué diferencia hay entre app/modelos y app/dao?
    2. ¿Qué archivo contiene la configuración de la base de datos?
    3. ¿Qué carpeta debe publicar Apache?
    4. ¿Por qué la configuración y los scripts de instalación no deben quedar dentro del directorio público?
    5. ¿Qué diferencia hay entre un archivo PHP y un archivo JavaScript?
    6. ¿Qué función tienen header.php, nav.php y footer.php?

    8. Seguir una operación sencilla

    Abre estos archivos:

    public/api/contacto.php
    app/modelos/Mensaje.php
    app/dao/MensajeDAO.php

    Localiza el recorrido de los datos:

    1. Se reciben los datos del formulario.
    2. Se validan.
    3. Se crea un objeto Mensaje.
    4. Se crea el DAO.
    5. El DAO inserta el mensaje en la base de datos.
    6. PHP devuelve una respuesta JSON.

    Identifica estas instrucciones:

    $mensaje = new Mensaje(0, $nombre, $email, $texto);
    
    $dao = new MensajeDAO($conexion);
    
    $dao->insertar($mensaje);

    No es necesario comprender todavía todo el proyecto. En esta fase debes poder explicar qué responsabilidad tiene cada archivo.

    Evidencia: incluye una captura del proyecto abierto en el editor y una explicación breve de este recorrido.


    Bloque B. Implantación local con XAMPP

    9. Preparar XAMPP

    Si todavía no tienes XAMPP, descárgalo desde su web oficial:

    Descargar XAMPP

    Las instrucciones siguientes utilizan una instalación de Windows situada en:

    C:\xampp

    Si tu instalación está en otra ubicación, adapta las rutas.

    Abre el panel de control de XAMPP e inicia:

    • Apache.
    • MySQL.

    Aunque el panel muestre el nombre MySQL, XAMPP puede incluir MariaDB como motor de base de datos.

    Comprueba estas direcciones:

    http://localhost
    http://localhost/phpmyadmin

    phpMyAdmin nos permitirá gestionar la base de datos de esta instalación. En la configuración básica de XAMPP se accede a él desde el propio equipo. apachefriends.org

    Evidencia: captura del panel con Apache y MySQL iniciados.

    10. Colocar el proyecto

    Crea esta carpeta:

    C:\proyectos

    Coloca dentro el proyecto descomprimido:

    C:\proyectos\loot-figures-clase

    Comprueba que existe:

    C:\proyectos\loot-figures-clase\public\index.php

    La carpeta del proyecto estará fuera de htdocs. Configuraremos Apache para que publique únicamente su carpeta public.

    Después, abre esta ubicación definitiva en VS Code o PhpStorm.

    11. Configurar Apache para la tienda

    11.1. Revisar la carga de VirtualHosts

    Abre este archivo:

    C:\xampp\apache\conf\httpd.conf

    Busca la línea:

    Include conf/extra/httpd-vhosts.conf

    Si aparece comentada con #, elimina ese carácter.

    Guarda el archivo.

    11.2. Añadir la configuración

    Abre:

    C:\xampp\apache\conf\extra\httpd-vhosts.conf

    Añade al final:

    Listen 8080
    
    <VirtualHost *:8080>
        ServerName localhost
    
        DocumentRoot "C:/proyectos/loot-figures-clase/public"
    
        <Directory "C:/proyectos/loot-figures-clase/public">
            Options -Indexes
            AllowOverride All
            Require local
        </Directory>
    </VirtualHost>

    Importante: si ya existe una instrucción Listen 8080 en la configuración de Apache, no la añadas otra vez.

    En la configuración de Apache hemos escrito las rutas con /.

    ¿Qué significa esta configuración?

    InstrucciónFunción
    Listen 8080Apache escucha también en el puerto 8080
    VirtualHost *:8080Define el sitio que responderá en ese puerto
    DocumentRootIndica la carpeta pública de la aplicación
    AllowOverride AllPermite aplicar las instrucciones de .htaccess
    Require localPermite el acceso desde el propio ordenador

    Detén y vuelve a iniciar Apache desde XAMPP.

    Si no arranca, revisa los mensajes y registros del servicio antes de continuar.

    12. Crear la base de datos

    Abre:

    http://localhost/phpmyadmin

    Selecciona la pestaña SQL y ejecuta:

    CREATE DATABASE loot_figures
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;
    
    CREATE USER 'loot_app'@'localhost'
    IDENTIFIED BY 'CambiaEstaClaveBD!';
    
    GRANT SELECT, INSERT, UPDATE, DELETE
    ON loot_figures.*
    TO 'loot_app'@'localhost';

    Hemos creado:

    • La base de datos loot_figures.
    • El usuario loot_app.
    • Los permisos necesarios para las operaciones de la tienda.

    La aplicación utilizará este usuario, no la cuenta root.

    Si la base o el usuario ya existen por un intento anterior, revisa lo que has creado antes de repetir las instrucciones. No elimines bases de datos para resolver un error sin comprobar su contenido.

    13. Importar las tablas

    En phpMyAdmin:

    1. Selecciona la base de datos loot_figures.
    2. Abre la pestaña Importar.
    3. Selecciona el archivo:
    C:\proyectos\loot-figures-clase\database\schema.sql
    1. Ejecuta la importación.
    2. Comprueba que aparecen estas tablas:
    users
    products
    orders
    order_items
    messages
    login_attempts

    La importación debe realizarse con la cuenta de administración de la base de datos, porque crea las tablas.

    No vuelvas a importar el esquema sobre una instalación que ya tiene sus tablas.

    Evidencia: captura de la base de datos con las tablas creadas.

    14. Crear el archivo de configuración

    Dentro de config, copia:

    config.example.php

    y llama a la copia:

    config.php

    Puedes hacerlo desde el editor o el explorador de archivos.

    Edita config.php y utiliza estos valores:

    <?php
    
    return [
        'db' => [
            'host' => 'localhost',
            'port' => 3306,
            'name' => 'loot_figures',
            'user' => 'loot_app',
            'password' => 'CambiaEstaClaveBD!'
        ],
    
        'secure_cookie' => false,
        'debug' => false
    ];

    Si tu servicio de base de datos utiliza otro puerto, debes cambiar el valor port.

    En esta práctica trabajamos con HTTP local, por eso secure_cookie permanece en false.

    No copies config.docker.php: esa configuración corresponde a otro entorno.

    15. Comprobar PHP y cargar los datos de ejemplo

    Abre una terminal de PowerShell.

    Comprueba el PHP de XAMPP:

    & "C:\xampp\php\php.exe" -v

    Después, sitúate en el proyecto:

    Set-Location "C:\proyectos\loot-figures-clase"

    Ejecuta:

    & "C:\xampp\php\php.exe" scripts/seed.php

    El script creará los usuarios y productos de demostración.

    Si aparece un error, comprueba:

    • Que MySQL está iniciado.
    • Que existe config/config.php.
    • Que la contraseña y el puerto son correctos.
    • Que has importado las tablas.

    El archivo seed.php se ejecuta desde la terminal. No tienes que abrirlo mediante una URL.

    16. Abrir la tienda en localhost

    Abre:

    http://localhost:8080

    La tienda debería mostrar la home, el logo, las imágenes y los productos.

    No abras index.php haciendo doble clic. Tampoco utilices Live Server para ejecutar esta aplicación PHP. Debes acceder mediante Apache.

    Cuentas de demostración

    RolCorreoContraseña
    Administradoradmin@loot.localAdminDemo!2026
    Clientecliente@loot.localClienteDemo!2026

    Estas cuentas utilizan direcciones ficticias. No necesitas disponer de esos correos.

    17. Comprobar el funcionamiento local

    Realiza estas pruebas:

    Zona pública

    1. Abre la home.
    2. Accede al catálogo.
    3. Busca una figura.
    4. Filtra por rareza.
    5. Cambia el orden de los productos.
    6. Abre el detalle de una figura.

    Registro y cuenta

    1. Crea una cuenta con datos ficticios.
    2. Cierra sesión.
    3. Vuelve a entrar.
    4. Accede a tu perfil.
    5. Cambia el teléfono y comprueba que se guarda.

    Carrito y compra

    1. Entra como cliente.
    2. Añade una figura al carrito.
    3. Cambia la cantidad.
    4. Añade otra figura.
    5. Elimina una línea.
    6. Introduce una dirección ficticia.
    7. Confirma el pedido.
    8. Comprueba que aparece en el historial.

    La compra es una simulación y no realiza ningún cobro.

    Administración

    1. Entra como administrador.
    2. Accede al listado de productos.
    3. Modifica el nombre, precio o stock de una figura.
    4. Comprueba el resultado en el catálogo.
    5. Crea un usuario de prueba.
    6. Modifica sus datos.
    7. Cambia su contraseña.
    8. Comprueba que puede iniciar sesión con la nueva contraseña.
    9. Sube una fotografía a un producto.

    Contacto

    1. Envía un mensaje desde la página de contacto.
    2. Entra como administrador.
    3. Comprueba que el mensaje aparece en el panel.

    El mensaje se guarda en la base de datos. No se envía un correo electrónico.

    18. Observar una petición JSON

    Abre las herramientas de desarrollo del navegador y selecciona la pestaña Red / Network.

    Recarga el catálogo y localiza la petición a:

    api/listar_productos.php

    Examina su respuesta.

    Responde:

    1. ¿Qué información contiene el JSON?
    2. ¿Qué campos identifican el nombre y el precio del producto?
    3. ¿El navegador recibe el código fuente PHP?
    4. ¿Qué archivo JavaScript utiliza esos datos para construir las tarjetas?

    Después, visita:

    http://localhost:8080/ejemplo_clase.php

    Este formulario permite observar un envío clásico por POST.

    Utiliza otra cuenta ficticia para probarlo. Después del envío verás la respuesta JSON directamente en el navegador.

    Explica la diferencia entre este formulario y el registro habitual de la tienda.


    Bloque C. Implantación en Ubuntu Server

    19. Preparar la máquina virtual

    Utiliza la máquina virtual creada en clase.

    Antes de comenzar:

    1. Comprueba que puedes iniciar sesión.
    2. Identifica tu usuario.
    3. Comprueba que puedes ejecutar sudo.
    4. Anota la versión de Ubuntu.
    5. Comprueba la dirección IP.
    6. Realiza una instantánea si el programa de virtualización lo permite.

    Ejecuta:

    whoami
    cat /etc/os-release
    ip -br a

    También puedes consultar:

    hostname -I

    En los siguientes ejemplos utilizaremos:

    192.168.1.50

    Es una IP de ejemplo. Debes sustituirla por la dirección real de tu servidor.

    20. Comprobar la comunicación con la máquina virtual

    Desde tu ordenador, intenta acceder por SSH si ya lo configuraste en clase:

    ssh alumno@192.168.1.50

    Sustituye alumno por tu usuario real.

    Si la máquina utiliza NAT

    Puede ser necesario configurar una redirección de puertos en el programa de virtualización.

    Por ejemplo:

    Puerto del ordenadorPuerto de UbuntuUso
    222222SSH
    80818080Tienda

    Con esa configuración, SSH se utilizaría así:

    ssh -p 2222 alumno@127.0.0.1

    Y la tienda de Ubuntu se abriría desde el ordenador mediante:

    http://localhost:8081

    Si utilizas una red en la que el ordenador puede acceder directamente a la IP de Ubuntu, no necesitas esas redirecciones.

    Documenta el modo de red que estás utilizando.

    21. Instalar los servicios necesarios

    En Ubuntu Server ejecuta:

    sudo apt update

    Después:

    sudo apt install apache2 mysql-server php libapache2-mod-php php-mysql php-cli unzip

    Apache atenderá las peticiones web; PHP ejecutará el código del servidor; MySQL almacenará los datos. La documentación de Ubuntu recoge la instalación de Apache y de PHP con su módulo para Apache. Ubuntu Server documentation

    Si algunos paquetes ya estaban instalados, revisa el resultado del comando: no es necesario reinstalarlos manualmente.

    Activa los servicios:

    sudo systemctl enable --now apache2
    sudo systemctl enable --now mysql

    Comprueba su estado:

    systemctl status apache2 --no-pager
    systemctl status mysql --no-pager

    Ambos deben aparecer activos.

    Evidencia: captura de los servicios funcionando.

    22. Comprobar la versión y las extensiones de PHP

    Ejecuta:

    php -v

    Después:

    php -m

    Comprueba que aparecen:

    mysqli
    mysqlnd
    fileinfo
    session

    Anota la versión de PHP de Ubuntu y compárala con la de XAMPP.

    23. Transferir el proyecto al servidor

    Para esta instalación, utiliza el ZIP original entregado por el profesor. Crearemos una base de datos nueva en Ubuntu.

    Puedes transferir el archivo mediante SFTP o con scp.

    Ejemplo, ejecutado desde tu ordenador y desde la carpeta donde está el ZIP:

    scp loot-figures-php-clase.zip alumno@192.168.1.50:~/

    Si utilizas la redirección SSH del ejemplo anterior:

    scp -P 2222 loot-figures-php-clase.zip alumno@127.0.0.1:~/

    Observa que scp utiliza -P mayúscula para indicar el puerto.

    En Ubuntu comprueba que ha llegado:

    ls -lh ~/loot-figures-php-clase.zip

    Descomprime:

    cd ~
    unzip loot-figures-php-clase.zip

    Comprueba:

    ls ~/loot-figures-clase

    Mueve el proyecto:

    sudo mv ~/loot-figures-clase /var/www/

    La ubicación final será:

    /var/www/loot-figures-clase

    Si ya existe una carpeta con ese nombre por un intento anterior, revisa su contenido antes de repetir el movimiento.

    24. Configurar Apache en Ubuntu

    24.1. Añadir el puerto

    Abre:

    sudo nano /etc/apache2/ports.conf

    Añade:

    Listen 8080

    Conserva las instrucciones existentes y no dupliques Listen 8080 si ya aparece.

    24.2. Crear el sitio

    Ejecuta:

    sudo nano /etc/apache2/sites-available/loot-figures.conf

    Introduce:

    <VirtualHost *:8080>
        DocumentRoot /var/www/loot-figures-clase/public
    
        <Directory /var/www/loot-figures-clase/public>
            Options -Indexes
            AllowOverride All
            Require all granted
        </Directory>
    
        ErrorLog ${APACHE_LOG_DIR}/loot-figures-error.log
        CustomLog ${APACHE_LOG_DIR}/loot-figures-access.log combined
    </VirtualHost>

    Guarda el archivo.

    Activa el sitio:

    sudo a2ensite loot-figures.conf

    Comprueba la sintaxis:

    sudo apache2ctl configtest

    Si aparece:

    Syntax OK

    reinicia Apache:

    sudo systemctl restart apache2

    No desactives los sitios anteriores de clase: esta tienda utiliza otro puerto.

    25. Crear la base de datos en Ubuntu

    Accede a MySQL:

    sudo mysql

    La instalación de MySQL mediante los paquetes de Ubuntu permite administrar el servicio y crear cuentas para las aplicaciones. Ubuntu Server documentation

    Ejecuta:

    CREATE DATABASE loot_figures
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;
    
    CREATE USER 'loot_app'@'localhost'
    IDENTIFIED BY 'CambiaEstaClaveBD!';
    
    GRANT SELECT, INSERT, UPDATE, DELETE
    ON loot_figures.*
    TO 'loot_app'@'localhost';

    Sal:

    EXIT;

    Importa las tablas:

    sudo mysql loot_figures < /var/www/loot-figures-clase/database/schema.sql

    Comprueba el resultado:

    sudo mysql -e "SHOW TABLES FROM loot_figures;"

    Debes encontrar las mismas tablas que en XAMPP.

    26. Configurar la conexión de la aplicación

    Copia la plantilla:

    sudo cp /var/www/loot-figures-clase/config/config.example.php /var/www/loot-figures-clase/config/config.php

    Edita:

    sudo nano /var/www/loot-figures-clase/config/config.php

    Utiliza:

    <?php
    
    return [
        'db' => [
            'host' => 'localhost',
            'port' => 3306,
            'name' => 'loot_figures',
            'user' => 'loot_app',
            'password' => 'CambiaEstaClaveBD!'
        ],
    
        'secure_cookie' => false,
        'debug' => false
    ];

    La base de datos está dentro de la propia máquina Ubuntu, por eso la aplicación utiliza localhost como servidor de base de datos.

    No debes poner la IP del ordenador Windows en esta configuración.

    27. Preparar propietarios y permisos

    Apache necesita leer los archivos del proyecto. Además, debe poder escribir en la carpeta donde se guardan las imágenes subidas.

    Configura el propietario y el grupo:

    sudo chown -R root:www-data /var/www/loot-figures-clase

    Configura los directorios:

    sudo find /var/www/loot-figures-clase -type d -exec chmod 750 {} \;

    Configura los archivos:

    sudo find /var/www/loot-figures-clase -type f -exec chmod 640 {} \;

    Permite que Apache gestione las imágenes subidas:

    sudo chown -R www-data:www-data /var/www/loot-figures-clase/public/uploads

    Comprueba:

    ls -ld /var/www/loot-figures-clase/public/uploads
    ls -l /var/www/loot-figures-clase/config/config.php

    Preguntas

    1. ¿Qué representa el usuario www-data?
    2. ¿Por qué Apache necesita permiso de escritura en uploads?
    3. ¿Por qué no necesita escribir en todos los archivos PHP?
    4. ¿Qué diferencia hay entre los permisos de un archivo y los de un directorio?

    No utilices chmod 777 para resolver problemas de permisos. Debes identificar qué usuario necesita acceso y qué operación debe realizar.

    28. Cargar los datos de ejemplo

    Ejecuta el script utilizando el usuario que puede leer los archivos del proyecto:

    sudo -u www-data php /var/www/loot-figures-clase/scripts/seed.php

    Comprueba los productos:

    sudo mysql -e "SELECT id, name, stock FROM loot_figures.products;"

    Comprueba los usuarios sin mostrar los hashes de contraseña:

    sudo mysql -e "SELECT id, name, email, role FROM loot_figures.users;"

    Evidencia: captura de los datos creados.

    29. Comprobar el acceso web

    Desde tu ordenador abre:

    http://192.168.1.50:8080

    Sustituye la IP por la del servidor.

    Si utilizas la redirección NAT propuesta, abre:

    http://localhost:8081

    Si no puedes acceder

    Comprueba el estado de Apache:

    systemctl status apache2 --no-pager

    Comprueba los puertos:

    sudo ss -lntp

    Revisa el cortafuegos:

    sudo ufw status

    Si UFW está activo, permite el puerto de la tienda:

    sudo ufw allow 8080/tcp

    No es necesario activar ni desactivar el cortafuegos para realizar esta comprobación.

    Consulta el registro de errores del sitio:

    sudo tail -n 50 /var/log/apache2/loot-figures-error.log

    Importante: escribir localhost en el navegador de tu ordenador apunta a tu ordenador. No apunta a Ubuntu, salvo que hayas configurado una redirección de puertos.

    30. Repetir las pruebas funcionales

    Realiza en Ubuntu las mismas pruebas que hiciste en XAMPP:

    • Navegación pública.
    • Catálogo y filtros.
    • Registro.
    • Login y cierre de sesión.
    • Edición del perfil.
    • Carrito y pedido.
    • Historial.
    • Administración de productos.
    • Administración de clientes.
    • Cambio de contraseña.
    • Subida de una imagen.
    • Envío y consulta de un mensaje.

    Comprueba especialmente la subida de imágenes: permite verificar que los permisos de uploads están preparados correctamente.

    31. Comprobar qué está publicando Apache

    Intenta abrir:

    http://IP_DEL_SERVIDOR:8080/config/config.php
    http://IP_DEL_SERVIDOR:8080/database/schema.sql
    http://IP_DEL_SERVIDOR:8080/scripts/seed.php

    Estas direcciones no deben permitir acceder a esos archivos, porque están fuera de la carpeta pública.

    En cambio, debe funcionar:

    http://IP_DEL_SERVIDOR:8080/api/listar_productos.php

    Esta dirección devolverá los productos en JSON.

    Explica por qué unas rutas son accesibles y otras no.


    Bloque D. Comparación, documentación y entrega

    32. Comparar las dos instalaciones

    Completa esta tabla con los datos reales de tu trabajo:

    AspectoXAMPPUbuntu Server
    Sistema operativo
    Versión de PHP
    Motor y versión de base de datos
    Ubicación del proyecto
    Carpeta pública
    Archivo de configuración del sitio Apache
    Dirección de acceso
    Puerto web
    Usuario de la base de datos
    Gestión de permisos de uploads
    Ubicación de los registros de errores

    Responde también:

    1. ¿Qué pasos son iguales en los dos entornos?
    2. ¿Qué pasos cambian?
    3. ¿Qué tareas facilita XAMPP?
    4. ¿Qué servicios has tenido que preparar en Ubuntu?
    5. ¿Por qué el editor no sustituye al servidor web?
    6. ¿Dónde se ejecuta PHP?
    7. ¿Dónde se ejecuta JavaScript?
    8. ¿Por qué necesitas copiar archivos y preparar también la base de datos?

    33. Registrar las pruebas

    Incluye una tabla de comprobación para cada instalación:

    PruebaResultado esperadoResultado obtenidoEvidencia
    Abrir la homeSe muestran diseño e imágenes
    Consultar catálogoSe muestran los productos
    Registrar clienteSe crea la cuenta
    Iniciar sesiónSe accede a la cuenta
    Crear pedidoSe guarda y aparece en el historial
    Editar productoEl cambio aparece en el catálogo
    Cambiar contraseñaFunciona la nueva contraseña
    Subir imagenSe guarda y se muestra
    Enviar contactoEl administrador puede leerlo

    No marques una prueba como correcta sin realizarla.

    34. Documentar incidencias

    Si encuentras un problema, registra:

    • Qué estabas intentando hacer.
    • El mensaje de error.
    • Qué comprobaste.
    • La causa que identificaste.
    • La solución aplicada.
    • Cómo verificaste que funcionaba.

    Si no encuentras incidencias, indica las comprobaciones preventivas que has realizado: configuración, servicios, puertos, permisos y conexión a la base de datos.

    35. Documentación que debes entregar

    Prepara un README.md con estas secciones:

    1. Título, autor y objetivo.
    2. Descripción de la aplicación.
    3. Requisitos.
    4. Estructura del proyecto.
    5. Instalación en XAMPP.
    6. Instalación en Ubuntu Server.
    7. Configuración de Apache y base de datos.
    8. Pruebas funcionales.
    9. Incidencias y soluciones.
    10. Comparación de los entornos.
    11. Conclusiones personales.

    Guarda las capturas en una carpeta:

    capturas

    Puedes insertarlas así:

    ![Panel de XAMPP con los servicios iniciados](capturas/xampp-servicios.png)

    Los comandos deben aparecer en bloques de código:

    ```bash
    sudo systemctl status apache2 --no-pager
    ```

    La documentación debe explicar tu instalación real, con tus rutas, tu IP, tus versiones y tus resultados.

    No entregues únicamente una copia de este enunciado.

    36. Entrega final

    Entrega un ZIP con:

    • README.md.
    • La carpeta capturas.
    • Los fragmentos de configuración necesarios para comprender tu instalación, con las contraseñas sustituidas por un marcador.

    Por ejemplo:

    'password' => 'CONTRASEÑA_OCULTA'

    No es necesario incluir los discos de la máquina virtual ni una copia completa de XAMPP.

    Conserva las dos instalaciones funcionando para poder demostrarlas en clase.

    37. Comprobación final antes de entregar

    • La aplicación funciona en XAMPP.
    • La aplicación funciona en Ubuntu Server.
    • Has comprobado las funciones de cliente y administrador.
    • La subida de imágenes funciona en ambos entornos.
    • Apache sirve únicamente la carpeta public.
    • Has identificado los modelos, DAO y archivos de operaciones.
    • Has observado una respuesta JSON.
    • La documentación contiene pasos, comandos y evidencias.
    • Las capturas se visualizan correctamente en el README.
    • Puedes explicar qué has configurado y por qué.

    La actividad se considerará completada cuando puedas mostrar las dos instalaciones y explicar el recorrido de una petición desde el navegador hasta la base de datos.

  • Proyecto Arduino: del sensor al coche robot Parte 1

    Proyecto Arduino: del sensor al coche robot Parte 1

    Presentación del proyecto

    En este proyecto aprenderás a utilizar los componentes que más adelante formarán parte de un coche robot de cuatro motores.

    Antes de construir el coche completo, trabajaremos por etapas para comprender qué hace cada componente, cómo se conecta y cómo se controla desde un programa.

    El proyecto se divide en dos partes:

    ParteQué vamos a trabajarResultado
    Parte 1. Medir y observar el entornoSensor de ultrasonidos, servomotor y combinación de ambosUn escáner que mide distancias en distintas direcciones
    Parte 2. Controlar el movimientoMotores DC, controlador L293D y combinación con el sensorUn montaje de banco que detiene los motores ante un obstáculo

    En la primera parte no conectaremos motores DC ni el controlador L293D. El servomotor se utilizará para orientar el sensor.

    El coche completo será una fase posterior. En estas dos partes nos centraremos en comprender y comprobar sus componentes.

    Objetivos generales

    Al finalizar el proyecto deberás ser capaz de:

    • Identificar los cables y conexiones de cada componente.
    • Distinguir entre un sensor y un actuador.
    • Medir distancias con un HC-SR04.
    • Colocar un servo en diferentes posiciones.
    • Combinar el servo y el sensor para observar distintas direcciones.
    • Controlar el sentido y la velocidad de motores DC mediante un L293D.
    • Utilizar una lectura del sensor para tomar una decisión.
    • Documentar los montajes y explicar los resultados.

    Forma de trabajo

    En cada práctica seguirás este proceso:

    1. Comprender: lee qué hace el componente y para qué sirve.
    2. Predecir: escribe qué esperas que ocurra.
    3. Montar: realiza las conexiones con la alimentación desconectada.
    4. Programar: introduce el código y súbelo al Arduino.
    5. Comprobar: observa el resultado y registra las pruebas.
    6. Modificar: cambia una parte del programa y explica su efecto.

    No basta con conseguir que el montaje funcione. Debes poder explicar por qué funciona.


    Parte 1. Medir y observar el entorno

    Material necesario

    • Arduino Uno R3 y cable USB.
    • Sensor de ultrasonidos HC-SR04.
    • Servomotor de posición SG90 o similar.
    • Fuente regulada de 5 V adecuada para el servo.
    • Cables y protoboard.
    • Regla o cinta métrica.
    • Una caja o superficie plana para realizar las mediciones.
    • Un soporte ligero para colocar el sensor sobre el servo.

    Preparación del entorno

    Antes de comenzar:

    1. Abre Arduino IDE.
    2. Selecciona la placa Arduino Uno.
    3. Selecciona el puerto al que está conectado el Arduino.
    4. Crea un programa diferente para cada práctica.
    5. Guarda los programas con estos nombres:
    01_ultrasonidos
    02_servo
    03_escaner

    Mapa de conexiones de esta parte

    ComponenteConexión al Arduino
    HC-SR04: VCC5V
    HC-SR04: GNDGND
    HC-SR04: TRIGD12
    HC-SR04: ECHOD13
    Señal del servoD9
    Alimentación del servoFuente externa regulada de 5 V
    GND de la fuente del servoUnido al GND del Arduino

    Práctica 1. Medir distancias con ultrasonidos

    Qué vamos a aprender

    El HC-SR04 permite medir la distancia hasta un objeto.

    Para hacerlo:

    1. El Arduino envía una orden por el pin TRIG.
    2. El sensor emite una señal ultrasónica.
    3. La señal rebota en el objeto.
    4. El sensor comunica por ECHO el tiempo asociado al recorrido.

    Como el sonido viaja hasta el objeto y vuelve, debemos dividir el recorrido entre dos.

    Conexiones

    Pin del HC-SR04Arduino Uno
    VCC5V
    GNDGND
    TRIGD12
    ECHOD13

    Comprueba los nombres impresos en el sensor antes de conectar los cables.

    Montaje paso a paso

    1. Desconecta el cable USB del Arduino.
    2. Conecta VCC a 5V.
    3. Conecta GND a GND.
    4. Conecta TRIG a D12.
    5. Conecta ECHO a D13.
    6. Coloca el sensor de forma que sus dos cápsulas apunten hacia la zona que quieres medir.
    7. Revisa las conexiones antes de conectar el USB.

    Programa

    const byte TRIG = 12;
    const byte ECHO = 13;
    
    float medirCm() {
      // Preparar el disparo.
      digitalWrite(TRIG, LOW);
      delayMicroseconds(2);
    
      // Enviar el pulso de disparo.
      digitalWrite(TRIG, HIGH);
      delayMicroseconds(10);
      digitalWrite(TRIG, LOW);
    
      // Esperar el eco, con un límite de tiempo.
      unsigned long tiempo = pulseIn(ECHO, HIGH, 30000UL);
    
      // Un resultado de cero significa que no llegó el eco.
      if (tiempo == 0) {
        return -1.0;
      }
    
      // Convertir el tiempo en distancia.
      return tiempo * 0.0343 / 2.0;
    }
    
    void setup() {
      pinMode(TRIG, OUTPUT);
      pinMode(ECHO, INPUT);
    
      Serial.begin(9600);
    }
    
    void loop() {
      float distancia = medirCm();
    
      if (distancia < 0) {
        Serial.println("Sin eco");
      } else {
        Serial.print("Distancia: ");
        Serial.print(distancia, 1);
        Serial.println(" cm");
      }
    
      delay(100);
    }

    Cómo comprobarlo

    1. Sube el programa.
    2. Abre el Monitor serie a 9600 baudios.
    3. Coloca una caja plana aproximadamente a 10 cm.
    4. Mide la distancia real con una regla.
    5. Repite a 20 y 40 cm.
    6. Inclina la caja y observa si cambia la lectura.
    7. Retira el objeto y observa qué sucede.

    Tabla de resultados

    Distancia medida con reglaLectura del sensorDiferencia
    10 cm
    20 cm
    40 cm

    Resultado esperado

    La distancia mostrada cambia al mover el objeto.

    Cuando no se recibe el eco dentro del tiempo de espera, aparece «Sin eco». Esto no significa que el objeto esté a cero centímetros.

    Modificación obligatoria

    Modifica el programa para que, además de la distancia, muestre:

    • «Objeto cercano» cuando la distancia sea de 20 cm o menos.
    • «Objeto alejado» cuando supere los 20 cm.
    • «Sin eco» cuando no exista una lectura.

    Preguntas

    1. ¿Qué función tiene TRIG?
    2. ¿Qué función tiene ECHO?
    3. ¿Por qué dividimos entre dos?
    4. ¿Por qué una superficie inclinada puede afectar a la lectura?
    5. ¿Por qué no debemos interpretar «Sin eco» como cero centímetros?

    Práctica 2. Controlar la posición de un servo

    Qué vamos a aprender

    Un servo de posición permite mover su eje hasta un ángulo determinado.

    Lo utilizaremos para orientar el sensor de ultrasonidos hacia diferentes direcciones.

    Este servo recibe una orden de posición. Su funcionamiento es distinto al de los motores DC que harán girar las ruedas.

    Conexiones

    Cable del servoConexión
    Señal, normalmente naranja o amarilloD9 del Arduino
    RojoPositivo de la fuente regulada de 5 V
    Marrón o negroGND de la fuente
    GND de la fuenteGND del Arduino

    Comprueba los colores y las características de tu modelo.

    La masa de la fuente y la del Arduino deben estar unidas.

    Montaje paso a paso

    1. Desconecta todas las alimentaciones.
    2. Conecta la señal del servo a D9.
    3. Conecta los cables de alimentación del servo a su fuente.
    4. Une el GND de la fuente con el GND del Arduino.
    5. Coloca el servo sobre una superficie estable.
    6. Revisa las conexiones.
    7. Conecta el Arduino y sube el programa.

    Programa

    #include <Servo.h>
    
    Servo giro;
    const byte PIN_SERVO = 9;
    
    void setup() {
      giro.attach(PIN_SERVO);
    
      // Comenzar en la posición central.
      giro.write(90);
      delay(1000);
    }
    
    void loop() {
      giro.write(30);
      delay(1000);
    
      giro.write(90);
      delay(1000);
    
      giro.write(150);
      delay(1000);
    }

    Cómo comprobarlo

    1. Observa el movimiento entre las tres posiciones.
    2. Comprueba que el brazo no choque con ningún objeto.
    3. No fuerces el eje con la mano.
    4. Cambia las posiciones a 45°, 90° y 135°.
    5. Cambia los tiempos de espera y observa la diferencia.

    Tabla de resultados

    Ángulo solicitadoPosición observadaObservaciones
    30°
    90°
    150°

    Resultado esperado

    El servo se coloca sucesivamente en las posiciones indicadas.

    Los ángulos del programa son órdenes de posición; la posición física exacta depende del servo y de cómo se haya colocado su brazo.

    Modificación obligatoria

    Crea una secuencia que:

    1. Comience a 90°.
    2. Se mueva a 45°.
    3. Regrese a 90°.
    4. Se mueva a 135°.
    5. Regrese a 90°.

    Espera un segundo entre posiciones.

    Preguntas

    1. ¿Para qué sirve el cable de señal?
    2. ¿Qué hace attach()?
    3. ¿Qué hace write()?
    4. ¿Por qué unimos las masas?
    5. ¿Qué diferencia existe entre un servo de posición y un motor DC de una rueda?

    Práctica 3. Construir un escáner de distancias

    Qué vamos a aprender

    Combinaremos los dos montajes anteriores.

    El servo orientará el HC-SR04 hacia distintas posiciones y el Arduino realizará una medición en cada una.

    El resultado será un pequeño escáner del entorno.

    Conexiones

    Mantén las conexiones de las prácticas anteriores:

    ElementoConexión
    HC-SR04 VCC5V del Arduino
    HC-SR04 GNDGND del Arduino
    HC-SR04 TRIGD12
    HC-SR04 ECHOD13
    Señal del servoD9
    Alimentación del servoFuente externa regulada de 5 V
    GND de la fuenteGND del Arduino

    Montaje paso a paso

    1. Lleva el servo a 90° con un programa de prueba.
    2. Desconecta las alimentaciones.
    3. Fija el sensor sobre el brazo del servo mediante un soporte ligero.
    4. Deja holgura en los cables del sensor.
    5. Comprueba que puede moverse entre 30° y 150° sin tirar de los cables.
    6. Combina las conexiones.
    7. Sube el programa.

    Programa

    #include <Servo.h>
    
    Servo giro;
    
    const byte PIN_SERVO = 9;
    const byte TRIG = 12;
    const byte ECHO = 13;
    
    float medirCm() {
      digitalWrite(TRIG, LOW);
      delayMicroseconds(2);
    
      digitalWrite(TRIG, HIGH);
      delayMicroseconds(10);
      digitalWrite(TRIG, LOW);
    
      unsigned long tiempo = pulseIn(ECHO, HIGH, 30000UL);
    
      if (tiempo == 0) {
        return -1.0;
      }
    
      return tiempo * 0.0343 / 2.0;
    }
    
    void setup() {
      pinMode(TRIG, OUTPUT);
      pinMode(ECHO, INPUT);
    
      Serial.begin(9600);
    
      giro.attach(PIN_SERVO);
      giro.write(90);
      delay(600);
    }
    
    void loop() {
      const byte angulos[] = {30, 90, 150, 90};
    
      for (byte i = 0; i < 4; i++) {
        giro.write(angulos[i]);
    
        // Esperar a que termine el movimiento antes de medir.
        delay(500);
    
        float distancia = medirCm();
    
        Serial.print("Angulo: ");
        Serial.print(angulos[i]);
        Serial.print(" grados | ");
    
        if (distancia < 0) {
          Serial.println("Sin eco");
        } else {
          Serial.print(distancia, 1);
          Serial.println(" cm");
        }
    
        delay(100);
      }
    }

    Cómo comprobarlo

    1. Abre el Monitor serie a 9600 baudios.
    2. Coloca objetos en distintas direcciones.
    3. Observa la distancia asociada a cada ángulo.
    4. Anota qué ángulo corresponde físicamente a izquierda, centro y derecha en tu montaje.
    5. Cambia la posición de los objetos y repite.

    Tabla de resultados

    ÁnguloDirección físicaDistanciaLectura válida
    30°
    90°
    150°

    Resultado esperado

    El sensor se orienta hacia varias posiciones y muestra una distancia asociada a cada una.

    Modificación obligatoria

    Modifica el programa para guardar las lecturas de las tres posiciones y mostrar cuál tiene mayor distancia entre las lecturas válidas.

    Si ninguna lectura es válida, debe mostrar:

    No se puede determinar la dirección más despejada.

    Preguntas

    1. ¿Por qué esperamos después de mover el servo?
    2. ¿Por qué debemos relacionar cada distancia con su ángulo?
    3. ¿Podemos considerar que una dirección sin eco está necesariamente libre?
    4. ¿Cómo podría utilizar esta información el futuro coche?

    Entrega de la parte 1

    Entrega una carpeta comprimida con:

    parte_1/
    ├── README.md
    ├── imagenes/
    ├── 01_ultrasonidos/
    ├── 02_servo/
    └── 03_escaner/

    En el README.md incluye:

    • Explicación de las tres prácticas.
    • Fotografías y conexiones.
    • Tablas de resultados.
    • Respuestas a las preguntas.
    • Modificaciones realizadas.
    • Una conclusión sobre cómo funciona el escáner.