Autor: ciberkaos

  • 4 – Práctica guiada: varios Arduinos con sensores conectados a una Raspberry Pi y almacenamiento en CSV o JSON

    4 – Práctica guiada: varios Arduinos con sensores conectados a una Raspberry Pi y almacenamiento en CSV o JSON

    1. Introducción

    En esta práctica vais a construir un pequeño sistema de adquisición de datos distribuido. En lugar de trabajar con un único sensor conectado a un solo dispositivo, vais a montar una arquitectura formada por varios Arduinos, cada uno con uno o varios sensores, y una Raspberry Pi que actuará como centro de recepción y almacenamiento.

    El objetivo no es solo «leer sensores», sino comprender cómo se organiza un sistema real donde varios nodos capturan información y un equipo central la recibe, la procesa y la guarda para su análisis posterior.

    Este tipo de arquitectura se parece mucho a sistemas reales de monitorización, automatización, IoT, domótica o control industrial.


    2. Objetivos de la práctica

    Al finalizar la práctica, debéis ser capaces de:

    • Conectar sensores a varios Arduinos.
    • Leer datos de sensores desde Arduino.
    • Enviar esos datos por comunicación serie a través de USB.
    • Conectar varios Arduinos a una Raspberry Pi.
    • Identificar los puertos serie de cada Arduino en Linux.
    • Crear en la Raspberry Pi un programa en Python que reciba datos desde varios Arduinos.
    • Guardar los datos recibidos en archivos CSV o JSON.
    • Documentar correctamente el montaje, el código y el funcionamiento del sistema.

    3. Descripción general del sistema

    La arquitectura general de la práctica será la siguiente:

    [Sensores] → [Arduino 1] ┐
    [Sensores] → [Arduino 2] ├── USB ──> [Raspberry Pi] ──> [CSV / JSON]
    [Sensores] → [Arduino 3] ┘

    Cada Arduino actuará como nodo de captura de datos.

    La Raspberry Pi actuará como:

    • receptor central,
    • sistema de almacenamiento,
    • y punto de análisis de los datos.

    4. Material necesario

    Cada grupo deberá disponer, como mínimo, de:

    • 1 Raspberry Pi con Raspberry Pi OS.
    • 2 o más placas Arduino.
    • Sensores asignados por el profesor.
    • Cables USB para conectar los Arduinos a la Raspberry Pi.
    • Protoboard y cables Dupont.
    • Resistencias si el sensor lo necesita.
    • Tarjeta microSD con sistema operativo funcional en la Raspberry Pi.
    • Acceso al terminal de la Raspberry Pi.

    Material opcional recomendable

    • Hub USB alimentado, si se van a usar muchos Arduinos.
    • Teclado, ratón y monitor para la Raspberry Pi, o acceso por SSH.

    5. Requisitos mínimos del proyecto

    El sistema desarrollado debe cumplir, al menos, con los siguientes requisitos:

    1. Usar varios Arduinos conectados a la Raspberry Pi.
    2. Cada Arduino debe leer al menos un sensor.
    3. Cada Arduino debe enviar sus datos por puerto serie.
    4. La Raspberry Pi debe recibir datos de todos los Arduinos.
    5. Los datos deben guardarse en un archivo CSV o JSON.
    6. Cada dato debe quedar identificado con:
      • fecha y hora,
      • identificador del Arduino,
      • nombre del sensor,
      • valor recibido.
    7. El grupo debe entregar documentación técnica y capturas.


    FASE 1. Diseño del sistema

    6.1. Planificación previa

    Antes de montar nada, debéis diseñar vuestro sistema.

    Debéis definir:

    • Cuántos Arduinos vais a usar.
    • Qué sensor o sensores irá conectado a cada Arduino.
    • Qué tipo de dato enviará cada uno.
    • Qué formato de mensaje vais a usar.
    • Si guardaréis los datos en CSV, JSON o ambos.

    Ejemplo de reparto

    • Arduino 1: temperatura y humedad
    • Arduino 2: luz
    • Arduino 3: movimiento

    6.2. Formato común de los datos

    Todos los grupos debéis usar un formato de texto común para enviar los datos desde Arduino a la Raspberry Pi.

    Formato recomendado

    arduino=A1;sensor=temp;valor=23.4
    arduino=A1;sensor=hum;valor=41.2
    arduino=A2;sensor=luz;valor=820
    arduino=A3;sensor=mov;valor=1

    Reglas del formato

    • Cada línea representa una lectura.
    • Cada dato estará compuesto por pares clave=valor.
    • Los pares estarán separados por ;.
    • Cada línea terminará con salto de línea.

    Este formato es muy sencillo de leer y muy fácil de procesar en Python.


    FASE 2. Montaje del hardware

    7. Conexión de sensores a los Arduinos

    Cada grupo conectará los sensores asignados a sus placas Arduino.

    Recomendaciones

    • Revisad siempre el voltaje de trabajo del sensor.
    • Comprobad si necesita resistencia pull-up o pull-down.
    • Verificad qué pin usaréis: digital o analógico.
    • Etiquetad en vuestra documentación qué sensor está conectado a qué pin.

    Ejemplo de tabla de montaje

    ArduinoSensorTipo de pinPin usado
    A1Sensor de temperaturaAnalógicoA0
    A1Sensor de humedadAnalógicoA1
    A2LDRAnalógicoA0
    A3PIRDigitalD2

    8. Conexión de los Arduinos a la Raspberry Pi

    Una vez que cada Arduino funcione correctamente con su sensor, debéis conectarlos a la Raspberry Pi mediante USB.

    Importante

    • Cada Arduino se conectará con su propio cable USB.
    • Si no hay suficientes puertos USB en la Raspberry, usad un hub USB.
    • Si usáis varios Arduinos, es recomendable que el hub sea alimentado externamente.

    FASE 3. Programación de cada Arduino

    9. Objetivo del código Arduino

    Cada Arduino debe:

    1. Inicializar su comunicación serie.
    2. Leer el valor del sensor.
    3. Construir un mensaje con el formato definido.
    4. Enviar ese mensaje por el puerto serie.
    5. Repetir el proceso cada cierto tiempo.

    10. Estructura general del programa Arduino

    Ejemplo genérico

    const String ID_ARDUINO = "A1";
    
    void setup() {
      Serial.begin(9600);
    }
    
    void loop() {
      int valorSensor = analogRead(A0);
    
      Serial.print("arduino=");
      Serial.print(ID_ARDUINO);
      Serial.print(";sensor=luz;valor=");
      Serial.println(valorSensor);
    
      delay(2000);
    }

    11. Recomendaciones para el código Arduino

    • No enviéis datos demasiado rápido.
    • Para empezar, una lectura cada 1 o 2 segundos es suficiente.
    • No mezcléis mensajes de depuración con mensajes de datos.
    • Usad siempre el mismo formato.

    Mal ejemplo

    Serial.println("Leyendo sensor...");
    Serial.println(valor);

    Buen ejemplo

    Serial.println("arduino=A1;sensor=luz;valor=830");

    FASE 4. Comprobación de los Arduinos en la Raspberry Pi

    12. Detectar los puertos serie

    Cuando conectéis los Arduinos a la Raspberry, debéis comprobar qué puertos se han creado.

    Comandos útiles

    ls /dev/ttyACM*
    ls /dev/ttyUSB*

    También podéis usar:

    dmesg | tail

    Posible resultado

    /dev/ttyACM0
    /dev/ttyACM1
    /dev/ttyACM2

    Cada uno suele corresponder a un Arduino distinto.


    13. Identificación más estable de los dispositivos

    En algunos casos, el nombre /dev/ttyACM0 puede cambiar al reiniciar o reconectar.

    Para identificar los puertos de forma más estable, podéis usar:

    ls /dev/serial/by-id/

    Esto mostrará nombres más largos, pero normalmente más estables.


    14. Prueba manual del puerto serie

    Antes de escribir el programa final en Python, comprobad que la Raspberry recibe datos.

    Podéis usar herramientas como:

    cat /dev/ttyACM0

    O instalar herramientas como screen o minicom.


    FASE 5. Preparación del entorno Python en Raspberry Pi

    15. Instalar Python y pyserial

    Normalmente Python ya estará instalado. Debéis instalar la librería pyserial.

    sudo apt update
    sudo apt install python3-pip -y
    pip3 install pyserial

    Si el sistema os da problemas con permisos, podéis usar entorno virtual o instalar el paquete del sistema si está disponible.


    FASE 6. Programa receptor en Raspberry Pi

    16. Objetivo del programa Python

    El script en Python debe:

    • abrir varios puertos serie,
    • escuchar los datos de varios Arduinos,
    • interpretar cada línea recibida,
    • añadir fecha y hora,
    • guardar los datos en CSV o JSON.

    17. Formato final recomendado de almacenamiento

    Opción CSV

    timestamp,arduino,sensor,valor
    2026-03-10 10:15:00,A1,temp,23.4
    2026-03-10 10:15:01,A1,hum,41.2
    2026-03-10 10:15:03,A2,luz,820

    Opción JSON (un registro por línea)

    {"timestamp":"2026-03-10 10:15:00","arduino":"A1","sensor":"temp","valor":"23.4"}
    {"timestamp":"2026-03-10 10:15:01","arduino":"A1","sensor":"hum","valor":"41.2"}
    {"timestamp":"2026-03-10 10:15:03","arduino":"A2","sensor":"luz","valor":"820"}

    18. Script Python de ejemplo para varios Arduinos y CSV

    import serial
    import threading
    import csv
    import os
    from datetime import datetime
    
    PUERTOS = [
        "/dev/ttyACM0",
        "/dev/ttyACM1",
        "/dev/ttyACM2"
    ]
    
    BAUDIOS = 9600
    ARCHIVO_CSV = "datos_sensores.csv"
    CAMPOS = ["timestamp", "arduino", "sensor", "valor"]
    lock_csv = threading.Lock()
    
    
    def parsear_linea(linea):
        datos = {}
        partes = linea.strip().split(";")
        for parte in partes:
            if "=" in parte:
                clave, valor = parte.split("=", 1)
                datos[clave.strip()] = valor.strip()
        return datos
    
    
    def inicializar_csv():
        if not os.path.exists(ARCHIVO_CSV) or os.path.getsize(ARCHIVO_CSV) == 0:
            with open(ARCHIVO_CSV, "w", newline="", encoding="utf-8") as f:
                writer = csv.DictWriter(f, fieldnames=CAMPOS)
                writer.writeheader()
    
    
    def guardar_csv(registro):
        with lock_csv:
            with open(ARCHIVO_CSV, "a", newline="", encoding="utf-8") as f:
                writer = csv.DictWriter(f, fieldnames=CAMPOS)
                writer.writerow(registro)
    
    
    def escuchar_puerto(puerto):
        try:
            ser = serial.Serial(puerto, BAUDIOS, timeout=1)
            print(f"Escuchando {puerto}")
    
            while True:
                linea = ser.readline().decode("utf-8", errors="ignore").strip()
    
                if linea:
                    print(f"{puerto} -> {linea}")
                    datos = parsear_linea(linea)
    
                    if "arduino" in datos and "sensor" in datos and "valor" in datos:
                        registro = {
                            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                            "arduino": datos["arduino"],
                            "sensor": datos["sensor"],
                            "valor": datos["valor"]
                        }
                        guardar_csv(registro)
        except Exception as e:
            print(f"Error en {puerto}: {e}")
    
    
    def main():
        inicializar_csv()
        hilos = []
    
        for puerto in PUERTOS:
            hilo = threading.Thread(target=escuchar_puerto, args=(puerto,), daemon=True)
            hilo.start()
            hilos.append(hilo)
    
        for hilo in hilos:
            hilo.join()
    
    
    if __name__ == "__main__":
        main()

    19. Script Python de ejemplo para guardar en JSON

    import serial
    import threading
    import json
    from datetime import datetime
    
    PUERTOS = [
        "/dev/ttyACM0",
        "/dev/ttyACM1"
    ]
    
    BAUDIOS = 9600
    ARCHIVO_JSON = "datos_sensores.jsonl"
    lock_json = threading.Lock()
    
    
    def parsear_linea(linea):
        datos = {}
        partes = linea.strip().split(";")
        for parte in partes:
            if "=" in parte:
                clave, valor = parte.split("=", 1)
                datos[clave.strip()] = valor.strip()
        return datos
    
    
    def guardar_json(registro):
        with lock_json:
            with open(ARCHIVO_JSON, "a", encoding="utf-8") as f:
                f.write(json.dumps(registro, ensure_ascii=False) + "\n")
    
    
    def escuchar_puerto(puerto):
        try:
            ser = serial.Serial(puerto, BAUDIOS, timeout=1)
            print(f"Escuchando {puerto}")
    
            while True:
                linea = ser.readline().decode("utf-8", errors="ignore").strip()
    
                if linea:
                    print(f"{puerto} -> {linea}")
                    datos = parsear_linea(linea)
    
                    if "arduino" in datos and "sensor" in datos and "valor" in datos:
                        registro = {
                            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                            "arduino": datos["arduino"],
                            "sensor": datos["sensor"],
                            "valor": datos["valor"]
                        }
                        guardar_json(registro)
        except Exception as e:
            print(f"Error en {puerto}: {e}")
    
    
    def main():
        hilos = []
    
        for puerto in PUERTOS:
            hilo = threading.Thread(target=escuchar_puerto, args=(puerto,), daemon=True)
            hilo.start()
            hilos.append(hilo)
    
        for hilo in hilos:
            hilo.join()
    
    
    if __name__ == "__main__":
        main()

    FASE 7. Ejecución del programa

    20. Guardar y ejecutar el script

    Guardad el script con un nombre como:

    nano receptor_sensores.py

    Pegad el código, guardadlo y ejecutadlo:

    python3 receptor_sensores.py

    Si todo funciona, deberíais ver en pantalla líneas como estas:

    /dev/ttyACM0 -> arduino=A1;sensor=temp;valor=23.7
    /dev/ttyACM1 -> arduino=A2;sensor=luz;valor=801
    /dev/ttyACM2 -> arduino=A3;sensor=mov;valor=1

    21. Comprobar los archivos generados

    Para CSV

    cat datos_sensores.csv

    Para JSON

    cat datos_sensores.jsonl

    FASE 8. Validación del sistema

    22. Qué debéis comprobar

    Debéis verificar que:

    • todos los Arduinos envían datos,
    • la Raspberry recibe los mensajes,
    • los datos se guardan correctamente,
    • cada línea incluye el identificador del Arduino,
    • los archivos se actualizan conforme llegan nuevas lecturas.

    23. Pruebas recomendadas

    Realizad pruebas como estas:

    1. Desconectar un Arduino y comprobar qué ocurre.
    2. Volver a conectarlo.
    3. Modificar el valor del sensor (por ejemplo, tapar una LDR o mover un PIR).
    4. Verificar que el cambio se refleja en el CSV o JSON.
    5. Comprobar si el sistema sigue funcionando con varios Arduinos enviando datos al mismo tiempo.

    FASE 9. Posibles errores y soluciones

    24. Error: no aparece /dev/ttyACM0

    Posibles causas

    • El cable USB no funciona.
    • El Arduino no está correctamente conectado.
    • El sistema no ha detectado la placa.

    Soluciones

    • Probar otro cable.
    • Ejecutar dmesg | tail.
    • Probar con otro puerto USB.

    25. Error: permiso denegado al abrir el puerto serie

    Solución posible

    Añadir el usuario al grupo adecuado:

    sudo usermod -a -G dialout $USER

    Después debéis cerrar sesión y volver a entrar.


    26. Error: el script recibe líneas vacías o mal formadas

    Posibles causas

    • El Arduino está enviando texto extra.
    • La velocidad en baudios no coincide.
    • El formato del mensaje no es correcto.

    Soluciones

    • Revisar Serial.begin(9600) en Arduino.
    • Comprobar que Python usa la misma velocidad.
    • Revisar el formato clave=valor;clave=valor.

    Entrega del trabajo

    27. Qué debéis entregar

    Cada grupo debe entregar:

    1. Código de todos los Arduinos.
    2. Código Python de la Raspberry Pi.
    3. Archivo README o memoria técnica.
    4. Esquema del sistema.
    5. Fotografías del montaje.
    6. Capturas del terminal mostrando la recepción de datos.
    7. Ejemplo del CSV o JSON generado.
    8. Explicación de problemas encontrados y cómo los resolvisteis.

    28. Estructura recomendada de carpetas

    practica-raspberry-arduinos/
    ├── README.md
    ├── arduino/
    │   ├── arduino_1/
    │   │   └── arduino_1.ino
    │   ├── arduino_2/
    │   │   └── arduino_2.ino
    │   └── arduino_3/
    │       └── arduino_3.ino
    ├── raspberry/
    │   ├── receptor_csv.py
    │   └── receptor_json.py
    ├── evidencias/
    │   ├── foto_montaje_1.jpg
    │   ├── terminal_recepcion.png
    │   └── csv_generado.png
    └── datos_ejemplo/
        ├── datos_sensores.csv
        └── datos_sensores.jsonl




    Conclusión

    Esta práctica os permitirá trabajar con una arquitectura completa de adquisición de datos usando varios microcontroladores y un sistema Linux central.

    No se trata solo de conectar cables y leer números, sino de entender cómo diseñar un sistema modular, organizado y ampliable. Ese enfoque es mucho más valioso que hacer una sola lectura aislada en un monitor serie.

    Cuando el sistema funcione, habréis construido una pequeña red de sensores con almacenamiento centralizado, algo muy cercano a muchos proyectos reales de automatización, IoT y monitorización.




  • 5 – Lectura de sensores con Python y almacenamiento automático en MariaDB

    5 – Lectura de sensores con Python y almacenamiento automático en MariaDB

    En esta práctica vas a convertir tu Raspberry Pi en un pequeño nodo autónomo de captura de datos dentro del proyecto RaspyAlarma.

    Tu Raspberry deberá ser capaz de:

    1. leer los datos de un sensor con Python,
    2. guardar esas lecturas en una base de datos MariaDB,
    3. ejecutar ese proceso automáticamente cada cierto tiempo mediante cron,
    4. y dejar preparada la información para una futura sincronización con un servidor central.

    Todos los alumnos deberán usar la misma estructura de base de datos, ya que más adelante será necesario unificar los datos de todos los sensores y de todas las Raspberry del proyecto.


    Objetivo general

    El objetivo de esta práctica es crear un sistema automático en el que una Raspberry:

    • lea un sensor,
    • almacene la lectura en una base de datos local,
    • identifique de qué Raspberry procede el dato,
    • indique si la lectura representa o no una situación de alerta,
    • y marque si ese dato ya fue enviado o no al servidor central.

    Estructura obligatoria de la base de datos

    Todos los alumnos deberán usar una base de datos llamada exactamente:

    raspialarm

    Y una tabla llamada exactamente:

    lecturas_sensores

    Estructura obligatoria de la tabla

    La tabla deberá contener estos campos:

    • id
    • raspberry_id
    • nombresensor
    • lectura1
    • lectura2
    • lectura3
    • fecha_hora
    • alumnoEncargado
    • descripcionSensor
    • estado_alerta
    • enviado_central

    Significado de los campos

    raspberry_id

    Este campo servirá para identificar qué Raspberry ha generado la lectura.

    Ejemplos válidos:

    • RPI01
    • RPI_AULA_01
    • RPI_PASILLO
    • RPI_HABITACION_3

    Este identificador debe ser fijo para cada Raspberry y no debe cambiar entre ejecuciones.


    estado_alerta

    Este campo servirá para indicar el estado de la lectura.

    Valores recomendados:

    • normal
    • aviso
    • critico

    No todos los sensores tendrán alertas complejas al principio, pero el campo debe existir ya desde esta fase.

    Ejemplo:

    • una lectura dentro de lo normal → normal
    • una lectura elevada pero no crítica → aviso
    • una lectura peligrosa o fuera de umbral → critico

    enviado_central

    Este campo servirá para saber si esa lectura ya ha sido enviada al servidor central del proyecto.

    Valores recomendados:

    • 0 → todavía no se ha enviado
    • 1 → ya se ha enviado

    En esta práctica, normalmente todos los registros nuevos se guardarán inicialmente con valor 0.


    Parte 1. Crear la base de datos

    Paso 1. Comprobar que MariaDB está activa

    Abre una terminal y ejecuta:

    sudo systemctl status mariadb

    Si no estuviera arrancada:

    sudo systemctl start mariadb

    Y para dejarla activada al iniciar el sistema:

    sudo systemctl enable mariadb

    Paso 2. Entrar en MariaDB

    sudo mariadb

    Paso 3. Crear la base de datos

    Dentro de MariaDB:

    CREATE DATABASE raspialarm;
    SHOW DATABASES;
    USE raspialarm;

    Parte 2. Crear la tabla común del proyecto

    Ejecuta esta instrucción SQL exactamente como aparece:

    CREATE TABLE lecturas_sensores (
    id INT AUTO_INCREMENT PRIMARY KEY,
    raspberry_id VARCHAR(50) NOT NULL,
    nombresensor VARCHAR(100) NOT NULL,
    lectura1 DECIMAL(10,2),
    lectura2 DECIMAL(10,2),
    lectura3 DECIMAL(10,2),
    fecha_hora DATETIME NOT NULL,
    alumnoEncargado VARCHAR(100) NOT NULL,
    descripcionSensor VARCHAR(255),
    estado_alerta VARCHAR(20) NOT NULL DEFAULT 'normal',
    enviado_central TINYINT(1) NOT NULL DEFAULT 0
    );

    Paso 4. Comprobar la estructura de la tabla

    DESCRIBE lecturas_sensores;

    También puedes verla completa con:

    SHOW CREATE TABLE lecturas_sensores;

    Parte 3. Crear un usuario específico para la aplicación

    No debes usar root dentro del script Python.

    Ejecuta:

    CREATE USER 'raspiuser'@'localhost' IDENTIFIED BY 'raspi1234';
    GRANT ALL PRIVILEGES ON raspialarm.* TO 'raspiuser'@'localhost';
    FLUSH PRIVILEGES;
    EXIT;

    Parte 4. Crear la estructura de carpetas

    Ejecuta:

    mkdir -p /home/pi/raspialarma/sensores
    mkdir -p /home/pi/raspialarma/logs
    mkdir -p /home/pi/raspialarma/docs

    Comprueba el resultado:

    ls -R /home/pi/raspialarma

    Parte 5. Instalar el conector de Python para MariaDB

    Ejecuta:

    sudo apt update
    sudo apt install python3-pip python3-mysqldb -y

    Parte 6. Crear primero un script de prueba con datos fijos

    Antes de leer el sensor real, primero debes comprobar que:

    • Python puede conectarse a MariaDB,
    • el INSERT funciona,
    • y las nuevas columnas también se guardan correctamente.

    Edita el archivo:

    nano /home/pi/raspialarma/sensores/captura_sensor.py

    Pega este contenido:

    #!/usr/bin/env python3
    
    import MySQLdb
    from datetime import datetime
    
    def escribir_log(mensaje):
        with open("/home/pi/raspialarma/logs/raspialarma.log", "a") as log:
            log.write(f"{datetime.now()} - {mensaje}\n")
    
    try:
        conexion = MySQLdb.connect(
            host="localhost",
            user="raspiuser",
            passwd="raspi1234",
            db="raspialarm"
        )
    
        cursor = conexion.cursor()
    
        raspberry_id = "RPI01"
        nombresensor = "SENSOR_PRUEBA"
        lectura1 = 25.50
        lectura2 = 60.00
        lectura3 = 0.00
        fecha_hora = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        alumnoEncargado = "TU_NOMBRE"
        descripcionSensor = "Prueba inicial de inserción en MariaDB"
        estado_alerta = "normal"
        enviado_central = 0
    
        sql = """
            INSERT INTO lecturas_sensores
            (raspberry_id, nombresensor, lectura1, lectura2, lectura3, fecha_hora,
             alumnoEncargado, descripcionSensor, estado_alerta, enviado_central)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
        """
    
        valores = (
            raspberry_id,
            nombresensor,
            lectura1,
            lectura2,
            lectura3,
            fecha_hora,
            alumnoEncargado,
            descripcionSensor,
            estado_alerta,
            enviado_central
        )
    
        cursor.execute(sql, valores)
        conexion.commit()
    
        mensaje = "Lectura guardada correctamente."
        print(mensaje)
        escribir_log(mensaje)
    
    except Exception as e:
        error = f"Error: {e}"
        print(error)
        escribir_log(error)
    
    finally:
        try:
            cursor.close()
            conexion.close()
        except:
            pass

    El INSERT debe hacerse desde Python

    En esta práctica no se insertarán datos desde PHP ni desde formularios web.
    La inserción de datos se realizará directamente desde un script Python.

    El script debe ejecutarse automáticamente

    El script Python deberá ejecutarse cada cierto tiempo utilizando cron.

    Debes documentar lo que significa cada lectura

    Cada sensor es diferente. Por tanto, deberás dejar claro en tu documentación qué representa:

    • lectura1
    • lectura2
    • lectura3

    Por ejemplo, en un DHT11 podrían significar:

    • lectura1 = temperatura
    • lectura2 = humedad
    • lectura3 = no usada

    Posible estructura de significado de las lecturas

    Para que todos trabajen de forma homogénea, puedes pedirles una tabla como esta en su documentación:

    Tipo de sensorlectura1lectura2lectura3
    DHT11 / DHT22TemperaturaHumedadNo usada
    HC-SR04DistanciaNo usadaNo usada
    PIRMovimiento detectado (0/1)No usadaNo usada
    MQ-2Nivel de gas/humoUmbralNo usada
    LDRIntensidad de luzNo usadaNo usada

    Paso 2. Personalizar el script

    Debes modificar al menos estos valores:

    raspberry_id = "RPI01"
    alumnoEncargado = "TU_NOMBRE"

    Sustitúyelos por valores reales.

    Ejemplo:

    raspberry_id = "RPI_AULA_03"
    alumnoEncargado = "Antonio"

    Paso 3. Dar permisos de ejecución

    chmod +x /home/pi/raspialarma/sensores/captura_sensor.py

    Paso 4. Ejecutar el script manualmente

    python3 /home/pi/raspialarma/sensores/captura_sensor.py

    Si todo ha ido bien, verás:

    Lectura guardada correctamente.

    Parte 7. Comprobar que el INSERT ha funcionado

    Entra en MariaDB:

    mariadb -u raspiuser -p

    Contraseña:

    raspi1234

    Dentro de MariaDB:

    USE raspialarm;
    SELECT * FROM lecturas_sensores;

    Comprueba que aparecen correctamente:

    • raspberry_id
    • estado_alerta
    • enviado_central

    Parte 8. Sustituir los datos fijos por la lectura real del sensor

    Cuando el script de prueba funcione, sustituye los valores fijos por los datos reales del sensor.

    La tabla seguirá siendo la misma.
    Solo cambiarán los valores que insertas.


    Parte 9. Cómo decidir el estado de alerta

    Debes programar una lógica sencilla para determinar el valor de estado_alerta.

    Ejemplo general

    if lectura1 < 30:
    estado_alerta = "normal"
    elif lectura1 < 50:
    estado_alerta = "aviso"
    else:
    estado_alerta = "critico"

    Esto es solo un ejemplo. Cada sensor deberá definir sus propios umbrales.


    Ejemplos según sensor

    Sensor de temperatura

    • temperatura normal → normal
    • temperatura alta → aviso
    • temperatura muy alta → critico

    Sensor de gas

    • valor bajo → normal
    • valor elevado → aviso
    • valor peligroso → critico

    Sensor PIR

    • sin movimiento → normal
    • movimiento detectado → puede mantenerse en aviso o critico, según el criterio del proyecto

    Sensor de distancia

    • distancia segura → normal
    • objeto cercano → aviso
    • objeto muy cercano → critico

    Parte 10. Valor inicial de enviado_central

    Todos los registros nuevos deberán guardarse inicialmente así:

    enviado_central = 0

    Esto significa que todavía no han sido enviados al servidor central.

    Más adelante, cuando construyas el sistema de sincronización, los registros enviados se actualizarán a:

    enviado_central = 1

    Parte 11. Ejemplo de script con lógica de alerta

    #!/usr/bin/env python3
    
    import MySQLdb
    from datetime import datetime
    
    def escribir_log(mensaje):
        with open("/home/pi/raspialarma/logs/raspialarma.log", "a") as log:
            log.write(f"{datetime.now()} - {mensaje}\n")
    
    try:
        conexion = MySQLdb.connect(
            host="localhost",
            user="raspiuser",
            passwd="raspi1234",
            db="raspialarm"
        )
    
        cursor = conexion.cursor()
    
        raspberry_id = "RPI01"
        nombresensor = "SENSOR_PRUEBA"
        lectura1 = 42.00
        lectura2 = 0.00
        lectura3 = 0.00
        fecha_hora = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        alumnoEncargado = "TU_NOMBRE"
        descripcionSensor = "Prueba con niveles de alerta"
        enviado_central = 0
    
        if lectura1 < 30:
            estado_alerta = "normal"
        elif lectura1 < 50:
            estado_alerta = "aviso"
        else:
            estado_alerta = "critico"
    
        sql = """
            INSERT INTO lecturas_sensores
            (raspberry_id, nombresensor, lectura1, lectura2, lectura3, fecha_hora,
             alumnoEncargado, descripcionSensor, estado_alerta, enviado_central)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
        """
    
        valores = (
            raspberry_id,
            nombresensor,
            lectura1,
            lectura2,
            lectura3,
            fecha_hora,
            alumnoEncargado,
            descripcionSensor,
            estado_alerta,
            enviado_central
        )
    
        cursor.execute(sql, valores)
        conexion.commit()
    
        mensaje = f"Lectura guardada correctamente con estado {estado_alerta}"
        print(mensaje)
        escribir_log(mensaje)
    
    except Exception as e:
        error = f"Error: {e}"
        print(error)
        escribir_log(error)
    
    finally:
        try:
            cursor.close()
            conexion.close()
        except:
            pass

    Parte 12. Automatizar la ejecución con cron

    Edita el crontab:

    crontab -e

    Añade una de estas opciones.

    Cada minuto

    * * * * * /usr/bin/python3 /home/pi/raspialarma/sensores/captura_sensor.py >> /home/pi/raspialarma/logs/cron.log 2>&1

    Cada 5 minutos

    */5 * * * * /usr/bin/python3 /home/pi/raspialarma/sensores/captura_sensor.py >> /home/pi/raspialarma/logs/cron.log 2>&1

    Cada 10 minutos

    */10 * * * * /usr/bin/python3 /home/pi/raspialarma/sensores/captura_sensor.py >> /home/pi/raspialarma/logs/cron.log 2>&1

    Parte 13. Consultas útiles para revisar la información

    Ver todas las lecturas

    SELECT * FROM lecturas_sensores;

    Ver las últimas lecturas

    SELECT * FROM lecturas_sensores ORDER BY fecha_hora DESC;

    Ver solo lecturas de una Raspberry

    SELECT * FROM lecturas_sensores
    WHERE raspberry_id = 'RPI01';

    Ver solo lecturas en estado crítico

    SELECT * FROM lecturas_sensores
    WHERE estado_alerta = 'critico';

    Ver solo lecturas pendientes de enviar al servidor central

    SELECT * FROM lecturas_sensores
    WHERE enviado_central = 0;

    Contar cuántas lecturas no se han enviado todavía

    SELECT COUNT(*) AS pendientes
    FROM lecturas_sensores
    WHERE enviado_central = 0;
  • 6 – Proyecto RaspyAlarma – Panel web local de lecturas

    6 – Proyecto RaspyAlarma – Panel web local de lecturas

    En la práctica anterior has preparado tu Raspberry Pi para que lea datos de un sensor con Python y los almacene automáticamente en una base de datos MariaDB llamada raspialarm.

    Ahora toca construir la siguiente pieza del sistema: un visualizador web en PHP que permita consultar las lecturas almacenadas.

    El objetivo es que tu Raspberry no solo capture y guarde información, sino que además pueda mostrarla en una página web accesible desde el navegador.

    Este visualizador será la base para futuros paneles más avanzados del proyecto RaspyAlarma, donde más adelante podrás añadir filtros, alertas visuales, gráficas y conexión con un servidor central.


    Objetivo de la práctica

    En esta práctica vas a crear un pequeño panel web en PHP que:

    • se conecte a la base de datos raspialarm,
    • lea los registros de la tabla lecturas_sensores,
    • muestre los datos en una tabla HTML,
    • permita visualizar las lecturas más recientes,
    • muestre información útil como el estado de alerta y si el dato fue enviado al servidor central.

    Qué vas a construir

    Al finalizar la práctica tendrás:

    • una carpeta web del proyecto,
    • un archivo de conexión a MariaDB,
    • una página PHP que recupere datos de la tabla,
    • una tabla HTML con todas las lecturas,
    • una versión mejorada con formato visual,
    • y un pequeño panel base para seguir ampliando en el futuro.

    Requisitos previos

    Antes de empezar debes tener ya preparado lo siguiente:

    • una Raspberry Pi funcionando,
    • Apache y PHP instalados,
    • MariaDB instalada y operativa,
    • la base de datos raspialarm creada,
    • la tabla lecturas_sensores creada,
    • y varios registros insertados desde el script Python.

    Si todavía no tienes registros en la base de datos, primero debes completar la práctica anterior.


    Estructura esperada de la base de datos

    La práctica parte de esta tabla:

    • id
    • raspberry_id
    • nombresensor
    • lectura1
    • lectura2
    • lectura3
    • fecha_hora
    • alumnoEncargado
    • descripcionSensor
    • estado_alerta
    • enviado_central

    Qué debe hacer el visualizador

    El visualizador deberá mostrar como mínimo:

    • el identificador del registro,
    • el identificador de la Raspberry,
    • el nombre del sensor,
    • las tres lecturas,
    • la fecha y hora,
    • el alumno encargado,
    • la descripción del sensor,
    • el estado de alerta,
    • y el estado de envío al servidor central.

    Parte 1. Comprobar que Apache, PHP y MariaDB están funcionando

    Paso 1. Comprobar Apache

    Abre una terminal y ejecuta:

    sudo systemctl status apache2

    Si no estuviera arrancado:

    sudo systemctl start apache2

    Y para dejarlo activado al inicio:

    sudo systemctl enable apache2

    Paso 2. Comprobar MariaDB

    sudo systemctl status mariadb

    Si no estuviera arrancada:

    sudo systemctl start mariadb

    Paso 3. Comprobar PHP

    Ejecuta:

    php -v

    Esto debe mostrarte la versión de PHP instalada.


    Parte 2. Preparar la carpeta del proyecto web

    Tu visualizador web se guardará dentro del directorio servido por Apache.

    Paso 1. Ir al directorio web

    cd /var/www/html

    Paso 2. Crear la carpeta del proyecto

    sudo mkdir -p /var/www/html/raspialarma

    Paso 3. Cambiar propietario de la carpeta

    sudo chown -R $USER:$USER /var/www/html/raspialarma

    Paso 4. Entrar en la carpeta

    cd /var/www/html/raspialarma

    Parte 3. Crear el archivo de conexión a la base de datos

    Lo primero que vas a hacer es crear un archivo reutilizable que conecte PHP con MariaDB.

    Paso 1. Crear el archivo conexion.php

    nano conexion.php

    Pega este contenido:

    <?php
    $host = "localhost";
    $usuario = "raspiuser";
    $contrasena = "raspi1234";
    $basedatos = "raspialarm";
    
    $conexion = new mysqli($host, $usuario, $contrasena, $basedatos);
    
    if ($conexion->connect_error) {
        die("Error de conexión: " . $conexion->connect_error);
    }
    
    $conexion->set_charset("utf8");
    ?>

    Guarda el archivo.


    Qué hace este archivo

    Este archivo:

    • define los datos de conexión,
    • crea una conexión con MariaDB,
    • comprueba si la conexión ha fallado,
    • y deja el objeto $conexion listo para usar en otras páginas PHP.

    Parte 4. Crear una prueba básica de conexión

    Antes de hacer el visualizador completo, conviene comprobar que PHP puede conectarse correctamente a la base de datos.

    Paso 1. Crear el archivo prueba_conexion.php

    nano prueba_conexion.php

    Pega este contenido:

    <?php
    include("conexion.php");
    
    echo "Conexión con la base de datos realizada correctamente.";
    
    $conexion->close();
    ?>

    Guarda el archivo.


    Paso 2. Probar desde el navegador

    Abre en el navegador:

    http://IP_DE_LA_RASPBERRY/raspialarma/prueba_conexion.php

    Si todo ha ido bien, deberías ver:

    Conexión con la base de datos realizada correctamente.

    Parte 5. Crear la primera página que lea datos de la tabla

    Ahora vas a crear una página PHP que recupere registros de lecturas_sensores.

    Paso 1. Crear el archivo ver_lecturas.php

    nano ver_lecturas.php

    Pega este contenido:

    <?php
    include("conexion.php");
    
    $sql = "SELECT * FROM lecturas_sensores ORDER BY fecha_hora DESC";
    $resultado = $conexion->query($sql);
    
    if ($resultado->num_rows > 0) {
        while ($fila = $resultado->fetch_assoc()) {
            echo "ID: " . $fila["id"] . "<br>";
            echo "Raspberry: " . $fila["raspberry_id"] . "<br>";
            echo "Sensor: " . $fila["nombresensor"] . "<br>";
            echo "Lectura 1: " . $fila["lectura1"] . "<br>";
            echo "Lectura 2: " . $fila["lectura2"] . "<br>";
            echo "Lectura 3: " . $fila["lectura3"] . "<br>";
            echo "Fecha y hora: " . $fila["fecha_hora"] . "<br>";
            echo "Alumno: " . $fila["alumnoEncargado"] . "<br>";
            echo "Descripción: " . $fila["descripcionSensor"] . "<br>";
            echo "Estado alerta: " . $fila["estado_alerta"] . "<br>";
            echo "Enviado central: " . $fila["enviado_central"] . "<br>";
            echo "<hr>";
        }
    } else {
        echo "No hay registros en la base de datos.";
    }
    
    $conexion->close();
    ?>

    Guarda el archivo.


    Paso 2. Probar desde el navegador

    Abre:

    http://IP_DE_LA_RASPBERRY/raspialarma/ver_lecturas.php

    Si tienes datos en la base, deberían mostrarse uno debajo de otro.


    Parte 6. Transformar la salida en una tabla HTML

    La salida anterior funciona, pero no es cómoda de leer.
    Ahora vas a mostrar los datos en una tabla HTML.

    Paso 1. Sustituir el contenido de ver_lecturas.php

    Edita el archivo:

    nano ver_lecturas.php

    Y reemplázalo por este contenido:

    <?php
    include("conexion.php");
    
    $sql = "SELECT * FROM lecturas_sensores ORDER BY fecha_hora DESC";
    $resultado = $conexion->query($sql);
    ?>
    
    <!DOCTYPE html>
    <html lang="es">
    <head>
        <meta charset="UTF-8">
        <title>Lecturas RaspyAlarma</title>
    </head>
    <body>
    
    <h1>Lecturas almacenadas en RaspyAlarma</h1>
    
    <?php
    if ($resultado->num_rows > 0) {
        echo "<table border='1' cellpadding='5' cellspacing='0'>";
        echo "<tr>
                <th>ID</th>
                <th>Raspberry</th>
                <th>Sensor</th>
                <th>Lectura 1</th>
                <th>Lectura 2</th>
                <th>Lectura 3</th>
                <th>Fecha y hora</th>
                <th>Alumno</th>
                <th>Descripción</th>
                <th>Estado alerta</th>
                <th>Enviado central</th>
              </tr>";
    
        while ($fila = $resultado->fetch_assoc()) {
            echo "<tr>";
            echo "<td>" . $fila["id"] . "</td>";
            echo "<td>" . $fila["raspberry_id"] . "</td>";
            echo "<td>" . $fila["nombresensor"] . "</td>";
            echo "<td>" . $fila["lectura1"] . "</td>";
            echo "<td>" . $fila["lectura2"] . "</td>";
            echo "<td>" . $fila["lectura3"] . "</td>";
            echo "<td>" . $fila["fecha_hora"] . "</td>";
            echo "<td>" . $fila["alumnoEncargado"] . "</td>";
            echo "<td>" . $fila["descripcionSensor"] . "</td>";
            echo "<td>" . $fila["estado_alerta"] . "</td>";
            echo "<td>" . $fila["enviado_central"] . "</td>";
            echo "</tr>";
        }
    
        echo "</table>";
    } else {
        echo "<p>No hay registros en la base de datos.</p>";
    }
    
    $conexion->close();
    ?>
    
    </body>
    </html>

    Guarda el archivo.


    Paso 2. Ver el resultado en el navegador

    Recarga:

    http://IP_DE_LA_RASPBERRY/raspialarma/ver_lecturas.php

    Ahora los datos deberían verse en forma de tabla.


    Parte 7. Mejorar la visualización del estado de alerta y del envío central

    Mostrar 0 y 1 no es muy visual.
    Vas a convertir esos valores en texto más claro.

    Paso 1. Modificar la parte del while

    Dentro del while, antes de imprimir la fila, añade esta lógica:

    $enviado = ($fila["enviado_central"] == 1) ? "Sí" : "No";

    Y en lugar de imprimir directamente enviado_central, muestra $enviado.

    Además, puedes mejorar el campo estado_alerta manteniéndolo como texto.

    La parte completa quedaría así:

    while ($fila = $resultado->fetch_assoc()) {
        $enviado = ($fila["enviado_central"] == 1) ? "Sí" : "No";
    
        echo "<tr>";
        echo "<td>" . $fila["id"] . "</td>";
        echo "<td>" . $fila["raspberry_id"] . "</td>";
        echo "<td>" . $fila["nombresensor"] . "</td>";
        echo "<td>" . $fila["lectura1"] . "</td>";
        echo "<td>" . $fila["lectura2"] . "</td>";
        echo "<td>" . $fila["lectura3"] . "</td>";
        echo "<td>" . $fila["fecha_hora"] . "</td>";
        echo "<td>" . $fila["alumnoEncargado"] . "</td>";
        echo "<td>" . $fila["descripcionSensor"] . "</td>";
        echo "<td>" . $fila["estado_alerta"] . "</td>";
        echo "<td>" . $enviado . "</td>";
        echo "</tr>";
    }

    Parte 8. Añadir estilo visual básico con CSS

    Ahora vas a mejorar un poco la apariencia de la página.

    Paso 1. Añadir estilos dentro del <head>

    Sustituye el <head> por este:

    <head>
        <meta charset="UTF-8">
        <title>Lecturas RaspyAlarma</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                background-color: #f4f6f8;
                margin: 20px;
            }
    
            h1 {
                color: #1f3c88;
            }
    
            table {
                width: 100%;
                border-collapse: collapse;
                background: white;
            }
    
            th {
                background-color: #1f3c88;
                color: white;
                padding: 10px;
            }
    
            td {
                padding: 8px;
                border: 1px solid #ccc;
                text-align: center;
            }
    
            tr:nth-child(even) {
                background-color: #f2f2f2;
            }
        </style>
    </head>

    Guarda y recarga la página.


    Parte 9. Colorear visualmente el estado de alerta

    Ahora vas a resaltar el estado de alerta.

    Paso 1. Añadir una variable para el color

    Dentro del while, añade esta lógica:

    $enviado = ($fila["enviado_central"] == 1) ? "Sí" : "No";
    
    $colorAlerta = "black";
    
    if ($fila["estado_alerta"] == "normal") {
        $colorAlerta = "green";
    } elseif ($fila["estado_alerta"] == "aviso") {
        $colorAlerta = "orange";
    } elseif ($fila["estado_alerta"] == "critico") {
        $colorAlerta = "red";
    }

    Paso 2. Mostrar el estado con color

    Sustituye la celda de estado_alerta por esta:

    echo "<td style='color:$colorAlerta; font-weight:bold;'>" . $fila["estado_alerta"] . "</td>";

    Esto hará que:

    • normal aparezca en verde,
    • aviso en naranja,
    • critico en rojo.

    Parte 10. Crear una versión que muestre solo las últimas lecturas

    A veces no interesa ver todo, sino solo las más recientes.

    Paso 1. Crear el archivo ultimas_lecturas.php

    nano ultimas_lecturas.php

    Pega este contenido:

    <?php
    include("conexion.php");
    
    $sql = "SELECT * FROM lecturas_sensores ORDER BY fecha_hora DESC LIMIT 10";
    $resultado = $conexion->query($sql);
    ?>
    
    <!DOCTYPE html>
    <html lang="es">
    <head>
        <meta charset="UTF-8">
        <title>Últimas lecturas</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                background-color: #f4f6f8;
                margin: 20px;
            }
    
            h1 {
                color: #1f3c88;
            }
    
            table {
                width: 100%;
                border-collapse: collapse;
                background: white;
            }
    
            th {
                background-color: #1f3c88;
                color: white;
                padding: 10px;
            }
    
            td {
                padding: 8px;
                border: 1px solid #ccc;
                text-align: center;
            }
    
            tr:nth-child(even) {
                background-color: #f2f2f2;
            }
        </style>
    </head>
    <body>
    
    <h1>Últimas 10 lecturas registradas</h1>
    
    <?php
    if ($resultado->num_rows > 0) {
        echo "<table>";
        echo "<tr>
                <th>ID</th>
                <th>Raspberry</th>
                <th>Sensor</th>
                <th>Lectura 1</th>
                <th>Lectura 2</th>
                <th>Lectura 3</th>
                <th>Fecha y hora</th>
                <th>Estado alerta</th>
              </tr>";
    
        while ($fila = $resultado->fetch_assoc()) {
            $colorAlerta = "black";
    
            if ($fila["estado_alerta"] == "normal") {
                $colorAlerta = "green";
            } elseif ($fila["estado_alerta"] == "aviso") {
                $colorAlerta = "orange";
            } elseif ($fila["estado_alerta"] == "critico") {
                $colorAlerta = "red";
            }
    
            echo "<tr>";
            echo "<td>" . $fila["id"] . "</td>";
            echo "<td>" . $fila["raspberry_id"] . "</td>";
            echo "<td>" . $fila["nombresensor"] . "</td>";
            echo "<td>" . $fila["lectura1"] . "</td>";
            echo "<td>" . $fila["lectura2"] . "</td>";
            echo "<td>" . $fila["lectura3"] . "</td>";
            echo "<td>" . $fila["fecha_hora"] . "</td>";
            echo "<td style='color:$colorAlerta; font-weight:bold;'>" . $fila["estado_alerta"] . "</td>";
            echo "</tr>";
        }
    
        echo "</table>";
    } else {
        echo "<p>No hay lecturas disponibles.</p>";
    }
    
    $conexion->close();
    ?>
    
    </body>
    </html>

    Guarda y prueba en el navegador.


    Parte 11. Crear una página con resumen rápido

    Ahora vas a crear una página que muestre algunos datos resumidos:

    • número total de registros,
    • número de alertas críticas,
    • número de registros pendientes de enviar.

    Paso 1. Crear el archivo resumen.php

    nano resumen.php

    Pega este contenido:

    <?php
    include("conexion.php");
    
    $sqlTotal = "SELECT COUNT(*) AS total FROM lecturas_sensores";
    $sqlCriticos = "SELECT COUNT(*) AS criticos FROM lecturas_sensores WHERE estado_alerta = 'critico'";
    $sqlPendientes = "SELECT COUNT(*) AS pendientes FROM lecturas_sensores WHERE enviado_central = 0";
    
    $total = $conexion->query($sqlTotal)->fetch_assoc()["total"];
    $criticos = $conexion->query($sqlCriticos)->fetch_assoc()["criticos"];
    $pendientes = $conexion->query($sqlPendientes)->fetch_assoc()["pendientes"];
    ?>
    
    <!DOCTYPE html>
    <html lang="es">
    <head>
        <meta charset="UTF-8">
        <title>Resumen RaspyAlarma</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                background-color: #f4f6f8;
                margin: 20px;
            }
    
            h1 {
                color: #1f3c88;
            }
    
            .caja {
                background: white;
                border: 1px solid #ccc;
                padding: 20px;
                margin-bottom: 15px;
                font-size: 20px;
            }
        </style>
    </head>
    <body>
    
    <h1>Resumen del sistema RaspyAlarma</h1>
    
    <div class="caja">Total de registros: <?php echo $total; ?></div>
    <div class="caja">Alertas críticas: <?php echo $criticos; ?></div>
    <div class="caja">Pendientes de enviar al servidor central: <?php echo $pendientes; ?></div>
    
    </body>
    </html>

    Guarda y pruébalo en el navegador.


    Parte 12. Qué debe comprobar el alumno

    Debes verificar que:

    • PHP conecta con la base de datos,
    • la consulta SQL recupera datos,
    • la tabla HTML se genera correctamente,
    • los campos se muestran completos,
    • el estado de alerta aparece bien,
    • el campo enviado_central se interpreta correctamente,
    • y las páginas son accesibles desde el navegador.

    Parte 13. Errores frecuentes

    Error 1. Página en blanco

    Suele ocurrir por:

    • un error de sintaxis en PHP,
    • una etiqueta mal cerrada,
    • o un problema en la conexión.

    Error 2. Error de conexión a la base de datos

    Comprueba:

    • usuario,
    • contraseña,
    • nombre de la base,
    • y que MariaDB esté arrancada.

    Error 3. No aparecen datos

    Comprueba:

    • que existan registros en lecturas_sensores,
    • que el SELECT esté bien escrito,
    • y que el script Python haya insertado correctamente.

    Error 4. Apache no muestra la página

    Comprueba:

    • que Apache esté arrancado,
    • que el archivo esté en /var/www/html/raspialarma,
    • y que la URL sea correcta.

    Parte 14. Orden recomendado de trabajo

    Debes seguir este orden:

    Fase 1

    Crear conexion.php.

    Fase 2

    Probar la conexión con prueba_conexion.php.

    Fase 3

    Crear ver_lecturas.php con salida simple.

    Fase 4

    Transformar la salida en tabla HTML.

    Fase 5

    Añadir estilos CSS.

    Fase 6

    Resaltar el estado de alerta y el envío central.

    Fase 7

    Crear páginas adicionales como ultimas_lecturas.php y resumen.php.


    Parte 15. Qué debes entregar

    La práctica deberá incluir:

    • archivo conexion.php,
    • archivo prueba_conexion.php,
    • archivo ver_lecturas.php,
    • archivo ultimas_lecturas.php,
    • archivo resumen.php,
    • capturas de funcionamiento en el navegador,
    • explicación de qué muestra cada página,
    • y evidencia de que los datos provienen realmente de MariaDB.

    Parte 16. Resultado esperado

    Al finalizar la práctica deberás tener un pequeño sistema web local capaz de consultar y mostrar los datos recogidos por tu Raspberry.

    Tu Raspberry ya no solo capturará y almacenará datos, sino que además ofrecerá un primer panel web para visualizarlos.

    Ese será el punto de partida para futuras mejoras como:

    • filtros por sensor,
    • búsquedas por Raspberry,
    • páginas de alertas,
    • gráficas,
    • exportación de datos,
    • y sincronización con el servidor central.

    Resumen rápido de archivos

    conexion.php

    Se encarga de conectar PHP con MariaDB.

    prueba_conexion.php

    Comprueba que la conexión funciona.

    ver_lecturas.php

    Muestra todos los registros de la tabla.

    ultimas_lecturas.php

    Muestra solo las últimas lecturas.

    resumen.php

    Muestra indicadores rápidos del sistema.

  • 7 – Creación del servidor central de recogida y visualización de sensores

    7 – Creación del servidor central de recogida y visualización de sensores

    🎧 Resumen en audio del tema
    Escucha este breve audio para repasar las ideas principales de la tarea antes de continuar o al finalizar la lectura.

    Introducción

    Hasta este momento, varias Raspberry Pi del proyecto RaspyAlarma ya son capaces de leer información de sensores y almacenarla en una base de datos local. Ese primer paso permite que cada nodo funcione de manera autónoma y conserve sus propias lecturas.

    Ahora vamos a dar el siguiente salto: construir un servidor central que reciba los datos generados por todas las Raspberry y los almacene en una base de datos general. A partir de esa información centralizada, desarrollaremos también una interfaz web que permita consultar el estado de todos los sensores, visualizar gráficas, detectar alertas y disponer de un histórico común.

    Aunque en un sistema real normalmente existiría un único servidor central, en esta práctica se permitirá que el alumnado trabaje de forma flexible: pueden hacerlo individualmente o en grupos, e incluso pueden desplegar más de un servidor central para repartir tareas y favorecer la participación.

    Reto del proyecto

    Debéis construir el servidor central del sistema RaspyAlarma. Este servidor se ejecutará en una máquina virtual Ubuntu alojada en Proxmox y será el encargado de recibir, almacenar y mostrar las lecturas de sensores enviadas por distintas Raspberry Pi.

    Cada Raspberry seguirá guardando sus datos en local, pero además tendrá que sincronizar los registros pendientes con la base de datos central. A partir de esta información, deberéis desarrollar una interfaz web que permita visualizar el estado de todos los sensores, consultar históricos y representar gráficas.

    El objetivo no es solo almacenar datos, sino crear una arquitectura distribuida funcional, organizada y ampliable, similar a la que podría utilizarse en un entorno profesional real.


    Objetivos del proyecto

    Al finalizar esta práctica, el alumnado deberá ser capaz de:

    • Desplegar una máquina virtual Ubuntu en Proxmox que actuará como servidor central.
    • Instalar y configurar en ella los servicios necesarios.
    • Diseñar una base de datos central para almacenar las lecturas de todas las Raspberry.
    • Implementar un mecanismo para recibir datos desde los nodos remotos.
    • Guardar las lecturas recibidas en una base de datos general.
    • Crear una interfaz web para visualizar la información agregada.
    • Mostrar listados, filtros, estados de alerta y gráficas.
    • Comprender la diferencia entre almacenamiento local en nodos y centralización de información.
    • Simular una arquitectura real de monitorización distribuida.

    Escenario de trabajo

    Imaginad que habéis diseñado un sistema de asistencia y monitorización distribuido. Cada Raspberry Pi recoge lecturas de sensores instalados en distintos puntos y almacena esos datos localmente. Sin embargo, eso no es suficiente para gestionar el sistema completo.

    Necesitamos un centro de control, una máquina central capaz de:

    • Recibir información desde todas las Raspberry.
    • Unificar los datos en una sola base de datos.
    • Mostrar el estado general del sistema.
    • Generar paneles visuales con tablas y gráficas.
    • Identificar situaciones de alerta.
    • Servir como base para futuras ampliaciones, como envío de correos, paneles de incidencias o integración con otros sistemas.

    Arquitectura propuesta

    La arquitectura recomendada para esta práctica será la siguiente:

    Nodo Raspberry Pi

    Cada Raspberry:

    • Lee sensores localmente.
    • Guarda sus lecturas en su propia base de datos.
    • Identifica qué registros todavía no han sido enviados al servidor central.
    • Envía únicamente esos registros pendientes.
    • Marca los registros como enviados cuando el servidor central los confirma.

    Servidor central Ubuntu

    La máquina virtual Ubuntu:

    • Ejecuta Apache o Nginx.
    • Ejecuta PHP para la parte web y la recepción de datos.
    • Ejecuta MariaDB o MySQL para almacenar todas las lecturas.
    • Expone una ruta o endpoint para recibir los datos enviados por las Raspberry.
    • Ofrece un panel web para visualizar y analizar la información.

    Idea importante de diseño

    Aunque técnicamente se podría hacer que el servidor central se conecte a las bases de datos de las Raspberry y “tire” de los datos, no es la opción recomendada para esta práctica.

    La mejor solución didáctica y técnica es esta:

    • La Raspberry guarda sus lecturas en local.
    • La Raspberry envía periódicamente los registros pendientes al servidor central.
    • El servidor central responde confirmando la recepción.
    • La Raspberry marca esos registros como enviados.

    Este enfoque tiene varias ventajas:

    • Es más realista.
    • Es más seguro.
    • Evita abrir las bases de datos de las Raspberry al exterior.
    • Reduce problemas de conectividad.
    • Permite trabajar con sincronización parcial y tolerancia a fallos.

    Requisitos de la práctica

    El sistema deberá cumplir, como mínimo, con los siguientes requisitos.

    En el servidor central

    • Máquina virtual Ubuntu funcionando en Proxmox.
    • IP fija o localizable dentro de la red.
    • Apache + PHP + MariaDB instalados.
    • Base de datos central creada.
    • Endpoint de recepción de datos funcionando.
    • Interfaz web operativa.

    En las Raspberry

    • Base de datos local con lecturas ya almacenadas.
    • Script de sincronización hacia el servidor central.
    • Uso de un identificador único de Raspberry.
    • Campo que indique si el registro ya fue enviado.
    • Capacidad para reenviar registros pendientes si hubo error.

    Tecnologías recomendadas

    Para esta práctica se recomienda utilizar:

    • Proxmox para alojar la máquina virtual.
    • Ubuntu Server como sistema operativo del servidor central.
    • Apache como servidor web.
    • PHP para el desarrollo del endpoint y del panel web.
    • MariaDB/MySQL como sistema gestor de base de datos.
    • Python en las Raspberry para el envío de datos.
    • Cron para automatizar los envíos periódicos.
    • Chart.js para representar gráficas en la interfaz web.

    Parte 1 – Creación del servidor central

    Paso 1. Crear la máquina virtual en Proxmox

    Cada grupo o alumno deberá crear una máquina virtual Ubuntu en Proxmox que actuará como servidor central.

    Características recomendadas

    • 2 vCPU
    • 2 GB o 4 GB de RAM
    • 20 GB de disco
    • Ubuntu Server
    • Adaptador de red en la misma red que las Raspberry

    Tareas

    • Crear la VM.
    • Instalar Ubuntu Server.
    • Configurar nombre del equipo.
    • Configurar acceso por usuario y contraseña.
    • Anotar la IP asignada.

    Recomendación

    Es buena idea asignar una IP fija o una reserva DHCP, para que las Raspberry sepan siempre a qué dirección enviar los datos.


    Paso 2. Actualizar el sistema

    Una vez instalada la VM, actualizad el sistema:

    sudo apt update && sudo apt upgrade -y

    Paso 3. Instalar los paquetes necesarios

    Instalad los componentes del servidor central:

    sudo apt install apache2 mariadb-server php libapache2-mod-php php-mysql php-json php-mbstring -y

    Comprobad que Apache está funcionando:

    sudo systemctl status apache2

    Comprobad también MariaDB:

    sudo systemctl status mariadb

    Paso 4. Preparar la base de datos central

    Acceded a MariaDB:

    sudo mysql

    Cread la base de datos general del proyecto. Por ejemplo:

    CREATE DATABASE raspialarma_central;

    Después, cread un usuario específico para la aplicación:

    CREATE USER 'raspyadmin'@'localhost' IDENTIFIED BY 'TuPasswordSegura';
    GRANT ALL PRIVILEGES ON raspialarma_central.* TO 'raspyadmin'@'localhost';
    FLUSH PRIVILEGES;
    EXIT;

    Parte 2 – Diseño de la base de datos central

    Paso 5. Crear la tabla principal de lecturas

    La tabla central debe recoger información común de todas las Raspberry y de todos los sensores.

    Estructura recomendada

    USE raspialarma_central;CREATE TABLE lecturas_centrales (
    id INT AUTO_INCREMENT PRIMARY KEY,
    id_raspberry VARCHAR(100) NOT NULL,
    nombresensor VARCHAR(100) NOT NULL,
    lectura1 VARCHAR(100),
    lectura2 VARCHAR(100),
    lectura3 VARCHAR(100),
    fecha_hora DATETIME NOT NULL,
    alumnoEncargado VARCHAR(100),
    descripcionSensor TEXT,
    estado_alerta VARCHAR(50) DEFAULT 'normal',
    fecha_recepcion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    origen_ip VARCHAR(100),
    hash_registro VARCHAR(255),
    UNIQUE KEY unique_registro (id_raspberry, nombresensor, fecha_hora, hash_registro)
    );

    Explicación de algunos campos importantes

    id_raspberry

    Identifica de forma única de qué Raspberry procede el dato.

    estado_alerta

    Permite saber si la lectura está en estado normal, aviso o alerta.

    fecha_recepcion

    Indica cuándo llegó el registro al servidor central.

    origen_ip

    Puede almacenar la IP desde la que se envió el dato.

    hash_registro

    Sirve para evitar duplicados si una Raspberry vuelve a mandar el mismo dato.


    Mejora interesante

    También podéis crear una tabla de nodos Raspberry:

    CREATE TABLE raspberries (
        id INT AUTO_INCREMENT PRIMARY KEY,
        id_raspberry VARCHAR(100) UNIQUE NOT NULL,
        nombre VARCHAR(100),
        ubicacion VARCHAR(100),
        descripcion TEXT,
        ultima_conexion DATETIME,
        estado VARCHAR(50) DEFAULT 'activo'
    );

    Esto os permitirá saber qué nodos existen, cuál fue su última conexión y tener una visión más ordenada del sistema.


    Parte 3 – Recepción de datos en el servidor central

    Paso 6. Crear el endpoint de recepción

    En el servidor Ubuntu, cread una carpeta para vuestra aplicación:

    sudo mkdir -p /var/www/html/raspialarma
    sudo chown -R $USER:$USER /var/www/html/raspialarma

    Dentro de esa carpeta, cread un archivo llamado por ejemplo:

    recibir_datos.php

    Este archivo actuará como punto de recepción de datos enviados desde las Raspberry.


    ¿Cómo funcionará?

    La idea es que cada Raspberry envíe una petición HTTP POST al servidor central con los datos pendientes. El servidor:

    • Recibe la información.
    • La valida.
    • La inserta en la base de datos central.
    • Devuelve una respuesta indicando si el envío fue correcto.

    Formato de envío recomendado

    Podéis trabajar en JSON. Por ejemplo, una Raspberry podría enviar algo parecido a esto:

    {
    "id_raspberry": "raspi-aula-01",
    "lecturas": [
    {
    "nombresensor": "temperatura",
    "lectura1": "24.5",
    "lectura2": null,
    "lectura3": null,
    "fecha_hora": "2026-04-21 10:15:00",
    "alumnoEncargado": "Ana",
    "descripcionSensor": "Sensor de temperatura laboratorio",
    "estado_alerta": "normal",
    "hash_registro": "abc123"
    }
    ]
    }

    Qué debe hacer el endpoint

    El archivo recibir_datos.php deberá:

    • Leer el JSON recibido.
    • Comprobar que el identificador de Raspberry existe.
    • Recorrer todas las lecturas recibidas.
    • Insertarlas en la tabla central.
    • Evitar duplicados.
    • Devolver una respuesta JSON con el resultado.

    Respuesta recomendada

    {
    "status": "ok",
    "insertados": 5,
    "duplicados": 1
    }

    Mejora recomendable de seguridad

    Para que no cualquier equipo mande datos falsos, podéis añadir una clave compartida o token simple.

    Por ejemplo, que cada Raspberry envíe también:

    • api_key
    • o una cabecera personalizada

    Y el servidor compruebe que coincide con la esperada.

    No hace falta complicarlo demasiado: para una práctica educativa, una clave sencilla ya es suficiente para introducir el concepto de autenticación entre máquinas.


    Parte 4 – Sincronización desde las Raspberry

    Paso 7. Adaptar el script Python de cada Raspberry

    Cada Raspberry ya guarda datos en local. Ahora deberá además:

    • Consultar en su base de datos local los registros no enviados.
    • Preparar un JSON con esos registros.
    • Enviarlo al servidor central.
    • Si el servidor responde correctamente, marcar esos registros como enviados.

    Lógica del proceso

    El flujo correcto debería ser este:

    1. La Raspberry lee el sensor.
    2. Inserta la lectura en su base local.
    3. Cada cierto tiempo, otro script consulta los registros con enviado_central = 0.
    4. Los envía al servidor central.
    5. Si el servidor responde correctamente, esos registros pasan a enviado_central = 1.

    Campo obligatorio en la base local

    Recordad que la tabla local de cada Raspberry debe incluir algo como:

    • id_raspberry
    • estado_alerta
    • enviado_central

    Esto permitirá gestionar la sincronización correctamente.


    Frecuencia de envío

    Podéis programar el script para que se ejecute con cron cada:

    • 1 minuto
    • 5 minutos
    • o el intervalo que considere el grupo

    Eso dependerá del ritmo de captura de datos.


    Parte 5 – Interfaz web del servidor central

    Paso 8. Crear el panel web principal

    Una vez que el servidor central ya recibe datos, habrá que crear una web que muestre toda la información agregada.

    La interfaz deberá desarrollarse en PHP y deberá leer los datos desde la base de datos central.


    Contenido mínimo del panel

    El panel debería mostrar, como mínimo:

    Vista general

    • Número total de lecturas recibidas.
    • Número de Raspberry activas.
    • Número de sensores registrados.
    • Número de alertas.

    Tabla de lecturas

    • ID
    • Raspberry origen
    • Nombre del sensor
    • Lecturas
    • Fecha y hora
    • Estado de alerta

    Filtros

    • Filtrar por Raspberry
    • Filtrar por sensor
    • Filtrar por fecha
    • Filtrar por estado de alerta

    Gráficas

    • Evolución temporal de un sensor
    • Número de alertas por nodo
    • Distribución de lecturas por tipo de sensor

    Paso 9. Crear una portada tipo dashboard

    Una buena práctica es construir una página principal con tarjetas o bloques visuales.

    Ejemplo de bloques

    • Total de nodos conectados
    • Última lectura recibida
    • Sensores en alerta
    • Total de registros

    Esto hace que el proyecto parezca mucho más profesional y permite al alumnado trabajar el diseño de interfaces orientadas a monitorización.


    Paso 10. Crear gráficas con Chart.js

    Para representar datos de forma visual, se recomienda utilizar Chart.js.

    Ejemplos de gráficas que podéis pedir:

    • Temperatura a lo largo del tiempo
    • Número de lecturas por Raspberry
    • Estados de alerta por tipo de sensor
    • Comparativa entre varios nodos

    Paso 11. Crear una página de detalle por Raspberry

    Además del panel general, es muy interesante que exista una vista individual para cada Raspberry.

    Esa página podría mostrar:

    • Identificador del nodo
    • Ubicación
    • Última conexión
    • Sensores asociados
    • Histórico de lecturas
    • Alertas detectadas

    Esto da una estructura mucho más completa al proyecto.


    Parte 6 – Funcionalidades extra recomendadas

    Aquí es donde puedes enriquecer muchísimo el proyecto.

    1. Sistema de alertas visuales

    Si una lectura supera un umbral, mostrarla en rojo o destacarla visualmente.

    Ejemplos:

    • Temperatura demasiado alta
    • Humedad anómala
    • Falta de actualización de un nodo

    2. Detección de Raspberry inactivas

    Si una Raspberry no envía datos en un tiempo determinado, el panel puede marcarla como:

    • Activa
    • Inactiva
    • Desconectada

    Esto es muy útil en un sistema distribuido real.


    3. Registro de logs

    Crear una tabla o archivo de log donde quede constancia de:

    • Envíos recibidos
    • Fallos de sincronización
    • Intentos inválidos
    • Errores de base de datos

    Así introducís el concepto de auditoría del sistema.


    4. Histórico de incidencias

    Podéis añadir una tabla para guardar incidencias detectadas:

    • Fecha
    • Raspberry
    • Tipo de problema
    • Descripción
    • Estado de resolución

    5. Exportación de datos

    Añadir una opción para exportar lecturas en CSV.

    Esto da mucho valor al proyecto porque permite luego tratar la información en Excel, LibreOffice Calc o herramientas de análisis.


    6. Panel responsive

    Hacer que la web se vea razonablemente bien en móvil, tablet y PC.


    7. Inicio de sesión

    Como ampliación, podéis pedir una pequeña autenticación en PHP para que el panel central no sea público.


    8. Mapa o distribución física

    Si queréis darle más fuerza visual, podéis representar cada Raspberry por ubicación o sala.



    Entregables

    Entregable 1 – Infraestructura

    Documento con:

    • Nombre del servidor
    • IP
    • Capturas de la VM
    • Servicios instalados

    Entregable 2 – Base de datos

    • Script SQL de creación
    • Explicación de tablas
    • Justificación del diseño

    Entregable 3 – Recepción de datos

    • Código del endpoint
    • Ejemplo de JSON recibido
    • Capturas de pruebas correctas

    Entregable 4 – Sincronización

    • Script Python de envío
    • Consulta de pendientes
    • Marcado de enviados

    Entregable 5 – Panel web

    • Capturas del dashboard
    • Tablas
    • Filtros
    • Gráficas

    Entregable 6 – Mejoras

    • Seguridad
    • Logs
    • Alertas
    • Exportación
    • Detección de nodos inactivos

    Criterios de evaluación orientativos

    Puedes evaluarlo así:

    Diseño de arquitectura

    • Claridad del planteamiento
    • Separación entre nodo y central
    • Coherencia técnica

    Base de datos

    • Estructura correcta
    • Uso de claves
    • Evitar duplicados
    • Campos útiles

    Comunicación entre sistemas

    • Envío correcto de datos
    • Gestión de errores
    • Registros marcados como enviados

    Interfaz web

    • Funcionalidad
    • Claridad visual
    • Utilidad de filtros
    • Gráficas correctas

    Mejoras

    • Seguridad
    • Alertas
    • Logs
    • Exportación
    • Control de nodos

    Documentación

    • Orden
    • Explicaciones
    • Capturas
    • Comprensión real del sistema

    Resultado final esperado

    Al terminar la práctica, el alumnado habrá construido un sistema completo con esta lógica:

    • Varias Raspberry recogen datos.
    • Cada una los almacena localmente.
    • Cada una sincroniza con el servidor central.
    • El servidor central unifica la información.
    • Un panel web muestra todo el sistema de manera visual y organizada.

    En otras palabras, no habrán hecho solo una página PHP, sino una infraestructura distribuida de monitorización, que ya se parece bastante a una solución profesional real.


    Ampliaciones que puedes realizar

    Esto te puede venir bien para futuras fases del proyecto:

    Envío de correos de alerta

    Cuando un sensor entre en estado crítico, enviar correo automático.

    API REST real

    En lugar de un PHP simple, crear una pequeña API con rutas más organizadas.

    Panel de administración

    Permitir alta de nodos, edición de sensores y gestión de incidencias.

    Copias de seguridad

    Programar backups automáticos de la base de datos central.


  • REDACTAR UNA MEMORIA TÉCNICA

    REDACTAR UNA MEMORIA TÉCNICA

    ¿Para qué sirve una memoria técnica?

    Una memoria técnica no es “un trabajo para entregar”.
    Es un documento que permite que otra persona pueda entender y reproducir exactamente lo que has hecho, sin preguntarte nada.

    En el entorno profesional, documentar es tan importante como configurar un servidor.

    Una memoria técnica es un documento estructurado que explica de forma clara, objetiva y detallada cómo se ha realizado un trabajo técnico.

    • No es un resumen.
    • No es una redacción larga.
    • No es una opinión.

    Es la descripción precisa de un proceso técnico para que otra persona pueda entenderlo, evaluarlo y, si quiere, reproducirlo.

    En ingeniería y en informática, la memoria técnica cumple una función muy concreta: convertir lo que has hecho en conocimiento transferible.

    Si configuras un servidor y no lo documentas, ese conocimiento vive solo en tu cabeza.
    Si lo documentas correctamente, se convierte en algo útil para un equipo, una empresa o incluso para ti mismo dentro de seis meses.

    Se usa principalmente para:

    – Documentar proyectos técnicos.
    – Justificar decisiones técnicas.
    – Permitir que otros reproduzcan una instalación o configuración.
    – Evaluar trabajos académicos o profesionales.
    – Dejar constancia formal de lo realizado.

    En el ámbito educativo, sirve además para entrenar una competencia clave: pensar de forma estructurada. Cuando redactas una memoria técnica estás organizando ideas, justificando pasos y demostrando que entiendes lo que haces.

    En el mundo profesional, la memoria técnica evita el caos. Imagina una empresa donde nadie documenta nada. Cada vez que algo falla, todo empieza desde cero. Eso no es ingeniería, es improvisación.

    Una buena memoria técnica tiene tres características fundamentales:

    • Precisión: explica exactamente lo que se ha hecho.
    • Claridad: cualquier técnico puede entenderla.
    • Reproducibilidad: permite repetir el proceso con el mismo resultado.

    En resumen, una memoria técnica transforma acciones en conocimiento estructurado. Y esa capacidad marca la diferencia entre “alguien que sabe hacer cosas” y “un profesional capaz de trabajar en equipo y mantener sistemas complejos”.

    Y aquí viene la idea interesante: escribir una memoria técnica no es solo comunicar. Es una forma de comprobar si realmente entiendes lo que has hecho. Porque si no puedes explicarlo con claridad, probablemente todavía no lo dominas del todo.

    Documentar es pensar. Y pensar bien es la base de cualquier disciplina técnica.


    ESTRUCTURA OBLIGATORIA DE LA MEMORIA


    1. Portada

    Debe incluir:

    • Título del proyecto o práctica
    • Módulo
    • Nombre del alumno
    • Fecha
    • Versión del documento (por ejemplo: 1.0)

    Ejemplo:

    Instalación y configuración de servidor Apache en Ubuntu Server


    2. Objetivo del proyecto

    En este apartado debes explicar:

    • Qué vas a realizar.
    • Qué problema estás resolviendo.
    • Qué resultado final se espera.

    Debe ser claro y técnico.

    ❌ Incorrecto:

    Vamos a instalar Apache.

    ✅ Correcto:

    Implementar y configurar un servidor Apache en Ubuntu Server para alojar un sitio web accesible desde la red local.


    3. Descripción del entorno

    Aquí debes indicar las condiciones técnicas en las que trabajas.

    3.1 Hardware

    • Equipo físico o máquina virtual.
    • Recursos asignados (RAM, CPU si es relevante).

    3.2 Sistema Operativo

    • Distribución.
    • Versión.
    • Arquitectura (si procede).

    3.3 Configuración de red

    • Dirección IP.
    • Tipo de red (NAT, Bridge, etc.).
    • Puertos utilizados.

    3.4 Software utilizado

    • Programas o servicios instalados.
    • Versiones si es posible.

    Este apartado es obligatorio porque permite reproducir el entorno.


    4. Desarrollo del proyecto

    Es la parte más importante del documento.

    Cada paso debe incluir:

    1. Qué se va a hacer.
    2. El comando o acción realizada.
    3. Una breve explicación técnica.
    4. El resultado obtenido.

    Ejemplo:


    4.1 Instalación de Apache

    Se instala el servidor web Apache utilizando el gestor de paquetes apt:

    sudo apt update
    sudo apt install apache2 -y

    Tras la instalación, el servicio se inicia automáticamente.
    Se comprueba su estado con el siguiente comando:

    sudo systemctl status apache2

    Resultado: El servicio aparece activo y escuchando en el puerto 80.


    ⚠ No se deben incluir comandos sin explicación.
    ⚠ No se deben insertar capturas sin describir lo que muestran.


    5. Pruebas realizadas

    Aquí debes demostrar que el sistema funciona correctamente.

    Ejemplos de pruebas:

    • Acceso desde navegador.
    • Comprobación de puertos abiertos.
    • Pruebas con ping, curl, netstat, ss, etc.

    Debes indicar:

    • Qué prueba realizas.
    • Qué resultado esperas.
    • Qué resultado obtienes.

    Si no se demuestra el funcionamiento, la práctica no está completa.


    6. Problemas encontrados y soluciones

    En este apartado debes explicar los errores o dificultades que han surgido.

    Formato recomendado:

    ProblemaCausaSolución

    Ejemplo:

    | Error 403 Forbidden | Permisos incorrectos en carpeta web | Se modificaron permisos con chmod |

    Este apartado es obligatorio.
    Si no has tenido problemas, debes indicarlo explícitamente.


    7. Conclusión técnica

    Debe ser breve y profesional.

    No se admiten opiniones personales.

    ❌ Incorrecto:

    Me ha parecido una práctica interesante.

    ✅ Correcto:

    Se ha configurado correctamente el servidor Apache, verificando su funcionamiento desde un equipo cliente de la red local.


    8. Anexos (si procede)

    Aquí puedes incluir:

    • Archivos completos de configuración.
    • Scripts utilizados.
    • Diagramas.
    • Información adicional relevante.

    Normas de redacción obligatorias

    • Utilizar lenguaje técnico y objetivo.
    • No escribir en primera persona.
    • No usar colores innecesarios.
    • Numerar correctamente los apartados.
    • Las capturas deben estar recortadas y ser legibles.
    • El código debe tener formato adecuado.
    • No copiar texto sin entenderlo.

    Errores frecuentes que debes evitar

    • Pegar comandos sin explicación.
    • Incluir capturas enormes sin describirlas.
    • No indicar el entorno utilizado.
    • Escribir conclusiones personales.
    • Entregar documentos sin estructura clara.

    Importante

    Documentar correctamente es una competencia técnica fundamental.

    Diferencia entre una Memoria Técnica y un README

    En el ámbito técnico es habitual encontrarse con ambos documentos: la memoria técnica y el archivo README. Aunque pueden parecer similares, no cumplen la misma función ni tienen el mismo nivel de profundidad.

    Comprender esta diferencia es importante para saber cuándo utilizar cada uno.

    ¿Qué es un README?

    Un README es un archivo introductorio que acompaña a un proyecto, normalmente en un repositorio (por ejemplo, en GitHub).

    Su función es explicar de forma clara y rápida:

    • Qué es el proyecto
    • Para qué sirve
    • Cómo instalarlo
    • Cómo usarlo
    • Qué requisitos necesita

    Un README no describe todo el proceso interno del proyecto. No detalla cada paso realizado. Su objetivo es orientar al usuario o desarrollador que quiera utilizar el proyecto.

    Es más breve, más práctico y más directo.

    Si la memoria técnica explica cómo se construyó el sistema, el README explica cómo usarlo.

    Diferencias principales

    La diferencia clave está en el nivel de profundidad y en el objetivo.

    La memoria técnica documenta el proceso completo.
    El README facilita el uso del resultado.

    Podemos resumirlo así:

    La memoria técnica responde a la pregunta:
    ¿Cómo se ha desarrollado este proyecto?

    El README responde a la pregunta:
    ¿Cómo puedo utilizar este proyecto?


    Ejemplo práctico

    Imaginemos que se instala y configura un servidor web Apache.

    La memoria técnica incluiría:

    • Descripción del entorno
    • Instalación paso a paso
    • Explicación de cada configuración
    • Problemas encontrados
    • Pruebas realizadas
    • Conclusión técnica

    El README incluiría:

    • Descripción del servidor
    • Requisitos mínimos
    • Pasos rápidos para instalarlo
    • Comandos necesarios
    • Cómo acceder al servicio

    La memoria explica el proceso completo.
    El README facilita la puesta en marcha.

  • 1.1 – Primeros pasos con variables

    1.1 – Primeros pasos con variables

    Primeros pasos con variables

    Python es un lenguaje no tipado. Por tanto, no es necesario (ni posible) definir los tipos de datos utilizados al declarar variables.

    En general, en Python las variables se declaran en el momento en el que éstas van a usarse, por lo que la declaración va acompañada del valor que se asigna.

     # El siguiente es un ejemplo de declaración de una variable.
    a = 10
    print("'a' vale:")
    print(a)
    print("")
    
    a = "abcd"
    print("Ahora 'a' vale:")
    print(a)

    Tipos de datos básicos (primitivos):

    – int: Números enteros, positivos o negativos.
    Ejemplo:

    x = 10
    y = -5

    – float: Números decimales.
    Ejemplo:

    pi = 3.14159

    – complex: Números complejos (con parte real e imaginaria).
    Ejemplo:

    z = 2 + 3j

    – bool: Valores lógicos: True o False.
    Ejemplo:

    is_active = True

    – str: Cadenas de texto.
    Ejemplo:

    name = "Python"

    Tipos de datos compuestos (colecciones):

    – list: Lista ordenada, modificable y que puede contener elementos heterogéneos.
    Ejemplo:

    numbers = [1, 2, 3, 4]

    – tuple: Tupla ordenada, inmutable y que puede contener elementos heterogéneos.
    Ejemplo:

    coordinates = (10, 20)

    – dict: Diccionarios, que almacenan pares clave-valor.
    Ejemplo:

    person = {"name": "Alice", "age": 25}

    Función type()

    Sin embargo es importante conocer cuál es el tipo real de los datos que estamos utilizando. Es posible conocer el tipo de cualquier variable (y objeto en general) usando la función type()

    var_1 = 100
    type(var_1)

    Respuesta: int

    var_1 = "abcd"
    type(var_1)

    Respuesta: str

    Trabajando con números

    En Python existen los siguientes tipos numéricos:

    • – integer: 10
    • – float: 10.0
    • – complex: 10+10j

    Así es como se realizan operaciones matemáticas sencillas con Python:

    # Suma
    a = 10
    b = 5
    resultado = a + b
    print(resultado) # Imprime 15

    # Resta
    a = 10
    b = 5
    resultado = a - b
    print(resultado) # Imprime 5

    # Multiplicación
    a = 10
    b = 5
    resultado = a * b
    print(resultado) # Imprime 50

    # División
    a = 10
    b = 5
    resultado = a / b
    print(resultado) # Imprime 2.0

    # Potencia
    a = 10
    b = 2
    resultado = a ** b
    print(resultado) # Imprime 100

    # Módulo (resto de la división)
    a = 10
    b = 3
    resultado = a % b
    print(resultado) # Imprime 1

    Resumen de las operaciones aritmeticas

    En Python podemos realizar varios tipos de operaciones, a continuación podemos ver algunas de ellas.

        x + y:          sum of x and y
        x - y:          difference of x and y
        x * y:          product of x and y
        z / y:          quotient of x and y
        x // y:         (floored) quotient of x and y
        x % y:          remainder of x / y
        -x:             x negated
        +x:             x unchanged
        abs(x):         absolute value or magnitude of x
        int(x):         x converted to integer
        long(x):        x converted to long integer
        float(x):       x converted to floating point
        complex(re,im): a complex number with real part re, imaginary part im. im defaults to zero.
        c.conjugate():  conjugate of the complex number c. (Identity on real numbers)
        divmod(x, y):  _math.trunc(x) the pair (x // y, x % y)
        pow(x, y):      x to the power y
        x ** y:         x to the power y
    
    Adicionalmente, los tipos int y float admiten las siguientes operaciones:
    
        math.trunc(x):  x truncated to Integral
        round(x[, n]):  x rounded to n digits, rounding ties away from zero. If n is omitted, it defaults to 0.
        math.floor(x):  the greatest integral float <= x
        math.ceil(x):   the least integral float >= x
    

    Coversión de tipos

    Si en una expresión aritmética intervienen diferentes tipos de número, Python convierte automáticamente al tipo más general, según el siguiente esquema:


    **int -> float -> complex**

    También es posible convertir tipos de manera explícita mediante casts explícitos, con la siguiente forma: {nombe_tipo}(variable)

    # Trasformación a entero
    int(12.5)
    # Trasformación a valor en coma flotante
    float(12)

    Trabajando con textos

    Tipo string (cadenas de daracteres). Strings delimitados por comillas dobles.

    cadena_a = "Hola"
    cadena_b = "Mundo"
    # Uso del operador "+" para concatenar
    print(cadena_a + " " + cadena_b)

    También es posible escribir strings multi-línea fácilmente

    string_multi_1 = "Esto es un string escrito en \
    dos líneas. En este caso el string se interpreta como una sóla línea."
    print(string_multi_1)
    string_multi_2 = '''Triples comillas: esto es también es un string escrito
    en dos líneas. En este caso se conserva la separación en líneas original.'''
    print(string_multi_2)
    string_multi_3 = """Triples comillas: Esto es también es un string escrito
    en dos líneas. En este caso se conserva la separación en líneas original."""
    print(string_multi_3)

    El resultado seria:

    Esto es un string escrito en dos líneas. En este caso el string se interpreta como una sóla línea.
    Triples comillas: esto es también es un string escrito
    en dos líneas. En este caso se conserva la separación en líneas original.
    Triples comillas: Esto es también es un string escrito
    en dos líneas. En este caso se conserva la separación en líneas original.
            

    Algunos caracteres deben ser ‘escapados’ para poder incluirse en un string. Los más frecuentes son los siguientes:

       Character  Hexadecimal    Description
        \a	       0x07           Bell or alert
        \b	       0x08           Backspace
        \cx	                      Control-x
        \C-x	 	              Control-x
        \e	       0x1b	          Escape
        \f	       0x0c	          Formfeed
        \M-\C-x	 	              Meta-Control-x
        \n	       0x0a           Newline
        \nnn	 	              Octal notation, where n is in the range 0.7
        \r	       0x0d	          Carriage return
        \s	       0x20	          Space
        \t	       0x09	          Tab
        \v	       0x0b	          Vertical tab
        \x	 	                  Character x
        \xnn	 	              Hexadecimal notation, where n is in the range 0.9, a.f, or A.F
            

    Python es muy versatil a la hora de usar operadores, en el caso de los textos podemos aplicar lógica a la propia cadena String

        +         Concatenation - Adds values on either side of the operator
        *         Repetition - Creates new strings, concatenating multiple copies of the same string
        []        Slice - Gives the character from the given index
        [ : ]     Range Slice - Gives the characters from the given range
        in        Membership - Returns true if a character exists in the given string
        not in    Membership - Returns true if a character does not exist in the given string
        r/R       Raw String - Suppresses actual meaning of Escape characters. The syntax for raw strings is exactly     
                  the same as for normal strings with the exception of the raw string operator, the letter "r," which
                  precedes the quotation marks.
                  The "r" can be lowercase (r) or uppercase (R) and must be placed immediately preceding the first Ç
                  quote mark.
        f         Format - Performs String formatting
            

    Ejemplos:

      "HOLA " * 5
                    #->HOLA HOLA HOLA HOLA HOLA
               "J" not in "HOLA"
                    #-> True
               name = "Antonio"
                f"Mi nombre es {name}"
                    #-> Mi nombre es Antonio
    

    Lectura por Teclado con Python

    El comando input() en Python se utiliza para tomar entradas del usuario durante la ejecución de un programa. Permite pausar el programa y esperar a que el usuario ingrese texto, que luego se puede procesar o almacenar en una variable.

    input(prompt)

    prompt (opcional): Una cadena de texto que se muestra al usuario como mensaje. Es útil para indicar qué tipo de entrada se espera. Si no se proporciona, no se muestra ningún mensaje.

        valor = input()
    ------------------------
    Esto lo escribo yo mismo
    
    # Con esto puedo hacer el típico mensaje de introduce un valor:
    
    numero = input("Introduce un numero: ")
    
    # De esta manera al usuario le aparecerá el mensaje de Introduce un valor
    ------------------------
    Introduce un numero: 50
    ------------------------
    
    # Si quiero hacer algo con el resultado obtenido de lo anterior, debo pasarle la opción int a la variable de antes:
    
    numero = int(numero)
    
    # Debo poner la variable = int(variable) y ahora ya se puede sumar o hacer lo que quiera:
    
    numero + 50
    -------------------------
    100
    -------------------------
    
    # Si el usuario pone un número decimal, tenemos que poner float, en lugar de int:
    
    numero = input("Introduce un numero con decimales: ")
    
    # Aquí abajo indico que el usuario va a introducir un número con decimales:
    
    numero = float(numero)
    -------------------------
    Introduce un numero con decimales: 12.5
    -------------------------
    
    # Y ya puedo operar con él sin problemas
    
    numero + 10
    
    # Con float puedo introducir números tanto enteros como decimales.
    -------------------------
    22.5
    -------------------------
    
    # También puedo hacer todo lo anterior en una misma línea a la vez, esta vez con número entero:
    
    numero = float( input("Introduce un número decimal o entero: "))
    
    -------------------------
    Introduce un número decimal o entero: 20
    -------------------------
    
    numero + 40
    
    -------------------------
    60.0
    -------------------------
    
    # Y ahora lo mismo pero con número decimal:
    
    numero = float( input("Introduce un número decimal o entero: "))
    
    -------------------------
    Introduce un número decimal o entero: 10.5
    -------------------------
    
    numero + 40
    
    # Como vemos con lo anterior, tenemos la opción de usar números enteros y decimales
    
    -------------------------
    50.5
    -------------------------
    
    # También puedo hacer esto con nombres; y la variable Mario se me guarda en nombre
    
    nombre = input("Introduce tu nombre: ")
    
    -------------------------
    Introduce tu nombre: Mario
    -------------------------
    
    nombre = input("Introduce tu nombre ")
    apellido = input("Introduce tu apellido ")
    edad = int(input("Introduce tu edad ")) # Para número entero
    estatura = float(input("Introduce tu estatura ")) # Para número decimal
    
    -------------------------
    Introduce tu nombre Mario
    Introduce tu apellido Álvarez
    Introduce tu edad 26
    Introduce tu estatura 1.77
    -------------------------
       

    Ejercicios

    Ejercicio 1: Crea tres variables: una que guarde tu nombre, otra tu edad, y otra tu ciudad. Imprime los valores con un mensaje como:
    «Hola, mi nombre es [nombre], tengo [edad] años y vivo en [ciudad].»

    Ejercicio 2: Declara una variable con tu edad. Luego usa esta variable para calcular cuántos días, horas y minutos has vivido (aproximadamente).

        Ejemplo:
        Días: edad * 365
        Horas: días * 24
        Minutos: horas * 60

    Ejercicio 3: Escribe un programa que pida al usuario su nombre y su comida favorita. Luego imprime un mensaje como:
    «Hola [nombre], me alegra saber que te gusta [comida favorita].»

    Ejercicio 4: Declara una variable de cada tipo:

    • Un número entero (int), por ejemplo, 42.
    • Un número decimal (float), por ejemplo, 3.14.
    • Una cadena de texto (str), por ejemplo, «Hola».
    • Un valor booleano (bool), por ejemplo, True.
    • Imprime el valor y el tipo de cada variable con type().
  • 1.2 – Estructuras condicionales en Python

    1.2 – Estructuras condicionales en Python

    Un programa sin decisiones es como un robot que solo sabe caminar recto hasta chocar contra una pared. Las estructuras condicionales permiten que el programa observe, compare, evalúe… y elija. Son el mecanismo que controla el flujo de ejecución, es decir, el orden en que el programa ejecuta instrucciones.

    En términos simples:

    Si ocurre A → haz X
    Si no ocurre A → haz Y

    Ese pequeño esquema es el corazón de casi todo software: validaciones, menús, videojuegos, seguridad, IA, redes, sistemas operativos… todo decide constantemente.


    ¿Qué es una condición?

    Una condición es una expresión que solo puede ser:

    • True (verdadero)
    • False (falso)

    Ejemplos:

    5 > 3        # True
    10 == 2 # False
    "hola" == "hola" # True

    Python evalúa la condición, y dependiendo del resultado, ejecuta o no un bloque de código.


    1. Condicional simple (if)

    Ejecuta código solo si la condición es verdadera.

    if condición:
    bloque_de_codigo

    Ejemplo:

    edad = 18
    if edad >= 18:
        print("Eres mayor de edad.")

    Si la condición es falsa, no ocurre nada. El programa sigue.


    2. Condicional con alternativa (if – else)

    Aquí el programa tiene dos caminos posibles.

    if condición:
    bloque_si_verdadero
    else:
    bloque_si_falso

    Ejemplo:

    edad = 16
    if edad >= 18:
        print("Eres mayor de edad.")
    else:
        print("Eres menor de edad.")

    Solo uno de los bloques se ejecuta. Nunca ambos.


    3. Condicional múltiple (if – elif – else)

    Cuando el universo tiene más de dos posibilidades.

    if condición1:
    bloque1
    elif condición2:
    bloque2
    elif condición3:
    bloque3
    else:
    bloque_final

    Python evalúa en orden. En cuanto encuentra una condición verdadera, ejecuta su bloque y sale.

    Ejemplo:

    temperatura = 30
    if temperatura > 30:
        print("Hace mucho calor.")
    elif temperatura > 20:
        print("El clima es agradable.")
    else:
        print("Hace frío.")

    Aquí, aunque 30 > 20 también es cierto, no se evalúa porque ya se encontró una condición válida antes.


    4. Condicionales anidadas

    Una decisión dentro de otra decisión. Como una caja dentro de otra caja.

    edad = 20
    tiene_identificacion = True
    if edad >= 18:
        if tiene_identificacion:
            print("Puedes ingresar.")
        else:
            print("Necesitas identificación.")
    else:
        print("Eres menor de edad.")

    Cuidado: demasiada anidación convierte el código en un laberinto. Cuando un if parece una pirámide… algo se puede simplificar.


    Cómo piensa Python: valores «Truth» y «Falsey»

    No todo en Python es literalmente True o False. Algunos valores se consideran falsos automáticamente:

    Falsos:

    • False
    • 0
    • None
    • "" (cadena vacía)
    • [] (lista vacía)
    • {} (diccionario vacío)

    Ejemplo:

    nombre = ""
    if nombre:
        print("Hay nombre")
    else:
        print("Está vacío")

    Resultado → "Está vacío"

    Python interpreta vacío = falso. Esto es muy poderoso y muy peligroso si no se entiende.


    Operadores en condiciones

    Operadores de comparación

    Comparan valores.

    • == igual
    • != distinto
    • < menor
    • > mayor
    • <= menor o igual
    • >= mayor o igual
    if 10 >= 5:
    print("Sí")

    Operadores lógicos

    Permiten combinar condiciones.

    • and → ambas deben ser verdaderas
    • or → al menos una verdadera
    • not → invierte el resultado
    edad = 20
    tiene_id = True
    if edad >= 18 and tiene_id:
        print("Acceso permitido")

    Operadores de pertenencia

    Comprueban si algo está dentro de algo.

    • in
    • not in
    if "a" in "python":
    print("Contiene la letra a")

    ¿Por qué Python no tenía switch?

    Históricamente Python prefería simplicidad. if-elif-else cubría el caso. Pero… desde Python 3.10 existe:

    match-case (el “switch” moderno)

    opcion = 2match opcion:
    case 1:
    print("Opción 1")
    case 2:
    print("Opción 2")
    case 3:
    print("Opción 3")
    case _:
    print("Opción no válida")

    Más limpio cuando hay muchas opciones. Especialmente útil en menús, parsers, interpretes, juegos…


    Buenas prácticas

    1. Evita condiciones innecesarias
    # MAL
    if activo == True:# BIEN
    if activo:
    1. Evita anidar demasiado
    2. Usa nombres claros en variables
    3. Ordena condiciones de más específica a más general
    4. Piensa en todos los casos posibles (incluido el inesperado)

    Ejercicios

    Bloque 1 — Fundamentos del if

    Ejercicio 1
    Pide un número. Si es mayor que 10, muestra mensaje.

    Ejercicio 2
    Pide edad. Mayor o igual 18 → mayor de edad, si no → menor.

    Ejercicio 3
    Número positivo / negativo / cero.


    Bloque 2 — Comparaciones

    Ejercicio 4
    Comparar dos números.

    Ejercicio 5
    Número dentro de rango 1–100.


    Bloque 3 — Lógica y condiciones

    Ejercicio 6
    Detectar vocal.

    Ejercicio 7
    Número → día de la semana.


    Bloque 4 — Aplicación práctica

    Ejercicio 8
    Validar contraseña "secreto123".

    Ejercicio 9
    Clasificar temperatura:

    • ≥ 30 → calor
    • 10–29 → templado
    • < 10 → frío

    Ejercicio 10
    Precio > 1000 → aplicar 10% descuento.


    Bloque 5 — Nivel mental superior (nuevos)

    Ejercicio 11 — Login limitado
    Pide usuario y contraseña.
    Si usuario = "admin" y contraseña correcta → acceso.
    Si falla 3 veces → bloqueado.

    Ejercicio 12 — Número par, impar o múltiplo de 5

    Ejercicio 13 — Año bisiesto

    Regla:

    • divisible por 4
    • no divisible por 100
    • salvo que sea divisible por 400

    Ejercicio 14 — Simulador de menú

    Mostrar:

    1. Ver perfil
    2. Cambiar contraseña
    3. Salir

    Usar match o if.

    Ejercicio 15 — Detector de fuerza de contraseña

    • < 6 → débil
    • 6–10 → media
    • 10 + número → fuerte
  • 1.3  – Colecciones

    1.3 – Colecciones

    Las colecciones en Python son estructuras de datos diseñadas para almacenar grupos de información y manipularlos de forma eficiente. Son el equivalente a cajas especializadas: algunas mantienen el orden, otras eliminan duplicados, otras asocian claves con valores.

    Python ofrece cuatro colecciones fundamentales:

    • Listas (list) → secuencias ordenadas y modificables
    • Tuplas (tuple) → secuencias ordenadas e inmutables
    • Conjuntos (set) → colecciones sin duplicados
    • Diccionarios (dict) → pares clave-valor

    Elegir bien la colección correcta es una habilidad esencial. No es solo sintaxis: es pensamiento estructurado.


    Listas (list)

    Una lista es una colección ordenada y modificable que permite duplicados.

    • Mantienen el orden de inserción
    • Son mutables (se pueden modificar)
    • Permiten duplicados
    • Admiten cualquier tipo de dato

    Crear y usar listas

    mi_lista = [1, 2, 3, 4, 5]print(mi_lista[0])      # 1
    print(mi_lista[-1]) # 5

    Modificar elementos

    mi_lista[1] = 10
    print(mi_lista)

    Añadir elementos

    mi_lista.append(6)      # Añade al final
    mi_lista.insert(0, 99) # Inserta en posición

    Eliminar elementos

    mi_lista.remove(10)     # Elimina por valor
    mi_lista.pop() # Elimina último
    del mi_lista[0] # Elimina por índice

    Slicing (rebanado)

    numeros = [10,20,30,40,50]print(numeros[1:4])   # [20,30,40]
    print(numeros[:3]) # [10,20,30]
    print(numeros[::2]) # [10,30,50]

    List Comprehension (forma elegante)

    cuadrados = [x**2 for x in range(6)]
    print(cuadrados)

    Tuplas (tuple)

    Una tupla es una colección ordenada e inmutable. Una vez creada, no cambia.

    • Ordenadas
    • Inmutables
    • Permiten duplicados
    • Más eficientes y seguras que listas

    Uso típico: datos constantes

    coordenadas = (10.0, 20.0)
    print(coordenadas[0])

    Desempaquetado

    x, y = coordenadas
    print(x, y)

    Tupla de un solo elemento (detalle raro)

    t = (5,)   # sin coma NO es tupla

    Conjuntos (set)

    Un set es una colección sin orden y sin duplicados. Perfecta para matemáticas y limpieza de datos.

    • No ordenados
    • No permiten duplicados
    • Mutables
    • Muy rápidos en búsquedas

    Crear y usar sets

    s = {1,2,3,4}
    s.add(5)
    s.remove(2)

    Operaciones matemáticas

    a = {1,2,3}
    b = {2,3,4}print(a | b) # Unión
    print(a & b) # Intersección
    print(a - b) # Diferencia
    print(a ^ b) # Diferencia simétrica

    Eliminar duplicados manteniendo orden

    lista = [1,2,2,3,4,4,5]
    sin_dup = list(dict.fromkeys(lista))
    print(sin_dup)

    Diccionarios (dict)

    Un diccionario almacena pares clave → valor. Es como una base de datos en miniatura.

    • Acceso por clave
    • Mutables
    • Claves únicas
    • Muy rápidos

    Crear diccionario

    persona = {
    "nombre": "Juan",
    "edad": 25,
    "ciudad": "Madrid"
    }

    Acceder

    print(persona["nombre"])
    print(persona.get("edad"))

    Modificar / Añadir

    persona["edad"] = 26
    persona["profesion"] = "Ingeniero"

    Eliminar

    persona.pop("ciudad")
    del persona["edad"]

    Recorrer diccionario

    for clave, valor in persona.items():
    print(clave, valor)

    Operaciones comunes y trucos reales

    Copia

    a = [1,2,3]
    b = a.copy() # copia independiente

    Ordenar listas

    nums = [5,2,8,1]
    nums.sort()
    print(nums)ordenados = sorted(nums, reverse=True)

    Comprobar pertenencia

    if 3 in nums:
    print("Existe")

    Longitud

    len(nums)

    Comparación de colecciones

    ColecciónOrdenadaMutableDuplicadosUso típico
    ListaSíSíSíSecuencias generales
    TuplaSíNoSíDatos constantes
    SetNoSíNoEliminar duplicados
    DictSí*SíClaves únicasAsociación clave-valor

    *Desde Python 3.7 mantienen orden de inserción.


    Ejemplos guiados

    Acceder al tercer elemento

    numeros = [10,20,30,40,50]
    tercer = numeros[2]
    print(tercer)

    Último elemento de una tupla

    colores = ("rojo","verde","azul")
    print(colores[-1])

    Eliminar duplicados

    nums = [1,2,2,3,4,4,5]
    print(list(set(nums)))

    Añadir clave a diccionario

    persona = {"nombre":"Ana","edad":30}
    persona["ciudad"] = "Barcelona"
    print(persona)

    Ejercicios

    Listas

    1. Crea una lista del 1 al 5 y muestra primero y último.
    2. Cambia «pera» por «uva» en ["manzana","pera","naranja"].
    3. Añade tres números a [10,20,30].

    Tuplas

    1. Crea tupla con tres ciudades.
    2. Extrae el segundo valor de (100,200,300).
    3. Crea tupla vacía y explica cómo añadir valores.

    Sets

    1. Crea {1,2,3} y añade 4.
    2. Comprueba si 5 está en {1,2,3,4}.
    3. Intersección {1,2,3} y {2,3,4}.

    Diccionarios

    1. Crea diccionario con nombre, edad y ciudad.
    2. Cambia un valor.
    3. Elimina una clave.
  • 1.4 – ¿Qué es una lista?

    1.4 – ¿Qué es una lista?

    Una lista en Python es una estructura de datos ordenada, mutable y que permite duplicados.

    Ordenada significa que mantiene el orden en el que insertas los elementos.
    Mutable significa que puedes modificarla después de crearla.
    Permite duplicados significa que puede contener el mismo valor varias veces.

    Es la colección más utilizada en Python.


    Crear una lista

    mi_lista = [1, 2, 3, 4, 5]

    También se puede crear vacía:

    mi_lista = []

    O usando el constructor:

    mi_lista = list((1, 2, 3))

    Características fundamentales

    • Mantienen el orden de inserción
    • Permiten cualquier tipo de dato
    • Admiten tipos mezclados
    • Son dinámicas (crecen y decrecen en tiempo de ejecución)
    mezcla = [1, "hola", 3.14, True]

    Acceso a elementos

    Por índice

    mi_lista = [10, 20, 30, 40]
    print(mi_lista[0])   # 10
    print(mi_lista[-1])  # 40 (último elemento)

    Los índices negativos cuentan desde el final.


    Rebanado (Slicing)

    numeros = [10, 20, 30, 40, 50]
    print(numeros[1:4])   # [20, 30, 40]
    print(numeros[:3])    # [10, 20, 30]
    print(numeros[::2])   # [10, 30, 50]

    Formato general:

    lista[inicio:fin:paso]


    Métodos útiles para listas

    1. Agregar elementos

    append(x)

    Añade un elemento al final.

    mi_lista.append(6)

    insert(i, x)

    Inserta en una posición específica.

    mi_lista.insert(2, 99)

    extend(iterable)

    Añade varios elementos.

    mi_lista.extend([7, 8, 9])

    Diferencia clave:

    a = [1,2]
    a.append([3,4]) # [1,2,[3,4]]
    a.extend([3,4]) # [1,2,3,4]

    2. Eliminar elementos

    remove(x)

    Elimina el primer elemento con ese valor.

    mi_lista.remove(99)

    Si el valor no existe, produce error.


    pop(i)

    Elimina y devuelve el elemento.

    mi_lista.pop()     # elimina último
    mi_lista.pop(2) # elimina posición 2

    clear()

    Vacía la lista.

    mi_lista.clear()

    3. Búsqueda

    index(x)

    Devuelve el índice del primer valor encontrado.

    mi_lista.index(42)

    count(x)

    Cuenta repeticiones.

    mi_lista.count(42)

    4. Ordenar y revertir

    sort()

    Ordena la lista en el lugar.

    mi_lista.sort()
    mi_lista.sort(reverse=True)

    sorted()

    Devuelve nueva lista ordenada.

    nueva = sorted(mi_lista)

    reverse()

    Invierte el orden.

    mi_lista.reverse()

    Ordenar con criterio (key)

    palabras = ["python", "java", "c"]
    palabras.sort(key=len)
    print(palabras)

    Ordena por longitud.


    5. Copiar listas

    copy()

    copia = mi_lista.copy()

    O usando slicing:

    copia = mi_lista[:]

    ⚠ Error común:

    a = [1,2,3]
    b = a # NO es copia, es referencia

    Ambas variables apuntan al mismo objeto.


    Funciones útiles con listas

    len(mi_lista)
    sum(mi_lista)
    min(mi_lista)
    max(mi_lista)

    Convertir iterable en lista:

    list(range(5))

    List Comprehension (forma avanzada y elegante)

    Permite crear listas de manera compacta.

    cuadrados = [x**2 for x in range(6)]
    print(cuadrados)

    Con condición:

    pares = [x for x in range(10) if x % 2 == 0]

    Recorrer listas

    Forma clásica

    for elemento in mi_lista:
    print(elemento)

    Con índice

    for i in range(len(mi_lista)):
    print(i, mi_lista[i])

    Con enumerate (mejor práctica)

    for i, valor in enumerate(mi_lista):
    print(i, valor)

    Listas anidadas (matrices)

    matriz = [
        [1,2,3],
        [4,5,6],
        [7,8,9]
    ]
    print(matriz[1][2])   # 6

    Ejemplo práctico completo

    mi_lista = [3, 1, 4, 1, 5, 9]mi_lista.append(2)
    mi_lista.sort()
    mi_lista.remove(1)print(mi_lista)

    Salida:

    [1, 2, 3, 4, 5, 9]


    Errores comunes

    • Intentar acceder a un índice inexistente
    • Confundir append con extend
    • Copiar listas con asignación directa
    • Modificar lista mientras se recorre

    Ejemplo problemático:

    for x in mi_lista:
    mi_lista.remove(x) # mala práctica

    Cuándo usar listas

    Usa listas cuando:

    • Necesites orden
    • Necesites permitir duplicados
    • Vayas a modificar frecuentemente los datos
    • Necesites indexación

    Métodos principales de las listas en Python

    MétodoQué haceEjemploResultado
    append(x)Añade un elemento al finallista.append(5)[1,2,3,5]
    insert(i, x)Inserta en posición específicalista.insert(1, 9)[1,9,2,3]
    extend(iterable)Añade varios elementoslista.extend([4,5])[1,2,3,4,5]
    remove(x)Elimina el primer valor encontradolista.remove(2)[1,3,4]
    pop()Elimina y devuelve el últimolista.pop()devuelve 4
    pop(i)Elimina por índicelista.pop(1)elimina posición 1
    clear()Vacía la listalista.clear()[]
    index(x)Devuelve índice del valorlista.index(3)2
    count(x)Cuenta repeticioneslista.count(2)1
    sort()Ordena la listalista.sort()[1,2,3]
    sort(reverse=True)Orden descendentelista.sort(reverse=True)[3,2,1]
    reverse()Invierte el ordenlista.reverse()[3,2,1]
    copy()Copia superficialnueva = lista.copy()nueva lista independiente

    Funciones útiles (no son métodos, pero se usan siempre)

    FunciónQué haceEjemplo
    len(lista)Tamaño de la listalen(lista)
    sum(lista)Suma elementossum(lista)
    min(lista)Valor mínimomin(lista)
    max(lista)Valor máximomax(lista)
    sorted(lista)Devuelve lista ordenadasorted(lista)
    list(iterable)Convierte iterable en listalist(range(5))

    Diferencia clave que suele confundir

    a = [1,2]
    a.append([3,4]) # [1,2,[3,4]]
    a.extend([3,4]) # [1,2,3,4]

    append mete un objeto, extend mete elementos.


    Ejercicios

    1. Crea una lista con los números del 1 al 10 y muestra solo los pares.
    2. Ordena una lista de palabras por su longitud.
    3. Elimina todos los duplicados manteniendo el orden.
    4. Crea una matriz 3×3 e imprime la diagonal principal.
    5. Genera una lista con los cubos de los números del 1 al 20 usando list comprehension.

  • 1.5 – ¿Qué es una tupla?

    1.5 – ¿Qué es una tupla?

    Las tuplas son criaturas curiosas del ecosistema Python. Parecen listas… pero no lo son. Son inmutables, compactas y muy seguras. Cuando algo en tu programa no debe cambiar jamás, una tupla es la cápsula criogénica perfecta.


    Una tupla (tuple) es una colección ordenada e inmutable de elementos.

    • Ordenada → mantiene el orden de inserción
    • Inmutable → no se puede modificar tras crearla
    • Permite duplicados
    • Puede contener distintos tipos

    La inmutabilidad las hace más rápidas, más seguras y más ligeras que las listas.


    Crear una tupla

    mi_tupla = (1, 2, 3, 4, 5)

    También se puede crear sin paréntesis:

    mi_tupla = 1, 2, 3, 4, 5

    Tupla vacía:

    vacia = ()

    Tuplas de un solo elemento

    Detalle clásico que rompe cerebros:

    tupla = (42,)   # Correcto
    no_tupla = (42) # Es un entero, NO una tupla

    La coma es obligatoria.


    Acceso a elementos

    Por índice

    t = (10, 20, 30, 40)
    print(t[0])    # 10
    print(t[-1])   # 40

    Slicing (rebanado)

    t = (10, 20, 30, 40, 50)
    print(t[1:4])   # (20, 30, 40)
    print(t[:3])    # (10, 20, 30)
    print(t[::2])   # (10, 30, 50)

    El slicing devuelve otra tupla.


    Métodos de las tuplas

    Las tuplas tienen pocos métodos porque no se pueden modificar.

    count(x)

    Cuenta cuántas veces aparece un valor.

    t = (1,2,3,2,2)
    print(t.count(2)) # 3

    index(x)

    Devuelve la posición del primer valor encontrado.

    t = (10,20,30,40)
    print(t.index(30)) # 2

    Si no existe → error.


    Funciones útiles con tuplas

    t = (10,20,30,40)
    len(t)   # tamaño
    min(t)   # mínimo
    max(t)   # máximo
    sum(t)   # suma (si numéricos)

    Concatenar y repetir tuplas

    Concatenar

    t1 = (1,2,3)
    t2 = (4,5,6)t3 = t1 + t2
    print(t3)

    Repetir

    t = (1,2,3)
    print(t * 2)

    Convertir entre lista y tupla

    Las tuplas no se pueden modificar, pero puedes convertirlas:

    t = (10,20,30)lista = list(t)
    lista.append(40)t = tuple(lista)
    print(t)

    Este es el truco clásico para “modificar” tuplas.


    Desempaquetado de tuplas

    Una de sus habilidades más elegantes.

    t = (10, 20, 30)a, b, c = t
    print(a, b, c)

    Desempaquetado extendido

    t = (1,2,3,4,5)a, *medio, b = t
    print(a) # 1
    print(medio) # [2,3,4]
    print(b) # 5

    Python convierte automáticamente el resto en lista.


    Verificar pertenencia

    t = (10,20,30)print(20 in t)   # True
    print(99 in t) # False

    Recorrer tuplas

    for valor in (1,2,3):
    print(valor)

    Con índice:

    t = (10,20,30)for i, v in enumerate(t):
    print(i, v)

    Por qué usar tuplas

    Usa tuplas cuando:

    • Los datos no deben cambiar
    • Quieres seguridad frente a modificaciones accidentales
    • Necesitas mejor rendimiento
    • Quieres usar datos como claves en diccionarios (las listas no pueden)

    Ejemplo real:

    coordenada = (40.4168, -3.7038)

    Diferencia clave: lista vs tupla

    CaracterísticaListaTupla
    MutableSíNo
    VelocidadMás lentaMás rápida
    MemoriaMásMenos
    MétodosMuchosPocos
    SeguridadBajaAlta

    Errores comunes

    Intentar modificar una tupla:

    t = (1,2,3)
    t[0] = 9 # TypeError

    Olvidar la coma en tupla de un elemento.


    Ejemplo práctico

    mi_tupla = (10, 20, 30, 40, 50)print(mi_tupla[1])
    print(mi_tupla.index(30))
    print(mi_tupla.count(20))lista = list(mi_tupla)
    lista.append(60)mi_tupla = tuple(lista)
    print(mi_tupla)

    Salida:

    (10, 20, 30, 40, 50, 60)

    Métodos principales de las tuplas en Python

    MétodoQué haceEjemploResultado
    count(x)Cuenta cuántas veces aparece un valor(1,2,2,3).count(2)2
    index(x)Devuelve el índice del primer valor encontrado(10,20,30).index(20)1

    Funciones útiles con tuplas

    (No son métodos propios de la tupla, pero se usan constantemente)

    FunciónQué haceEjemploResultado
    len(t)Número de elementoslen((1,2,3))3
    min(t)Valor mínimomin((5,2,9))2
    max(t)Valor máximomax((5,2,9))9
    sum(t)Suma de elementos numéricossum((1,2,3))6
    sorted(t)Devuelve lista ordenadasorted((3,1,2))[1,2,3]
    tuple(iterable)Convierte iterable a tuplatuple([1,2,3])(1,2,3)
    list(t)Convierte tupla a listalist((1,2,3))[1,2,3]

    Operaciones comunes con tuplas

    OperaciónQué haceEjemploResultado
    t1 + t2Concatenar tuplas(1,2) + (3,4)(1,2,3,4)
    t * nRepetir tupla(1,2) * 2(1,2,1,2)
    x in tVerificar pertenencia2 in (1,2,3)True
    x not in tVerificar ausencia9 in (1,2,3)False

    Recordatorio importante

    Las tuplas no tienen métodos para modificar datos porque son inmutables:

    • No append
    • No remove
    • No insert
    • No pop
    • No sort

    Si necesitas modificar → convierte a lista, modifica, y vuelve a tupla.


    Ejercicios

    1. Crea una tupla con 5 números y muestra el primero y último.
    2. Cuenta cuántas veces aparece un número repetido.
    3. Convierte una tupla a lista, añade un valor y vuelve a tupla.
    4. Usa desempaquetado para guardar los valores en variables.
    5. Crea una tupla de coordenadas y verifica si contiene cierto valor.