Categoría: Programación

  • 1.6 – Arrays en JavaScript

    1.6 – Arrays en JavaScript

    Un array es una estructura que permite almacenar múltiples valores en una sola variable.

    Puede contener:

    • Números
    • Texto
    • Objetos
    • Otros arrays
    • Mezclas de todo (JavaScript no es quisquilloso)

    Crear un array

    let numeros= [10, 20, 30];

    También:

    letvacio= [];

    O usando el constructor:

    let lista=new Array(1,2,3);

    Acceder a elementos

    Los arrays empiezan en índice 0 (como casi todo en informática).

    let colores = ["rojo", "verde", "azul"];
    
    alert(colores[0]); // rojo
    alert(colores[2]); // azul

    Propiedad fundamental

    let numeros = [1,2,3,4];
    alert(numeros.length);

    length → número de elementos


    Métodos principales de Array

    Añadir y eliminar

    let lista = [10,20];
    
    lista.push(30);   // añade al final
    lista.pop();      // elimina último
    lista.unshift(5); // añade al inicio
    lista.shift();    // elimina primero

    Buscar elementos

    let numeros = [10,20,30];
    
    numeros.includes(20); // true
    numeros.indexOf(30);  // 2

    Convertir a texto

    let colores = ["rojo","verde","azul"];
    alert(colores.join(" - "));

    Resultado:

    rojo - verde - azul

    Extraer partes

    let datos = [1,2,3,4,5];
    
    let sub = datos.slice(1,4); // [2,3,4]

    slice no modifica el original.


    Modificar el array

    let datos = [1,2,3,4];
    
    datos.splice(1,2); // elimina desde posición 1 → elimina 2 elementos

    Resultado:

    [1,4]

    Video Arrays

    Tabla completa de métodos importantes

    MétodoQué haceEjemplo
    lengthTamaño del array[1,2].length
    push(x)Añade al finalarr.push(5)
    pop()Elimina últimoarr.pop()
    shift()Elimina primeroarr.shift()
    unshift(x)Añade al inicioarr.unshift(0)
    includes(x)Comprueba existenciaarr.includes(10)
    indexOf(x)Posición del valorarr.indexOf(20)
    join(sep)Convierte a textoarr.join(",")
    slice(a,b)Subarray sin modificararr.slice(1,3)
    splice(a,b)Modifica/elimina/añadearr.splice(1,1)
    reverse()Invierte arrayarr.reverse()
    sort()Ordenaarr.sort()
    concat()Une arraysa.concat(b)
    forEach()Recorre arrayarr.forEach(f)
    map()Transforma elementosarr.map(f)
    filter()Filtra elementosarr.filter(f)
    find()Encuentra elementoarr.find(f)

    Recorrer arrays

    Método clásico

    let numeros = [10,20,30];
    
    for (let i = 0; i < numeros.length; i++) {
      console.log(numeros[i]);
    }

    forEach

    numeros.forEach(function(n) {
      console.log(n);
    });

    map (transformar)

    let dobles = numeros.map(n => n * 2);

    filter (filtrar)

    let mayores = numeros.filter(n => n > 15);

    Arrays con objetos

    let alumnos = [
      {nombre:"Ana", nota:8},
      {nombre:"Luis", nota:5}
    ];
    
    console.log(alumnos[0].nombre);

    Esto es el paso previo al JSON del backend → DOM


    Conceptos clave

    • Los arrays pueden crecer dinámicamente
    • Pueden contener cualquier tipo
    • Son objetos (typeof array → "object")
    • map/filter/find son la base del JavaScript moderno

    Ejercicios para alumnos

    Ejercicio 1

    Crear un array con 5 números y mostrar el tamaño.

    Ejercicio 2

    Añadir un número y eliminar el primero.

    Ejercicio 3

    Mostrar solo los números mayores que 10.

    Ejercicio 4

    Crear un array de nombres y convertirlos a mayúsculas con map.

    Ejercicio 5

    Crear un array de objetos alumno {nombre, nota} y mostrar solo los aprobados.

  • 1.7 – Funciones con JS

    1.7 – Funciones con JS

    Una función es un bloque de código reutilizable que ejecuta una tarea concreta.
    Sirve para organizar, reutilizar y abstraer lógica.

    Idea clave: no repitas código, encapsúlalo.

    Ejemplo mental: una cafetera.
    Entras agua + café → proceso → sale café.
    La función hace exactamente eso, pero con datos.


    2. Sintaxis básica

    function nombreFuncion() {
        // código a ejecutar
    }

    Ejemplo:

    function saludar() {
        console.log("Hola mundo");
    }

    La función no se ejecuta sola. Hay que llamarla:

    saludar();

    3. Funciones con parámetros

    Los parámetros son datos que recibe la función.

    function saludar(nombre) {
        console.log("Hola " + nombre);
    }
    
    saludar("Antonio");
    saludar("Bea");

    La función se adapta al dato. Eso es abstracción.


    4. Retorno de valores (return)

    Una función puede devolver un resultado.

    function saludar(nombre) {
        console.log("Hola " + nombre);
    }
    
    saludar("Antonio");
    saludar("Bea");

    Sin return, la función devuelve undefined.

    Concepto importante:
    return termina la ejecución de la función.


    5. Parámetros vs argumentos

    • Parámetros → variables definidas en la función
    • Argumentos → valores reales que se pasan al llamar
    functionmultiplicar(x, y) {   // parámetrosreturnx*y;}multiplicar(4, 2);             // argumentos

    6. Funciones que llaman a otras funciones

    Las funciones pueden componer comportamiento.

    function cuadrado(n) {
        return n * n;
    }
    
    function mostrarCuadrado(num) {
        let resultado = cuadrado(num);
        console.log(resultado);
    }
    
    mostrarCuadrado(5);

    Esto es modularidad. Construir piezas pequeñas y combinarlas.


    7. Funciones anónimas

    Funciones sin nombre, normalmente guardadas en variables.

    let saludar = function(nombre) {
        return "Hola " + nombre;
    };
    
    console.log(saludar("Carlos"));

    8. Arrow functions (funciones flecha)

    Forma moderna y compacta.

    let sumar = (a, b) => {
        return a + b;
    };

    Si solo hay una línea:

    let sumar = (a, b) => {
        return a + b;
    };

    Si solo hay un parámetro:

    let cuadrado = n => n * n;

    9. Ámbito (scope)

    El scope define dónde existe una variable.

    function prueba() {
        let x = 10;
        console.log(x);
    }
    
    prueba();
    console.log(x); // ERROR

    x solo vive dentro de la función.
    Esto protege el programa de efectos colaterales.


    10. Buenas prácticas

    Nombres claros

    Mal:

    functionf(x) {}

    Bien:

    function calcularTotal(precio, iva) {}

    Funciones pequeñas

    Una función debe hacer una sola cosa.


    Evitar repetir código

    Si copias y pegas → necesitas una función.


    11. Ejemplos prácticos

    Calcular precio con IVA

    function calcularPrecioConIVA(precio) {
        return precio * 1.21;
    }
    
    console.log(calcularPrecioConIVA(100));

    Verificar si un número es par

    function esPar(numero) {
        return numero % 2 === 0;
    }
    
    console.log(esPar(4));
    console.log(esPar(7));

    Convertir JSON recibido del backend (pensando en JEE)

    function pintarUsuario(usuario) {
        console.log("Nombre: " + usuario.nombre);
        console.log("Email: " + usuario.email);
    }
    
    let json = {
        nombre: "Antonio",
        email: "antonio@email.com"
    };
    
    pintarUsuario(json);

    Aquí empieza el puente hacia DOM + datos del servidor.


    12. Ejercicios

    1. Crear función que calcule el área de un rectángulo
    2. Crear función que convierta grados Celsius a Fahrenheit
    3. Crear función que reciba un nombre y devuelva: "Bienvenido <nombre>"
    4. Crear función que reciba un array y devuelva la suma total
  • 2.1 – JSON: Formato de intercambio y almacenamiento de datos

    2.1 – JSON: Formato de intercambio y almacenamiento de datos

    JSON (JavaScript Object Notation) es un formato de texto basado en la estructura de objetos de JavaScript. Más moderno y ligero que XML, se utiliza ampliamente tanto para la transmisión de datos entre aplicaciones como para su almacenamiento.

    Este modelo de datos se ha popularizado porque prioriza el contenido sobre la estructura, reduciendo la cantidad de sintaxis necesaria y facilitando la lectura por parte de humanos y máquinas.


    Estructura básica

    Un archivo JSON es una cadena de texto que debe parsearse (analizarse sintácticamente) para acceder a sus datos. Este proceso se denomina parsing cuando convertimos una cadena en un objeto, y stringification cuando hacemos la operación inversa (objeto → texto).

    Los principales elementos de la estructura JSON son:

    • {}: delimitan un objeto.
    • []: delimitan un array (lista ordenada de elementos).
    • :: separa las claves de sus valores.
    • ,: separa los diferentes pares de clave-valor o elementos.
    • "": delimitan los valores de texto (string).

    Ejemplo de objeto JSON

    {
      "squadName": "Super hero squad",
      "homeTown": "Metro City",
      "formed": 2016,
      "secretBase": "Super tower",
      "active": true,
      "members": [
        {
          "name": "Molecule Man",
          "age": 29,
          "secretIdentity": "Dan Jukes",
          "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
        },
        {
          "name": "Madame Uppercut",
          "age": 39,
          "secretIdentity": "Jane Wilson",
          "powers": ["Million tonne punch", "Damage resistance", "Superhuman reflexes"]
        },
        {
          "name": "Eternal Flame",
          "age": 1000000,
          "secretIdentity": "Unknown",
          "powers": ["Immortality", "Heat Immunity", "Inferno", "Teleportation", "Interdimensional travel"]
        }
      ]
    }
    

    Reglas básicas para crear archivos JSON

    1. Los datos se organizan en pares clave:valor, separados por comas.
    2. Tanto claves como valores de texto van entre comillas dobles («»).
    3. Los objetos se delimitan con llaves {}.
    4. Los arrays se delimitan con corchetes [].
    5. Los objetos y arrays pueden anidarse entre sí.

    Ejemplo:

    {
      "nombre": "Jose",
      "edad": 20,
      "genero": "masculino",
      "email": "correodejose@dominio.com",
      "localidad": "Madrid",
      "telefono": "91000000",
      "mando": true,
      "marco": "<iframe width=560 height=315 src='https://www.youtube.com/embed/QCEU-vba8mw' frameborder=0 allowfullscreen></iframe>"
    }
    

    Objetos y arrays en JSON

    • Objeto: se define con llaves {} y contiene uno o varios pares clave-valor.json{ "nombre": "Juan", "apellidos": "Gonzalez" }
    • Array: se define con corchetes [] y puede incluir valores o incluso objetos.json{ "nombre": "Juan", "edad": 30, "coches": ["Ford", "BMW", "Fiat"] }
    • Array de objetos:json{ "nombre": "Juan", "edad": 30, "coches": [ { "nombre": "Ford", "modelos": ["Fiesta", "Focus", "Mustang"] }, { "nombre": "BMW", "modelos": ["320", "X3", "X5"] }, { "nombre": "Fiat", "modelos": ["500", "Panda"] } ] }

    Tipos de datos permitidos

    • string: cadenas de texto.
    • number: números enteros o decimales.
    • object: objetos JSON.
    • array: listas ordenadas.
    • boolean: valores true o false.
    • null: valor nulo o sin asignar.

    JSON vs XML

    JSON y XML son formatos ampliamente usados para intercambiar información entre sistemas y almacenar configuraciones o datos estructurados.

    Aunque JSON suele ser más ligero y fácil de leer, XML ofrece ventajas en casos donde se necesita validación mediante esquemas (XSD) o metadatos complejos.

    Ejemplo JSON:

    {
      "employees": [
        { "firstName": "John", "lastName": "Doe" },
        { "firstName": "Anna", "lastName": "Smith" },
        { "firstName": "Peter", "lastName": "Jones" }
      ]
    }
    

    Ejemplo XML:

    <employees>
      <employee>
        <firstName>John</firstName>
        <lastName>Doe</lastName>
      </employee>
      <employee>
        <firstName>Anna</firstName>
        <lastName>Smith</lastName>
      </employee>
      <employee>
        <firstName>Peter</firstName>
        <lastName>Jones</lastName>
      </employee>
    </employees>
    

    En general, JSON es ideal para intercambio rápido de datos entre aplicaciones web, mientras que XML puede ser preferible en entornos donde se requiera validación formal o compatibilidad con sistemas antiguos.

  • Actividad – Json Invertido

    Actividad – Json Invertido

    Tu tarea es analizar el siguiente código JavaScript y crear el archivo JSON correcto para que funcione sin errores.

    EJERCICIO 1

     Código JavaScript (index.html + script.js)

    Guarda estos archivos y ábrelos en un navegador. (Es  necesario que utilices modo servidor como un contendor o XAMPP)

    https://drive.google.com/file/d/1tK86N1EGZlHKjtxSIHV0TJZ4IcgYyWMd/view?usp=sharingLinks to an external site.

    Si el JSON está bien hecho, al abrir index.html en el navegador debería mostrarse:

    Lista de Productos

    • Tarta de Chocolate – $15 – Stock: 10
    • Cheesecake – $12 – Stock: 5
    • Brownie – $8 – Stock: 7

    EJERCICIO 2

    Tu tarea es analizar el código JavaScript y crear el archivo JSON correcto para que funcione sin errores.

    Código JavaScript (index.html + script.js)

    Guarda estos archivos y ábrelos en un navegador.

    https://drive.google.com/file/d/1spFSlotW1K6Cnm-N5LejSGQhn79phaAd/view?usp=sharingLinks to an external site.

  • Reto –  “Madrid bajo cero”

    Reto – “Madrid bajo cero”

    Hoy ha nevado en Madrid. El ayuntamiento quiere un pequeño programa en Java para registrar datos comunes de la nevada, sin entrar en sistemas complejos ni lógica avanzada.

    Repasa la documentación sobre este reto: https://laaventuradeaprender.com/una-clase-una-instancia-y-cero-excusas-static-y-singleton-en-java/

    EstacionMeteorologica

    Crear una clase llamada EstacionMeteorologica.
    Cada estación representa un punto de medición distinto de Madrid (Sol, Chamartín, Moncloa…), pero hay datos que son globales, iguales para todas las estaciones.

    1. Crea una clase llamada EstacionMeteorologica.
    2. Cada estación tendrá un atributo que es su numero de serie
    3. Añade los siguientes atributos estáticos:
      • ciudad (String) → siempre será "Madrid"
      • temperaturaMedia (double)
      • nevadaActiva (boolean)
    4. Crea solo un constructor vacío (no parámetros).
    5. En el main:
      • Asigna valores a los atributos estáticos usando el nombre de la clase.
      • Crea al menos tres objetos de EstacionMeteorologica.
      • Muestra por pantalla los valores de los atributos estáticos desde cada objeto.

    Reglas del reto

    • No usar métodos static (solo atributos).
    • No usar herencia.
    • No usar arrays, listas ni entrada por teclado.
    • No usar getters ni setters.
    • No usar lógica condicional (if, switch, etc.)

    Ejemplo de salida esperada (orientativa)

    Ciudad: Madrid
    Numero de serie: 124
    Temperatura media: -1.5
    Nevada activa: true
    
    Ciudad: Madrid
    Numero de serie: 234
    Temperatura media: -1.5
    Nevada activa: true

    Emergencia por nieve en Madrid

    Tras la nevada, el ayuntamiento necesita acciones rápidas y globales: avisar a la población, consultar el estado general y actualizar la temperatura.
    Estas acciones no dependen de una estación concreta, sino del estado general de la ciudad.

    Se trabaja con una clase llamada CentroEmergencias.
    No representa un objeto físico concreto, sino un servicio global que actúa sobre el estado general de Madrid.

    1. Crea una clase llamada CentroEmergencias.
    2. Añade los siguientes atributos estáticos:
      • ciudad (String) → "Madrid"
      • temperatura (double)
      • nevadaActiva (boolean)
    3. Añade los siguientes métodos estáticos:
      • activarNevada() → pone nevadaActiva a true
      • desactivarNevada() → pone nevadaActiva a false
      • actualizarTemperatura(double t)
      • mostrarEstado() → muestra todos los datos por pantalla
    4. En el main:
      • Llama a los métodos sin crear ningún objeto.
      • Cambia el estado varias veces.
      • Muestra el estado tras cada cambio.

    Ejemplo de ejecución esperada (orientativa)

    Ciudad: Madrid
    Temperatura: -2.0
    Nevada activa: false
    
    Activando nevada...
    
    Ciudad: Madrid
    Temperatura: -2.0
    Nevada activa: true
    
    Actualizando temperatura...
    
    Ciudad: Madrid
    Temperatura: -4.5
    Nevada activa: true

    Parte meteorológico express

    Tras la nevada, los medios necesitan cálculos rápidos y mensajes claros, pero no quieren guardar estado.
    Solo quieren usar herramientas que reciban datos, hagan su trabajo y desaparezcan sin dejar rastro.

    El objetivo del reto es entender que un método static puede existir sin ningún atributo static.

    Se crea una clase llamada InformeNieve.
    Esta clase no guarda información, solo procesa datos que se le pasan y devuelve resultados o muestra mensajes.

    Es una clase “herramienta”, no un objeto con estado.

    Crea una clase llamada InformeNieve.

    No declares ningún atributo (ni estático ni no estático).

    Crea los siguientes métodos estáticos:

    • mostrarAviso(double temperatura)
    • convertirCelsiusAFahrenheit(double celsius)
    • calcularSensacionTermica(double temperatura, double viento)

    En el main:

    • Llama a los métodos directamente usando el nombre de la clase.
    • Usa valores literales (números escritos directamente).
    • Muestra los resultados por pantalla.

    Reglas del reto

    • Prohibido declarar atributos.
    • Prohibido usar new.
    • Prohibido usar getters, setters o constructores.
    • No usar colecciones ni entrada por teclado.
    • Mantener los métodos simples y claros.

    Ejemplo de uso

    Aviso: Temperatura crítica (-3.0 ºC)
    
    Temperatura en Fahrenheit: 26.6
    
    Sensación térmica estimada: -8.5 ºC

    Este reto es deliberadamente austero.

    “El Quitanieves Supremo de Madrid” (Singleton)

    Madrid ha amanecido blanco. El ayuntamiento activa un único centro de control que coordina todo: no puede haber dos, porque acabarían pisándose órdenes (y eso en una nevada acaba en caos).

    Vas a crear la clase CentroQuitanieves que gestiona el estado de la emergencia:

    • cuántas máquinas quitanieves están activas
    • cuántas toneladas de sal se han repartido
    • cuántos avisos se han emitido

    Todo eso debe estar dentro de una única instancia.

    1. Crea la clase CentroQuitanieves.
    2. Implementa Singleton clásico:
      • atributo privado static para la instancia
      • constructor private
      • método public static CentroQuitanieves getInstancia()
    3. Añade atributos NO estáticos (del objeto único):
      • int maquinasActivas
      • int toneladasSal
      • int avisosEmitidos
    4. Añade métodos (no estáticos) simples:
      • activarMaquina() → suma 1 a maquinasActivas
      • repartirSal(int toneladas) → suma a toneladasSal
      • emitirAviso() → suma 1 a avisosEmitidos
      • mostrarResumen() → imprime todos los datos
    • No puedes hacer new CentroQuitanieves()
    • Debes pedir el centro así:
    CentroQuitanieves c1 = CentroQuitanieves.getInstancia();
    CentroQuitanieves c2 = CentroQuitanieves.getInstancia();
    
    • Comprueba que es el mismo objeto:
    System.out.println(c1 == c2); // debería ser true
    
    • Realiza acciones con c1 y muestra el resumen con c2 (o al revés) para comprobar que comparten estado.

    Ejemplo de salida:

    ¿Mismo centro? true
    
    Resumen Madrid:
    Máquinas activas: 2
    Toneladas de sal: 15
    Avisos emitidos: 3
  • Una clase, una instancia y cero excusas: static y Singleton en Java

    Una clase, una instancia y cero excusas: static y Singleton en Java

    Un atributo static es un atributo que pertenece a la clase, no a los objetos creados a partir de ella.
    Existe una única copia del atributo para toda la aplicación, compartida por todas las instancias.

    • Se declara con la palabra clave static.
    • Se accede usando el nombre de la clase.
    • No depende de ningún objeto concreto.
    • Se inicializa cuando la clase se carga en memoria.

    Cuándo usarlo

    • Contadores globales.
    • Constantes compartidas.
    • Información común a todos los objetos.
    • Estados que deben ser únicos para la clase.

    public class Jugador {
        private String nombre;
        private int puntos;
        public static int totalJugadores = 0;
    
        public Jugador(String nombre) {
            this.nombre = nombre;
            this.puntos = 0;
            totalJugadores++; // actualiza el contador compartido
        }
    }
    


    Si luego haces:

    new Jugador("Ana");
    new Jugador("Luis");
    System.out.println(Jugador.totalJugadores); // Imprime 2
    Da igual cuántos jugadores existan o cuáles sean sus puntos: el contador es único.

    Este tipo de atributo se usa cuando algo pertenece conceptualmente a la clase como conjunto, no a cada objeto. Desde aquí la curiosidad lleva a campos como los métodos static, inicialización estática y patrones de diseño como Singleton o constantes globales, donde las clases juegan a ser “contenedores” más que “plantillas de objetos”.

    Errores comunes

    • Usar static para guardar datos que deberían ser propios de cada objeto.
    • Acceder a un atributo estático usando una instancia en vez de la clase.

    Métodos static en Java

    Un método static es un método que pertenece a la clase, no a los objetos.
    Se puede ejecutar sin crear ninguna instancia.

    • Se llama directamente desde la clase.
    • No puede usar this.
    • No puede acceder directamente a atributos no estáticos.
    • Ideal para lógica independiente del estado de los objetos.

    Cuándo usarlo

    • Métodos de utilidad (cálculos, conversiones, validaciones).
    • Operaciones matemáticas o lógicas puras.
    • Métodos fábrica.
    • Acceso a recursos compartidos.
    public class Conversor {
        public static double celsiusAFahrenheit(double c) {
            return (c * 9/5) + 32;
        }
    }
    double f = Conversor.celsiusAFahrenheit(20);
    System.out.println(f); // 68.0

    Diferencia clave frente a métodos normales

    Un método normal trabaja sobre un objeto concreto.
    Un método static trabaja sobre la idea de la clase.


    Relación entre static y diseño de clases

    • Lo no estático representa el estado individual de cada objeto.
    • Lo estático representa el estado o comportamiento global de la clase.

    Entender esta diferencia es fundamental para:

    • Diseñar bien las clases.
    • Evitar estado global innecesario.
    • Preparar el terreno para patrones de diseño.

    Introducción al patrón Singleton

    El patrón Singleton garantiza que una clase tenga una única instancia y proporciona un punto de acceso global a ella.

    Por qué existe

    Hay elementos del sistema que deben existir una sola vez:

    • Configuración global.
    • Logger.
    • Gestor de recursos.
    • Controladores centrales.

    Características básicas

    • Constructor privado (no se puede usar new desde fuera).
    • Atributo static que guarda la única instancia.
    • Método static que devuelve esa instancia.

    Qué problema resuelve

    • Crear múltiples objetos que representan lo mismo.
    • Inconsistencias de estado.
    • Accesos descontrolados a recursos compartidos.

    Singleton y static: conexión directa

    El patrón Singleton combina directamente los conceptos vistos:

    • Usa un atributo static para almacenar la instancia única.
    • Usa un método static como punto de acceso.
    • Refuerza la idea de que algunas cosas pertenecen a la clase, no a los objetos.

    Ejemplo

    1. La clase se crea a sí misma por dentro.
    2. La clase guarda la única instancia en un atributo estático.
    3. Como el constructor es privado, nadie puede hacer new.
    4. Se accede usando un método static que devuelve siempre la misma instancia.

    Un ejemplo:

    public class Configuracion {
    
        // La única instancia
        private static Configuracion instancia;
    
        // Constructor privado para evitar new desde fuera
        private Configuracion() {
            // Inicialización
        }
    
        // Punto de acceso controlado
        public static Configuracion getInstancia() {
            if (instancia == null) {
                instancia = new Configuracion();
            }
            return instancia;
        }
    
        public void saludar() {
            System.out.println("Soy la única configuración.");
        }
    }
    

    Uso:

    Configuracion c1 = Configuracion.getInstancia();
    Configuracion c2 = Configuracion.getInstancia();
    
    System.out.println(c1 == c2); // true
    

    Ese true es la prueba de que ambos apuntan al mismo objeto.

    Singleton = una clase que garantiza una sola instancia y un único punto de acceso.


  • R7 [Solución] – Guerra por el Sistema Solar: Tierra, Marte y el Cinturón” (The Expanse)

    1) Nave.java

    
    
    
    
    
    /**
     * Representa una nave espacial dentro del universo de The Expanse.
     * 
     * Conceptos que se trabajan aquí:
     * - Encapsulamiento (atributos privados + getters/setters)
     * - Constructor completo
     * - toString() para imprimir bonito el objeto
     */
    public class Nave {
    
        // -------------------------
        // Atributos (siempre privados)
        // -------------------------
        private String nombre;
        private String faccion; // "Tierra", "Marte", "Cinturon"
        private int misiles;
        private boolean necesitaReparacion;
    
        // -------------------------
        // Constructores
        // -------------------------
    
        /**
         * Constructor completo exigido por el enunciado.
         */
        public Nave(String nombre, String faccion, int misiles, boolean necesitaReparacion) {
            this.nombre = nombre;
            this.faccion = faccion;
            this.misiles = misiles;
            this.necesitaReparacion = necesitaReparacion;
        }
    
        /**
         * Constructor opcional de conveniencia (por si quieres crear naves “rápido”).
         * Por ejemplo: misiles = 0 y no necesita reparación por defecto.
         */
        public Nave(String nombre, String faccion) {
            this(nombre, faccion, 0, false);
        }
    
        // -------------------------
        // Getters y Setters
        // -------------------------
        public String getNombre() {
            return nombre;
        }
    
        public void setNombre(String nombre) {
            this.nombre = nombre;
        }
    
        public String getFaccion() {
            return faccion;
        }
    
        public void setFaccion(String faccion) {
            this.faccion = faccion;
        }
    
        public int getMisiles() {
            return misiles;
        }
    
        public void setMisiles(int misiles) {
            this.misiles = misiles;
        }
    
        public boolean isNecesitaReparacion() {
            return necesitaReparacion;
        }
    
        public void setNecesitaReparacion(boolean necesitaReparacion) {
            this.necesitaReparacion = necesitaReparacion;
        }
    
        // -------------------------
        // toString() (muy útil para listar)
        // -------------------------
        @Override
        public String toString() {
            return "Nave{" +
                    "nombre='" + nombre + '\'' +
                    ", faccion='" + faccion + '\'' +
                    ", misiles=" + misiles +
                    ", necesitaReparacion=" + (necesitaReparacion ? "Sí" : "No") +
                    '}';
        }
    }
    

    2) Flota.java

    
    
    
    
    
    import java.util.ArrayList;
    import java.util.Scanner;
    
    /**
     * Flota = conjunto de naves.
     *
     * Conceptos que se trabajan:
     * - ArrayList con objetos (ArrayList<Nave>)
     * - Métodos para recorrer y filtrar
     * - Métodos con Scanner para crear objetos desde teclado
     */
    public class Flota {
    
        // Lista privada: nadie fuera debería tocarla directamente
        private ArrayList<Nave> naves;
    
        // -------------------------
        // Constructor
        // -------------------------
        public Flota() {
            // Inicializamos la lista vacía (obligatorio)
            this.naves = new ArrayList<>();
        }
    
        // -------------------------
        // Agregar nave ya creada
        // -------------------------
        public void agregarNave(Nave nave) {
            if (nave == null) {
                System.out.println("No se puede añadir una nave nula.");
                return;
            }
            naves.add(nave);
            System.out.println("✅ Nave añadida a la flota: " + nave.getNombre());
        }
    
        // -------------------------
        // Crear nave por teclado
        // -------------------------
        public void crearNaveDesdeTeclado(Scanner sc) {
            System.out.println("=== CREAR NUEVA NAVE ===");
    
            // Nombre
            System.out.print("Nombre de la nave: ");
            String nombre = sc.nextLine().trim();
    
            // Facción (validada)
            String faccion = leerFaccionValida(sc);
    
            // Misiles (entero >= 0)
            int misiles = leerEnteroNoNegativo(sc, "Número de misiles: ");
    
            // Reparación (S/N)
            boolean necesitaReparacion = leerBooleanSN(sc, "¿Necesita reparación? (S/N): ");
    
            // Creamos el objeto Nave con los datos recogidos
            Nave nave = new Nave(nombre, faccion, misiles, necesitaReparacion);
    
            // La añadimos al ArrayList
            naves.add(nave);
    
            System.out.println("✅ Nave creada y añadida: " + nave);
        }
    
        // -------------------------
        // Número de naves
        // -------------------------
        public int getNumeroNaves() {
            return naves.size();
        }
    
        // -------------------------
        // Listar todas
        // -------------------------
        public void listarTodas() {
            System.out.println("=== LISTADO COMPLETO DE NAVES ===");
    
            if (naves.isEmpty()) {
                System.out.println("La flota está vacía.");
                return;
            }
    
            for (Nave n : naves) {
                System.out.println(n); // llama a toString()
            }
        }
    
        // -------------------------
        // Listar naves para reparar
        // -------------------------
        public void listarNavesParaReparar() {
            System.out.println("=== NAVES QUE NECESITAN REPARACIÓN ===");
    
            boolean hay = false;
    
            for (Nave n : naves) {
                if (n.isNecesitaReparacion()) {
                    System.out.println(n);
                    hay = true;
                }
            }
    
            if (!hay) {
                System.out.println("No hay naves marcadas para reparación.");
            }
        }
    
        // -------------------------
        // Total de misiles
        // -------------------------
        public int getTotalMisiles() {
            int total = 0;
            for (Nave n : naves) {
                total += n.getMisiles();
            }
            return total;
        }
    
        // -------------------------
        // Listar por facción
        // -------------------------
        public void listarPorFaccion(String faccion) {
            System.out.println("=== NAVES DE LA FACCIÓN: " + faccion + " ===");
    
            boolean hay = false;
    
            for (Nave n : naves) {
                // equalsIgnoreCase = compara ignorando mayúsculas/minúsculas
                if (n.getFaccion().equalsIgnoreCase(faccion)) {
                    System.out.println(n);
                    hay = true;
                }
            }
    
            if (!hay) {
                System.out.println("No se encontraron naves de esa facción.");
            }
        }
    
        // -------------------------
        // Contar por facción
        // -------------------------
        public int contarPorFaccion(String faccion) {
            int contador = 0;
    
            for (Nave n : naves) {
                if (n.getFaccion().equalsIgnoreCase(faccion)) {
                    contador++;
                }
            }
    
            return contador;
        }
    
        // ==========================================================
        // Métodos auxiliares (para que el código quede limpio y robusto)
        // ==========================================================
    
        /**
         * Pide al usuario una facción válida: Tierra / Marte / Cinturon.
         */
        private String leerFaccionValida(Scanner sc) {
            while (true) {
                System.out.print("Facción (Tierra/Marte/Cinturon): ");
                String faccion = sc.nextLine().trim();
    
                if (esFaccionValida(faccion)) {
                    // Normalizamos para que quede bonito y consistente
                    return normalizarFaccion(faccion);
                }
    
                System.out.println("❌ Facción no válida. Usa: Tierra, Marte o Cinturon.");
            }
        }
    
        private boolean esFaccionValida(String faccion) {
            String f = faccion.trim().toLowerCase();
            return f.equals("tierra") || f.equals("marte") || f.equals("cinturon");
        }
    
        private String normalizarFaccion(String faccion) {
            String f = faccion.trim().toLowerCase();
            if (f.equals("tierra")) return "Tierra";
            if (f.equals("marte")) return "Marte";
            return "Cinturon";
        }
    
        /**
         * Lee un entero >= 0, repitiendo hasta que sea válido.
         */
        private int leerEnteroNoNegativo(Scanner sc, String mensaje) {
            while (true) {
                System.out.print(mensaje);
                String linea = sc.nextLine().trim();
    
                try {
                    int valor = Integer.parseInt(linea);
                    if (valor < 0) {
                        System.out.println("❌ No puede ser negativo.");
                    } else {
                        return valor;
                    }
                } catch (NumberFormatException e) {
                    System.out.println("❌ Introduce un número entero válido.");
                }
            }
        }
    
        /**
         * Lee una respuesta tipo S/N y la convierte a boolean.
         * S -> true, N -> false
         */
        private boolean leerBooleanSN(Scanner sc, String mensaje) {
            while (true) {
                System.out.print(mensaje);
                String resp = sc.nextLine().trim().toLowerCase();
    
                if (resp.equals("s") || resp.equals("si") || resp.equals("sí")) return true;
                if (resp.equals("n") || resp.equals("no")) return false;
    
                System.out.println("❌ Respuesta no válida. Escribe S o N.");
            }
        }
    }
    

    3) ControlFlota.java (main + menú)

    
    
    
    
    
    import java.util.Scanner;
    
    /**
     * Controlador principal.
     *
     * Conceptos que se trabajan:
     * - Menú por consola repetitivo (do-while)
     * - switch para ejecutar opciones
     * - Reutilización de Scanner y validación de enteros
     */
    public class ControlFlota {
    
        public static void main(String[] args) {
    
            Scanner sc = new Scanner(System.in);
            Flota flota = new Flota();
    
            // Naves iniciales opcionales (ambiente The Expanse)
            flota.agregarNave(new Nave("Rocinante", "Cinturon", 24, false));
            flota.agregarNave(new Nave("Donnager", "Marte", 60, true));
            flota.agregarNave(new Nave("Agatha King", "Tierra", 40, true));
    
            int opcion;
    
            do {
                mostrarMenu();
                opcion = leerEnteroSeguro(sc);
    
                switch (opcion) {
    
                    case 1:
                        // Crear nave desde teclado
                        flota.crearNaveDesdeTeclado(sc);
                        break;
    
                    case 2:
                        // Crear nave desde el main (valores fijos)
                        Nave n = new Nave("Nauvoo", "Cinturon", 80, false);
                        flota.agregarNave(n);
                        System.out.println("Nave creada desde main y añadida: " + n);
                        break;
    
                    case 3:
                        flota.listarTodas();
                        break;
    
                    case 4:
                        flota.listarNavesParaReparar();
                        break;
    
                    case 5:
                        System.out.println("Número total de naves: " + flota.getNumeroNaves());
                        break;
    
                    case 6:
                        System.out.println("Misiles totales en la flota: " + flota.getTotalMisiles());
                        break;
    
                    case 7:
                        System.out.print("Introduce la facción (Tierra/Marte/Cinturon): ");
                        String fac = sc.nextLine().trim();
                        flota.listarPorFaccion(fac);
                        break;
    
                    case 8:
                        System.out.print("Introduce la facción (Tierra/Marte/Cinturon): ");
                        String fac2 = sc.nextLine().trim();
                        int num = flota.contarPorFaccion(fac2);
                        System.out.println("Número de naves de la facción " + fac2 + ": " + num);
                        break;
    
                    case 0:
                        System.out.println("Saliendo del sistema de control de flota...");
                        break;
    
                    default:
                        System.out.println("Opción no válida.");
                }
    
                System.out.println(); // línea en blanco para separar “vueltas” del menú
    
            } while (opcion != 0);
    
            sc.close();
        }
    
        // -------------------------
        // Menú
        // -------------------------
        private static void mostrarMenu() {
            System.out.println("=== CONTROL DE FLOTA - GUERRA DEL SISTEMA SOLAR ===");
            System.out.println("1. Añadir nave (datos por teclado)");
            System.out.println("2. Añadir nave (creada en el main)");
            System.out.println("3. Listar todas las naves");
            System.out.println("4. Listar naves que necesitan reparación");
            System.out.println("5. Mostrar número total de naves");
            System.out.println("6. Mostrar número total de misiles");
            System.out.println("7. Listar naves por facción");
            System.out.println("8. Contar naves por facción");
            System.out.println("0. Salir");
            System.out.print("Elige una opción: ");
        }
    
        /**
         * Lee un entero de forma segura usando nextLine() + parseInt.
         * Ventaja: evita problemas típicos de Scanner con nextInt() y saltos de línea.
         */
        private static int leerEnteroSeguro(Scanner sc) {
            while (true) {
                try {
                    return Integer.parseInt(sc.nextLine().trim());
                } catch (NumberFormatException e) {
                    System.out.print("Introduce un número válido: ");
                }
            }
        }
    }
    

  • 2.1 – Librerías en Python

    2.1 – Librerías en Python

    ¿Qué es una librería en Python?

    Una librería (o “biblioteca”) es código que alguien ya escribió para resolver un problema común: hacer peticiones web, leer PDFs, crear gráficos, trabajar con fechas, etc.

    • Python estándar: viene “de serie” (por ejemplo json, os, math, datetime).
    • Librerías externas: se instalan aparte (por ejemplo requests, pandas, flask, numpy).

    Regla mental útil:

    • Si lo importas y no lo instalas, normalmente es estándar.
    • Si lo importas y te da error, probablemente hay que instalarlo.

    ¿Qué significa import?

    Cuando haces:

    
    
    
    
    
    import requests
    

    Estás diciendo: “Python, carga ese paquete para que pueda usar sus funciones”.

    También puedes importar cosas concretas:

    
    
    
    
    
    from datetime import datetime
    

    O ponerle un alias (muy común):

    
    
    
    
    
    import pandas as pd
    

    ¿Dónde se instalan librerías?

    En Python, las librerías se instalan en “un Python concreto”, que puede ser:

    1. Python del sistema (global).
    2. Un entorno virtual (recomendado para clase/proyectos).
    3. Una instalación específica (por ejemplo, un Python dentro de VS Code, conda, etc.).

    La confusión típica del alumnado:

    “He instalado requests pero sigue dando error”
    Casi siempre es porque instalaron en un Python y ejecutan con otro.


    Gestores de paquetes: pip

    pip es el instalador estándar de paquetes Python.

    Comandos básicos:

    
    
    
    
    
    pip --version
    python3 -m pip --version
    

    Instalar una librería:

    
    
    
    
    
    python3 -m pip install requests
    

    Actualizar una librería:

    
    
    
    
    
    python3 -m pip install --upgrade requests
    

    Ver lo instalado:

    
    
    
    
    
    python3 -m pip list
    

    Ver información de un paquete:

    
    
    
    
    
    python3 -m pip show requests
    

    La forma “pro” para clase: entornos virtuales (venv)

    Esto evita romper el sistema y hace que cada proyecto tenga sus librerías.

    Crear entorno virtual

    En la carpeta del proyecto:

    
    
    
    
    
    python3 -m venv venv
    

    Activarlo

    Linux/Mac:

    
    
    
    
    
    source venv/bin/activate
    

    Windows (PowerShell):

    
    
    
    
    
    venv\Scripts\Activate.ps1
    

    Windows (CMD):

    
    
    
    
    
    venv\Scripts\activate.bat
    

    Cuando está activo, normalmente verás (venv) al inicio de la terminal.

    Instalar dentro del entorno

    
    
    
    
    
    python -m pip install requests
    

    5.4 Salir del entorno

    
    
    
    
    
    deactivate
    

    Cómo saber si algo es estándar o externo

    Ejemplos:

    • Estándar:
    
    
    
    
    
    import json
    import os
    import datetime
    
    • Externas (requieren pip):
    
    
    
    
    
    import requests
    import pandas
    import flask
    

    Buenas prácticas al trabajar con librerías

    Fijar dependencias: requirements.txt

    Generar lista de dependencias del entorno:

    
    
    
    
    
    python -m pip freeze > requirements.txt
    

    Instalar dependencias en otro equipo:

    
    
    
    
    
    python -m pip install -r requirements.txt
    

    Esto es oro puro para que los proyectos “funcionen igual” en todas las máquinas.

    Leer documentación y “ejemplos mínimos”

    La documentación oficial suele tener:

    • instalación
    • ejemplos pequeños
    • parámetros importantes
    • errores comunes

    Ejemplo completo con requests (GET)

    Vamos a hacer una petición HTTP a una API pública de ejemplo y procesar JSON.

    Instalar requests

    Si estás en venv, mejor.

    
    
    
    
    
    python -m pip install requests
    

    Script: peticion_get.py

    
    
    
    
    
    import requests
    
    URL = "https://httpbin.org/get"
    
    def main():
        try:
            # timeout: evita que el programa se quede colgado si el servidor no responde
            response = requests.get(URL, timeout=10)
    
            # Lanza excepción si el status code es 4xx/5xx
            response.raise_for_status()
    
            # httpbin devuelve JSON
            data = response.json()
    
            print("✅ Status:", response.status_code)
            print("✅ Tu IP según el servidor:", data.get("origin"))
            print("✅ Headers enviados (ejemplo):")
            headers = data.get("headers", {})
            print("   User-Agent:", headers.get("User-Agent"))
    
        except requests.exceptions.Timeout:
            print("⏳ Error: timeout (el servidor tardó demasiado en responder).")
        except requests.exceptions.HTTPError as e:
            print("🚫 Error HTTP:", e)
        except requests.exceptions.RequestException as e:
            # Cubre: problemas de red, DNS, conexión, etc.
            print("🌐 Error de red:", e)
        except ValueError:
            # Si .json() falla porque la respuesta no era JSON
            print("🧩 Error: la respuesta no era JSON válido.")
    
    if __name__ == "__main__":
        main()
    

    Ejecutarlo

    
    
    
    
    
    python peticion_get.py
    

    Qué deberían observar:

    • status_code (200 si ok)
    • origin (la IP “vista” por el servidor)
    • headers (cabeceras HTTP)

    Ejemplo con parámetros (GET con params)

    params construye la query ?clave=valor.

    
    
    
    
    
    import requests
    
    URL = "https://httpbin.org/get"
    
    params = {
        "busqueda": "python",
        "pagina": 1
    }
    
    r = requests.get(URL, params=params, timeout=10)
    print("URL final:", r.url)
    print("Respuesta JSON:", r.json().get("args"))
    

    Ejemplo POST (enviar datos)

    Enviar JSON

    
    
    
    
    
    import requests
    
    URL = "https://httpbin.org/post"
    
    payload = {
        "usuario": "alumno01",
        "rol": "tester"
    }
    
    r = requests.post(URL, json=payload, timeout=10)
    r.raise_for_status()
    
    data = r.json()
    print("Enviado:", data.get("json"))
    

    Enviar formulario (application/x-www-form-urlencoded)

    
    
    
    
    
    import requests
    
    URL = "https://httpbin.org/post"
    
    form_data = {
        "email": "alumno@ejemplo.com",
        "password": "1234"
    }
    
    r = requests.post(URL, data=form_data, timeout=10)
    print(r.json().get("form"))
    

    Cabeceras y autenticación básica

    Cabeceras (headers)

    
    
    
    
    
    import requests
    
    URL = "https://httpbin.org/headers"
    
    headers = {
        "User-Agent": "ClasePython/1.0",
        "X-Profesor": "Antonio"
    }
    
    r = requests.get(URL, headers=headers, timeout=10)
    print(r.json())
    

    Auth básica (solo ejemplo didáctico)

    
    
    
    
    
    import requests
    
    URL = "https://httpbin.org/basic-auth/user/pass"
    r = requests.get(URL, auth=("user", "pass"), timeout=10)
    print(r.status_code, r.json())
    

    Errores típicos (y cómo cazarlos rápido)

    “ModuleNotFoundError: No module named ‘requests’”

    • No está instalado en ese Python.
    • Solución:
    
    
    
    
    
    python -m pip install requests
    python -c "import requests; print(requests.__version__)"
    

    “Funciona en terminal pero no en VS Code”

    • VS Code está usando otro intérprete.
    • Solución: seleccionar el intérprete del venv en VS Code (Python: Select Interpreter).

    “Se queda colgado”

    • Falta timeout.
    • Solución: siempre pon timeout=... en peticiones.

  • 2.2 – ReconLite (mini escáner OSINT HTTP)

    2.2 – ReconLite (mini escáner OSINT HTTP)

    En este caso vamos a construir paso a paso ReconLite, un mini escáner HTTP orientado a reconocimiento OSINT ligero. No es una herramienta para “atacar”, ni pretende competir con scanners profesionales, sino un proyecto didáctico pensado para entender qué información expone un servicio web antes incluso de hablar de vulnerabilidades.

    ReconLite nace con una idea muy clara: en ciberseguridad, observar bien es más importante que correr rápido. Aprender a mirar cabeceras, códigos de estado, tiempos de respuesta, redirecciones o endpoints típicos permite formarse una primera imagen del objetivo sin romper nada, sin hacer ruido innecesario y, sobre todo, dejando evidencias claras y reproducibles.

    A lo largo del proyecto se trabajan conceptos clave que aparecen constantemente en auditorías reales y análisis forense web: normalización de objetivos, fingerprinting pasivo, revisión de hardening HTTP, enumeración controlada de rutas y generación de reportes estructurados. Todo ello usando Python y la librería requests, sin magia negra y con explicaciones claras de cada decisión tomada en el código.

    El objetivo no es solo que el script funcione, sino que entiendas por qué se hace cada cosa, qué información aporta y cuáles son sus límites. ReconLite está diseñado para fomentar una mentalidad ciber realista:
    mirar sin asumir, registrar sin interpretar de más y recordar siempre que un 200 no significa “seguro”, ni un 403 significa “no existe”.

    A partir de aquí encontrarás la estructura completa del proyecto, el código íntegro y una explicación detallada de cada parte, para que puedas usarlo, modificarlo y ampliarlo como base para prácticas, laboratorios o proyectos más avanzados.

    Qué hace:

    • Valida y normaliza una URL objetivo
    • Descubre endpoints típicos (con un wordlist pequeño)
    • Revisa cabeceras de seguridad (CSP, HSTS, etc.)
    • Detecta tecnologías básicas por headers (sin magia negra)
    • Comprueba robots.txt y sitemap.xml
    • Mide tiempos y códigos de estado
    • Saca un reporte en JSON

    Mentalidad ciber: “mirar sin romper”, registrar evidencias, y no asumir que “200 = seguro” ni que “403 = no existe”.


    Estructura

    Crea esta carpeta:

    • reconlite/
      • reconlite.py
      • requirements.txt
      • README.md
      • report.json (se genera)

    requirements.txt

    
    
    
    
    
    requests>=2.31.0
    

    reconlite.py (proyecto completo)

    
    
    
    
    
    #!/usr/bin/env python3
    import argparse
    import json
    import re
    import sys
    import time
    from urllib.parse import urljoin, urlparse
    
    import requests
    
    
    DEFAULT_PATHS = [
        "/", "/robots.txt", "/sitemap.xml",
        "/.git/", "/.env", "/config.php", "/phpinfo.php",
        "/admin", "/admin/", "/login", "/login/",
        "/wp-admin", "/wp-login.php",
        "/api", "/api/", "/swagger", "/swagger/", "/openapi.json",
        "/server-status", "/actuator", "/actuator/health"
    ]
    
    SEC_HEADERS = [
        "Strict-Transport-Security",
        "Content-Security-Policy",
        "X-Content-Type-Options",
        "X-Frame-Options",
        "Referrer-Policy",
        "Permissions-Policy",
        "Cross-Origin-Opener-Policy",
        "Cross-Origin-Resource-Policy",
        "Cross-Origin-Embedder-Policy",
    ]
    
    
    def normalize_url(raw: str) -> str:
        raw = raw.strip()
        if not raw:
            raise ValueError("URL vacía.")
        if not re.match(r"^https?://", raw, re.IGNORECASE):
            raw = "https://" + raw  # por defecto https
        u = urlparse(raw)
        if not u.netloc:
            raise ValueError("URL inválida. Ejemplo: https://example.com")
        # reconstrucción limpia (sin fragmentos)
        clean = f"{u.scheme}://{u.netloc}"
        if u.path and u.path != "/":
            clean += u.path.rstrip("/")
        return clean
    
    
    def safe_request(session: requests.Session, method: str, url: str, **kwargs):
        t0 = time.time()
        try:
            r = session.request(method, url, **kwargs)
            dt = (time.time() - t0) * 1000.0
            return r, dt, None
        except requests.RequestException as e:
            dt = (time.time() - t0) * 1000.0
            return None, dt, str(e)
    
    
    def extract_basic_fingerprint(headers: dict) -> dict:
        # Fingerprinting suave: headers típicos (no infalible)
        server = headers.get("Server")
        powered = headers.get("X-Powered-By")
        via = headers.get("Via")
        return {
            "server": server,
            "x_powered_by": powered,
            "via": via,
        }
    
    
    def analyze_security_headers(headers: dict) -> dict:
        present = {}
        missing = []
        for h in SEC_HEADERS:
            if h in headers:
                present[h] = headers.get(h)
            else:
                missing.append(h)
    
        # Heurísticas sencillas (no dogma)
        notes = []
        if "Strict-Transport-Security" not in headers:
            notes.append("No HSTS: si es un sitio web público, se suele recomendar forzar HTTPS con HSTS.")
        if headers.get("X-Content-Type-Options", "").lower() != "nosniff":
            notes.append("X-Content-Type-Options no es 'nosniff' (o falta).")
        if "Content-Security-Policy" not in headers:
            notes.append("Sin CSP: suele reducir impacto de XSS (no lo elimina).")
    
        return {"present": present, "missing": missing, "notes": notes}
    
    
    def is_interesting_status(code: int) -> bool:
        # 200/204/3xx/401/403 son “interesantes” para enumeración
        return code in (200, 201, 202, 204, 301, 302, 307, 308, 401, 403)
    
    
    def scan_paths(base_url: str, paths: list, timeout: int, verify_tls: bool, user_agent: str, max_paths: int):
        session = requests.Session()
        session.headers.update({"User-Agent": user_agent})
    
        results = []
        for i, p in enumerate(paths[:max_paths], start=1):
            full = urljoin(base_url + "/", p.lstrip("/"))
            r, dt, err = safe_request(
                session,
                "GET",
                full,
                timeout=timeout,
                allow_redirects=False,
                verify=verify_tls,
            )
    
            entry = {
                "path": p,
                "url": full,
                "error": err,
                "ms": round(dt, 2),
            }
    
            if r is not None:
                entry.update({
                    "status": r.status_code,
                    "content_type": r.headers.get("Content-Type"),
                    "content_length": r.headers.get("Content-Length"),
                    "location": r.headers.get("Location"),
                })
    
                # Guardamos solo “señales”, no el contenido entero (más limpio y ético)
                if is_interesting_status(r.status_code):
                    results.append(entry)
            else:
                # errores de red también son evidencia
                results.append(entry)
    
        return results
    
    
    def head_base(base_url: str, timeout: int, verify_tls: bool, user_agent: str):
        session = requests.Session()
        session.headers.update({"User-Agent": user_agent})
    
        r, dt, err = safe_request(
            session,
            "HEAD",
            base_url,
            timeout=timeout,
            allow_redirects=False,
            verify=verify_tls,
        )
    
        if r is None:
            return {"error": err, "ms": round(dt, 2)}
    
        return {
            "status": r.status_code,
            "ms": round(dt, 2),
            "headers": dict(r.headers),
        }
    
    
    def main():
        parser = argparse.ArgumentParser(
            description="ReconLite - Recon HTTP/OSINT ligero con mentalidad ciber (solo objetivos autorizados)."
        )
        parser.add_argument("target", help="URL o dominio (ej: https://example.com o example.com)")
        parser.add_argument("--timeout", type=int, default=8, help="Timeout por request en segundos (default: 8)")
        parser.add_argument("--insecure", action="store_true", help="No verificar TLS (NO recomendado)")
        parser.add_argument("--max-paths", type=int, default=40, help="Máximo de rutas a probar (default: 40)")
        parser.add_argument("--paths-file", help="Archivo de rutas (una por línea) para enumeración")
        parser.add_argument("--out", default="report.json", help="Ruta del reporte JSON (default: report.json)")
        parser.add_argument("--ua", default="ReconLite/1.0 (+educational)", help="User-Agent personalizado")
    
        args = parser.parse_args()
    
        try:
            base_url = normalize_url(args.target)
        except ValueError as e:
            print(f"[!] {e}", file=sys.stderr)
            sys.exit(1)
    
        verify_tls = not args.insecure
    
        paths = DEFAULT_PATHS
        if args.paths_file:
            try:
                with open(args.paths_file, "r", encoding="utf-8") as f:
                    custom = [line.strip() for line in f if line.strip() and not line.strip().startswith("#")]
                # Normaliza para que siempre empiecen por "/"
                paths = [p if p.startswith("/") else "/" + p for p in custom]
            except OSError as e:
                print(f"[!] No se pudo leer paths-file: {e}", file=sys.stderr)
                sys.exit(1)
    
        report = {
            "target": base_url,
            "timestamp_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
            "config": {
                "timeout_s": args.timeout,
                "verify_tls": verify_tls,
                "max_paths": args.max_paths,
                "user_agent": args.ua,
            },
            "base_head": {},
            "fingerprint": {},
            "security_headers": {},
            "findings": [],
        }
    
        # 1) HEAD base
        base_head = head_base(base_url, args.timeout, verify_tls, args.ua)
        report["base_head"] = base_head
    
        if "headers" in base_head:
            headers = base_head["headers"]
            report["fingerprint"] = extract_basic_fingerprint(headers)
            report["security_headers"] = analyze_security_headers(headers)
    
        # 2) Enumeración de rutas
        findings = scan_paths(base_url, paths, args.timeout, verify_tls, args.ua, args.max_paths)
        report["findings"] = findings
    
        # 3) Guardar reporte
        try:
            with open(args.out, "w", encoding="utf-8") as f:
                json.dump(report, f, indent=2, ensure_ascii=False)
        except OSError as e:
            print(f"[!] No se pudo escribir el reporte: {e}", file=sys.stderr)
            sys.exit(1)
    
        # Resumen consola
        interesting = [x for x in findings if x.get("status") is not None]
        print(f"✅ Objetivo: {base_url}")
        if "status" in base_head:
            print(f"✅ HEAD status: {base_head['status']} ({base_head['ms']} ms)")
        print(f"✅ Hallazgos guardados en: {args.out}")
        print(f"✅ Endpoints interesantes (con status): {len(interesting)}")
    
    
    if __name__ == "__main__":
        main()
    

    README.md

    README.md 
    # ReconLite (proyecto resuelto) – Python + requests con mentalidad ciber

    ## Objetivo
    Hacer reconocimiento HTTP/OSINT de forma ligera y responsable:
    - Cabeceras de seguridad (HSTS, CSP, etc.)
    - Fingerprinting básico por headers
    - Enumeración “suave” de rutas típicas
    - Reporte JSON para evidencias

    > Úsalo solo contra objetivos autorizados.

    ## Instalación
    ```bash
    python3 -m venv venv
    source venv/bin/activate
    pip install -r requirements.txt
    ```

    ## Uso básico
    ```bash
    python reconlite.py https://example.com
    ```

    ## Cambiar User-Agent
    ```bash
    python reconlite.py example.com --ua "Mozilla/5.0 (ReconLite class)"
    ```

    ## Añadir tu propio wordlist de rutas
    Archivo `paths.txt` (una ruta por línea):
    ```
    admin
    admin/
    robots.txt
    api/v1
    ```

    Ejecución:
    ```bash
    python reconlite.py https://midominio.com --paths-file paths.txt --max-paths 200
    ```

    ## TLS
    Por defecto verifica TLS.
    Si estás en laboratorio con certificados raros (NO recomendado):
    ```bash
    python reconlite.py https://lab.local --insecure
    ```

    ## Salida
    Genera `report.json` con:
    - Config usada
    - HEAD base con headers
    - Seguridad de headers
    - Listado de rutas con status, tiempos, redirects y content-type
    ```

    ---

    ## Ejemplos de ejecución (para clase)
    ~~~bash
    # 1) Recon rápido
    python reconlite.py https://testphp.vulnweb.com

    # 2) Enumeración más grande con tu lista
    python reconlite.py https://testphp.vulnweb.com --paths-file paths.txt --max-paths 150

    # 3) Cambiar timeout (objetivos lentos)
    python reconlite.py https://testphp.vulnweb.com --timeout 15

    Aqhí tienes todos los archivos del proyecto para que puedas usarlos.

    Métodos comentados

    normalize_url(raw: str) -> str

    Propósito: convertir lo que te pase el usuario (example.com, http://..., https://.../algo) en una URL base “limpia” y usable.

    Qué hace paso a paso:

    • raw.strip() limpia espacios (entrada típica de alumno: " example.com ").
    • Si está vacío → ValueError. Evita que luego requests reviente con errores menos claros.
    • Si no empieza por http:// o https://, le añade https:// por defecto.
      • Mentalidad ciber: preferir TLS. Si el sitio no soporta HTTPS, ya verás error/redirect.
    • urlparse(raw) separa esquema, host, path, etc.
    • Si no hay netloc (host) → URL inválida.
    • Reconstruye una URL “limpia” sin fragmentos y con path normalizado.

    Por qué es importante en ciber:

    • Un recon decente empieza por normalizar objetivos. Evitas escanear accidentalmente https://https://... o rutas raras.

    Limitaciones:

    • No valida DNS ni conectividad; solo sintaxis.
    • No fuerza www ni detecta canonical URL.

    Mejoras típicas:

    • Permitir http por defecto si https falla (con aviso).
    • Bloquear esquemas raros aunque vengan “inyectados”.

    safe_request(session, method, url, **kwargs)

    Propósito: envoltorio robusto alrededor de requests para:

    • medir tiempo
    • capturar errores de red
    • devolver siempre un resultado uniforme

    Qué hace:

    • Arranca cronómetro (t0 = time.time()).
    • Intenta session.request(...) con método, URL y parámetros.
    • Si va bien → devuelve (response, ms, None).
    • Si falla (timeout, DNS, TLS, conexión, etc.) → devuelve (None, ms, "error string").

    Por qué es importante en ciber:

    • En recon real, los fallos son evidencia: “no resuelve”, “TLS handshake falla”, “timeout”… eso te dice cosas del perímetro.

    Limitaciones:

    • Devuelve str(e) sin clasificar error (timeout vs ssl vs conn).

    Mejoras:

    • Clasificar excepciones (Timeout, SSLError, ConnectionError) en un campo error_type.
    • Añadir reintentos con backoff (con cuidado de no hacer ruido).

    extract_basic_fingerprint(headers: dict) -> dict

    Propósito: fingerprinting básico y ético: mirar lo que el servidor declara en headers.

    Qué hace:

    • Lee Server, X-Powered-By, Via.
    • Devuelve un dict con esos valores.

    Por qué es ciber:

    • Esto a veces revela stack (“nginx”, “Apache”, “Express”, “PHP”, “cloudflare”, proxies…).
    • Es “OSINT pasivo” dentro de una request normal.

    Limitaciones (muy importantes):

    • No es fiable: muchos servidores mienten o lo ocultan.
    • No debe tomarse como prueba definitiva.

    Mejoras:

    • Añadir heurísticas con Set-Cookie, X-AspNet-Version, CF-RAY, etc.
    • Detectar CDN/WAF con señales comunes (pero siempre como “posible”).

    analyze_security_headers(headers: dict) -> dict

    Propósito: comprobar presencia/ausencia de headers de seguridad y generar notas.

    Qué hace:

    • Recorre SEC_HEADERS:
      • Si está presente → lo guarda con su valor.
      • Si falta → lo añade a missing.
    • Genera notes con heurísticas:
      • falta HSTS
      • X-Content-Type-Options no es nosniff
      • falta CSP

    Por qué es ciber:

    • Esto te da un “termómetro” rápido de hardening HTTP.
    • Útil para que alumnos aprendan que seguridad también es config.

    Limitaciones:

    • Que falte un header ≠ vulnerabilidad explotable.
    • CSP/HSTS requieren contexto (subdominios, preload, mixed content).

    Mejoras:

    • Analizar valores:
      • HSTS: max-age, includeSubDomains, preload
      • XFO: DENY/SAMEORIGIN
      • CSP: evitar unsafe-inline (ojo: no siempre posible)
    • Añadir chequeo de Secure/HttpOnly/SameSite en cookies (si haces GET real y hay cookies).

    is_interesting_status(code: int) -> bool

    Propósito: decidir qué códigos son “interesantes” para enumeración sin guardar ruido.

    Qué hace:

    • Devuelve True si status ∈ {200, 201, 202, 204, 301, 302, 307, 308, 401, 403}

    Por qué es ciber:

    • 401/403 son oro: “existe pero protegido”.
    • 3xx también: revela rutas canonical o paneles movidos.

    Limitaciones:

    • 404 también puede ser interesante si detectas “soft 404” (200 con página de error).
    • 500/502/503 deberían contarse como hallazgo porque indican superficie frágil.

    Mejoras:

    • Permitir configurar lista de códigos por CLI.
    • Detectar “soft 404” comparando longitud/huella de respuesta.

    scan_paths(base_url, paths, timeout, verify_tls, user_agent, max_paths)

    Propósito: enumeración “suave” de endpoints típicos con GET sin seguir redirecciones.

    Qué hace:

    • Crea requests.Session() (mejor que requests.get repetido):
      • Reutiliza conexión (keep-alive) → más rápido, menos carga.
    • Fija User-Agent.
    • Recorre rutas hasta max_paths.
    • Construye URL final con urljoin.
    • Llama safe_request(GET, ...) con:
      • allow_redirects=False (clave para recon: quieres ver el Location)
      • verify=verify_tls
      • timeout=timeout
    • Crea entry con datos:
      • path, url, error, ms
    • Si hay respuesta:
      • status, content-type, content-length, location
      • Si status es “interesante” → lo añade
    • Si hay error:
      • también lo añade (evidencia)

    Por qué es ciber:

    • Enumeración controlada, orientada a reporte.
    • No seguir redirects evita perder información y evita cadenas largas.

    Limitaciones / ética:

    • Un wordlist grande puede parecer “escaneo agresivo”.
    • No hay rate-limit → en un entorno real debes dormir entre requests.

    Mejoras pro:

    • Añadir --delay-ms o --rps para no hacer ruido.
    • Añadir método HEAD para paths (menos transferencia) y usar GET solo si “promete”.
    • Añadir comparación de contenido para detectar páginas trampa.

    head_base(base_url, timeout, verify_tls, user_agent)

    Propósito: obtener headers del objetivo base con HEAD (ligero).

    Qué hace:

    • Crea sesión, pone UA.
    • Llama safe_request("HEAD", base_url, allow_redirects=False, ...)
    • Si falla → devuelve dict con error y ms.
    • Si va bien → devuelve status, ms, headers.

    Por qué es ciber:

    • Minimiza impacto: HEAD normalmente no descarga cuerpo.
    • Buen punto de partida para fingerprint y seguridad headers.

    Limitaciones:

    • Algunos servidores no implementan HEAD bien:
      • responden distinto que GET
      • devuelven 405 Method Not Allowed
    • Si hay WAF/CDN, puede variar por método.

    Mejoras:

    • Si HEAD da 405 → fallback a GET con stream=True y sin leer cuerpo (o leyendo muy poco).

    main()

    Propósito: orquestar todo: argumentos, normalización, ejecutar recon, guardar reporte, resumen por consola.

    Qué hace:

    1. argparse define CLI:
      • target, timeout, insecure, max-paths, paths-file, out, ua
    2. Normaliza URL (si falla, sale con error claro).
    3. verify_tls = not args.insecure
    4. Carga paths:
      • si hay --paths-file, lo lee y normaliza cada ruta a /...
    5. Inicializa report:
      • target, timestamp_utc, config, base_head, fingerprint, security_headers, findings
    6. Llama head_base
      • si trae headers → fingerprint + security header analysis
    7. Llama scan_paths
    8. Guarda JSON en args.out
    9. Imprime resumen

    Por qué es ciber:

    • Deja un rastro reproducible: configuración + timestamp + evidencias.
    • Separa “descubrimiento” (head) de “enumeración” (paths).

    Limitaciones:

    • No hay control de concurrencia ni rate limit.
    • No hay logging estructurado (solo print final).

    Mejoras:

    • Añadir niveles de verbosidad -v / -vv.
    • Exportar también a Markdown (bonito para entregar como informe).
    • Añadir “modo aula”: limitar a X requests por minuto.

    if __name__ == "__main__": main()

    Propósito: permitir que el script funcione:

    • como programa ejecutable (CLI)
    • y también importable como módulo sin ejecutarse automáticamente

    • Puedes reutilizar funciones en otro fichero (tests, GUI, etc.).
  • R3.1 – Programación-bucles

    R3.1 – Programación-bucles

    Ejercicio 1

    Contando del 1 al 10

    Crea un programa en Java que muestre por pantalla los números del 1 al 10 utilizando un bucle for.

    Requisitos:

    1. El programa debe tener una clase llamada Contador.
    2. Dentro del método main, utiliza un bucle for para recorrer los números del 1 al 10.
    3. En cada iteración, imprime el número actual en una nueva línea.

    Salida esperada:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    

    Ejercicio 2

    Cuenta atrás de 100 a 0 en pasos de 10

    Crea un programa en Java que haga una cuenta atrás desde 100 hasta 0, restando 10 en cada paso.

    Cuando llegue a 0, debe mostrar el mensaje «Máquina hackeada».

    Requisitos:

    1. El programa debe tener una clase llamada CuentaAtras10.
    2. Dentro del método main, usa un bucle for que empiece en 100 y vaya disminuyendo de 10 en 10 hasta llegar a 0.
    3. En cada iteración imprime el número actual.
    4. Al finalizar, muestra el texto Máquina hackeada.

    Salida esperada:

    100
    90
    80
    70
    60
    50
    40
    30
    20
    10
    0
    Máquina hackeada
    
    

    Ejercicio 3

    Realiza un algoritmo que simule una caja registradora de un supermercado, donde va pidiendo cantidades y las va sumando, cada vez que pongamos una cantidad por la terminal saldra la suma acumulada del total.

    El programa dejara de pedir las cantidades de compra cuando insertemos un 0.

    En ese momento mostrara el total de la compra y nos pedira que le indiquemos con cuanto dinero nos pagan e indicara el cambio que debemos devolver.

    Ejemplo de ejecución:

    Introduce la cantidad de la compra (0 para finalizar): 25.50
    Suma acumulada: 25.50
    Introduce la cantidad de la compra (0 para finalizar): 40.00
    Suma acumulada: 65.50
    Introduce la cantidad de la compra (0 para finalizar): 15.75
    Suma acumulada: 81.25
    Introduce la cantidad de la compra (0 para finalizar): 0
    Total a pagar: 81.25
    Con cuánto dinero te pagan? 100.00
    Cambio a devolver: 18.75
    

    Ejercicio 4

    Desarrollar un programa en Java que genere un dibujo usando únicamente el carácter asterisco (*). Este ejercicio te ayudará a entender mejor el control de bucles y el diseño de patrones en la consola.

    Descripción:

    Tu tarea es crear un programa que dibuje una pirámide de asteriscos. La altura de la pirámide será determinada por el usuario al inicio del programa.

    Requisitos:

    1. El programa debe pedir al usuario que introduzca un número entero positivo, el cual representará la altura de la pirámide.
    2. Utiliza bucles for o while para generar el patrón.
    3. La pirámide debe estar alineada a la izquierda, sin utilizar espacios para centrar las líneas.

    Ejemplo de salida:

    Si el usuario ingresa 5, el programa deberá mostrar el siguiente patrón:

    *
    **
    ***
    ****
    *****
    

    Ejercicio 5

    Crea un programa en Java que muestre el siguiente patrón usando dos bucles for anidados:

    Salida esperada:

    54321
    4321
    321
    21
    1
    
    

    Requisitos:

    1. Crea una clase llamada PatronDel5al1.
    2. Dentro del método main, utiliza un primer bucle for para controlar las filas, que empiece en 5 y termine en 1.
    3. Dentro de ese bucle, usa otro for que empiece en el valor de la fila y baje hasta 1.
    4. Después del bucle interno, imprime un salto de línea (System.out.println()).