Construcción de Proyecto Repobit

En este proyecto vamos a construir la herramienta Repobit, usada en clases para el calculo de subredes.

Este proyecto esta construido sobre Front (html, css, js) detallaremos uno a uno cada documento a fin de que el estudiante tenga claro lo que esta construyendo.

Index.html

1. Cabecera del Documento (<head>)

Aquí es donde definimos los metadatos y las dependencias externas.

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title> Repobit | by @curowalther </title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="estilos.css">
</head>

charset="UTF-8": Asegura que el navegador interprete correctamente caracteres especiales (tildes, ñ, etc.).

viewport: Crucial para el Responsive Design. Permite que la escala se adapte al ancho del dispositivo (móviles, tablets).

Bootstrap 4.5.2: Se utiliza este framework de CSS externo para agilizar el diseño de tablas, botones y formularios, proporcionando una base estética profesional y estructurada mediante rejillas (grids).

estilos.css: Nuestra hoja de estilos personalizada donde manejamos el «Modo Oscuro» y ajustes finos que Bootstrap no cubre.

2. Estructura de la Cabecera Visual (.header)

Ubicada dentro del contenedor principal, maneja la identidad y utilidades rápidas.

<div class="header">
    <span id="public-ip">Mi IP: Cargando...</span>
    <h1> Repobit | by @curowalther </h1>
    <button id="theme-toggle" class="btn btn-dark-mode" onclick="modoOscuro()">🌙</button>
</div>

<span id="public-ip">: Un marcador de posición. Al cargar la página, la función traerIpPublica() en logica.js buscará este ID para inyectar la IP detectada.

onclick="modoOscuro()": Un event listener directo. Al hacer clic, se dispara la función de JavaScript que alterna la clase .dark-mode en el body.

3. Sección FLSM (Fixed Length Subnet Mask)

Esta sección permite dividir una red en subredes de igual tamaño.

<div class="section-container">
    <h3 class="mb-3">FLSM (mismo tamaño)</h3>
    <div class="form-group">
        <label for="network">Dirección de Red (ejemplo. 192.168.1.0/24)</label>
        <input type="text" class="form-control" id="network" placeholder="Ingrese la dirección de red"
            oninput="desplegarSubnets()">
        <div id="subnet-error" class="text-danger"></div>
    </div>
    <div class="form-group">
        <label for="subnets">Número de Subredes</label>
        <select class="form-control" id="subnets">
            <option value="">Seleccione una opción</option>
        </select>
    </div>
    <button class="btn btn-calculate" onclick="calcularSubnets()">Calcular Subredes</button>
    ...

oninput="desplegarSubnets()": Este es un detalle técnico importante. Mientras el usuario escribe la red (ej. 192.168.1.0/24), la función valida el prefijo en tiempo real y llena dinámicamente el elemento <select> con las opciones de subredes posibles basándose en los bits disponibles.

id="network" y id="subnets": IDs únicos que JavaScript utiliza para extraer los valores de entrada.

id="resultados" (en la tabla): El <tbody> está vacío inicialmente. JavaScript generará filas <tr> dinámicamente y las inyectará aquí tras el cálculo.

4. Sección VLSM (Variable Length Subnet Mask)

A diferencia de FLSM, esta sección maneja requerimientos variables, lo que implica una entrada de datos más abierta.

<div class="section-container mt-5">
    <h3 class="mb-3">VLSM (tamaños variables)</h3>
    <div class="form-group">
        <label for="vlsm-network">Dirección de Red base (ejemplo. 192.168.1.0/24)</label>
        <input type="text" class="form-control" id="vlsm-network" placeholder="Ingrese la red base en CIDR">
    </div>
    <div class="form-group">
        <label for="vlsm-hosts">Requerimientos de hosts por subred</label>
        <textarea id="vlsm-hosts" class="form-control" rows="3" placeholder="Ejemplo: 50,20,10,6"></textarea>
        <small class="text-muted">Se asignarán en orden descendente...</small>
    </div>
    <button class="btn btn-success" onclick="calcularVLSM()">Calcular VLSM</button>
    ...

<textarea id="vlsm-hosts">: Se utiliza un área de texto en lugar de un simple input porque esperamos una lista de números (hosts) separados por comas. Técnicamente, JavaScript procesará este string, lo dividirá (split) y lo convertirá en un array de enteros.

onclick="calcularVLSM()": Dispara la lógica más compleja del proyecto, que debe ordenar los hosts de mayor a menor para optimizar el espacio de direccionamiento (estándar de redes).

id="vlsm-resultados": Al igual que en FLSM, este es el contenedor de tipo <tbody> donde se inyectarán los cálculos (IP de red, prefijo, máscara, hosts usables y broadcast).

5. Conversores de Sistemas Numéricos

Estos están agrupados en un contenedor flexbox (.converter-container) definido en CSS para que aparezcan uno al lado del otro en pantallas grandes.

Binario a Decimal/Hexadecimal

<div class="converter">
    <h3>Binario a Decimal/Hexadecimal</h3>
    <div class="form-group">
        <label for="binary-input" id="bit-counter">Número Binario</label>
        <input type="text" class="form-control" id="binary-input" oninput="validarBits(event)"
            placeholder="Ingrese solo bits (0s y 1s)">
    </div>
    <button class="btn btn-secondary" onclick="convertirBinario()">Convertir</button>
    ...
</div>

oninput="validarBits(event)": Detalles técnicos de validación:

Filtra en tiempo real para que el usuario no pueda escribir nada que no sea 0 o 1.

Actualiza dinámicamente el id="bit-counter" para mostrar cuántos bits lleva el usuario (útil para completar octetos de 8 bits).

Decimal a Binario/Hexadecimal

<div class="converter">
    <h3>Decimal a Binario/Hexadecimal</h3>
    <div class="form-group">
        <label for="decimal-input">Número Decimal</label>
        <input type="number" class="form-control" id="decimal-input" ...>
    </div>
    <button class="btn btn-secondary" onclick="convertirDecimal()">Convertir</button>
    ...
</div>

Aquí el input es de tipo number, lo que activa los controles del navegador para valores numéricos y restringe caracteres alfabéticos de entrada.

6. Cierre y Scripts

    <script src="logica.js"></script>
</body>

Ubicación del <script>: Situado justo antes de cerrar el </body>. Esto es una buena práctica técnica: asegura que todo el DOM (Document Object Model) esté cargado en la memoria del navegador antes de que el script intente buscar elementos por ID. Si se pusiera en el <head>, el script fallaría al no encontrar los inputs o tablas.

estilos.css

El archivo estilos.css es el motor visual del proyecto. No solo se encarga de la estética, sino que implementa una lógica de tematización dinámica (Modo Claro/Oscuro) de forma muy eficiente.

Aquí tienes el desglose técnico:

1. Sistema de Transiciones y Tematización Base

body {
    background-color: #ffffff;
    color: #000000;
    transition: background-color 0.3s, color 0.3s;
}

body.dark-mode {
    background-color: #121212;
    color: #ffffff;
}

transition: 0.3s: Técnicamente, esto suaviza el cambio de colores. Sin esto, el cambio a modo oscuro sería instantáneo y «agresivo» para la vista.

Estrategia de Clase: En lugar de usar múltiples hojas de estilos, usamos una sola clase .dark-mode en el body. JavaScript simplemente alterna esta clase, y el CSS se encarga del resto mediante herencia y especificidad.

2. Layout (Distribución de Espacios)





.main-container {
    max-width: 800px;
    margin: auto;
    padding: 20px;
}

.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

Flexbox: En la clase .header, usamos display: flex.

justify-content: space-between empuja los elementos a los extremos (IP a la izquierda, título al centro y botón de modo oscuro a la derecha).

align-items: center asegura que todos tengan la misma línea base vertical.

3. Diseño de Componentes (Tarjetas y Botones)

El proyecto utiliza un diseño de «tarjetas» (cards) para separar las secciones:

.section-container, .converter {
    background-color: #f9f9f9;
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1);
}

box-shadow: Crea profundidad. Un desplazamiento de 0px con un desenfoque de 8px da un efecto de elevación sutil.

Color de Acento (#e5322b): Se define una identidad visual usando este tono rojizo para los bordes de enfoque (:focus) y los botones principales (.btn-calculate).

4. Modo Oscuro Avanzado (Sobreescritura)

.dark-mode .section-container,
.dark-mode .converter {
    background-color: #333;
    color: #fff;
}

Aquí vemos la potencia del CSS: cuando el body tiene la clase .dark-mode, las tarjetas cambian de un gris muy claro (#f9f9f9) a un gris oscuro (#333). Esto mantiene el contraste necesario para la legibilidad sin usar negro puro.

5. Adaptabilidad (Responsive Design)

@media (max-width: 480px) {
    .header {
        flex-direction: column;
    }
    .converter-container {
        flex-direction: column;
    }
}

Media Queries: Es vital para herramientas de cálculo que se usan en campo (móviles).

Cambio de Axis: El flex-direction: column hace que los conversores, que en PC están uno al lado del otro, se apilen verticalmente en móviles para evitar que el contenido se desborde o se vea comprimido.

logica.js

Llegamos al cerebro del proyecto: logica.js. Aquí es donde ocurren los cálculos matemáticos binarios y la manipulación dinámica del DOM.

Vamos a dividirlo por bloques funcionales.

1. Utilidades de Direccionamiento IP

Estas funciones son la base de todos los cálculos de red. Trabajan convirtiendo IPs (strings) a números enteros (bits) para poder operar con ellas.

function ipaEntero(ip) {
    return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>> 0;
}

function esteroaIp(num) {
    return [
        (num >>> 24) & 255,
        (num >>> 16) & 255,
        (num >>> 8) & 255,
        num & 255
    ].join('.');
}

ipaEntero (IP a Entero):

Técnicamente, una IP son 32 bits. Esta función toma los 4 octetos, los desplaza a la izquierda (<< 8) y los suma.

El operador >>> 0 es un truco de JavaScript para asegurar que el resultado sea un entero de 32 bits sin signo.

esteroaIp (Entero a IP): Hace lo contrario. Usa desplazamientos a la derecha (>>>) y una máscara a nivel de bits (& 255) para extraer cada octeto de 8 bits y volver a formar la cadena X.X.X.X.

2. Validaciones (Seguridad y Datos)

Antes de calcular, el script asegura que los datos sean correctos para evitar errores en tiempo de ejecución.

function validarCIDR(cidr) {
    const regex = /^(\d{1,3}\.){3}\d{1,3}\/([0-9]|[1-2][0-9]|3[0-2])$/;
    if (!regex.test(cidr)) return false;
    // ... validación adicional de rangos 0-255
}

Expresiones Regulares (Regex): Se usa una expresión regular estricta para validar el formato CIDR (ej: 192.168.1.0/24). Verifica que haya 4 grupos de números y un prefijo entre 0 y 32.

3. Lógica FLSM (Fixed Length Subnet Mask)

Esta función calcula subredes de igual tamaño basándose en potencias de 2.

function calcularSubnets() {
    // ... extracción de datos
    const newPrefix = prefixInt + Math.ceil(Math.log2(subnetCount));
    const subnetSize = Math.pow(2, 32 - newPrefix);
    
    for (let i = 0; i < subnetCount; i++) {
        const network = esteroaIp(currentNetwork);
        const broadcast = esteroaIp(currentNetwork + subnetSize - 1);
        // ... inyección en la tabla
    }
}

Math.log2: Determina cuántos bits «prestados» se necesitan para crear el número de subredes solicitado.

Cálculo de Saltos: El subnetSize define de cuánto en cuánto «salta» la dirección de red. El broadcast es siempre la dirección anterior a la siguiente red (+ subnetSize - 1).

4. Lógica de Conversores

Funciones simples pero potentes que aprovechan las capacidades nativas de JavaScript.

function convertirBinario() {
    const decimal = parseInt(binaryInput, 2);
    const hexadecimal = decimal.toString(16).toUpperCase();
}

parseInt(valor, 2): Convierte una cadena binaria directamente a base 10 (decimal).

.toString(16): Convierte un número decimal a su representación hexadecimal.

5. Lógica VLSM (Variable Length Subnet Mask)

Esta es la «joya» del script. A diferencia de FLSM, aquí las subredes pueden tener diferentes máscaras.

function calcularVLSM() {
    // ...
    let hosts = hostsInput.split(",").map(h => parseInt(h.trim()));
    hosts.sort((a, b) => b - a); // Ordenar de mayor a menor

    for (let i = 0; i < hosts.length; i++) {
        const needed = hosts[i] + 2; // Hosts + Red + Broadcast
        let bits = Math.ceil(Math.log2(needed));
        let subnetSize = Math.pow(2, bits);
        let newPrefix = 32 - bits;
        // ...
        currentIP += subnetSize; // El próximo salto
    }
}

Análisis técnico del proceso:

Ordenamiento Decisivo: Es obligatorio ordenar los requerimientos de hosts de mayor a menor. Si se calcularan subredes pequeñas primero y luego una grande, se fragmentaría el espacio de direcciones IP y no cabrían las redes mayores.

La regla del +2: Todo ingeniero de redes sabe que se necesitan dos direcciones extra por subred: una para la Dirección de Red y otra para el Broadcast. El código lo contempla en hosts[i] + 2.

Cálculo de Bits:

Math.log2(needed) nos dice cuántos bits de host se necesitan.

Math.ceil redondea hacia arriba (ej: si necesitas 5 hosts + 2 = 7, log2(7) es aproximadamente 2.8, redondeado a 3 bits, lo que da una subred de 8 direcciones).

Control de Desbordamiento: El script verifica continuamente si la suma de todas las subredes excede la capacidad de la red base (ej: una /24 solo tiene 256 IPs totales).

6. Utilidades de Conectividad e Interfaz

Obtención de IP Pública

async function traerIpPublica() {
    try {
        const publicIpResponse = await fetch('https://api.ipify.org?format=json');
        const publicIpData = await publicIpResponse.json();
        document.getElementById('public-ip').innerText = publicIpData.ip;
    } catch (error) {
        document.getElementById('public-ip').innerText = "Error...";
    }
}

Asincronía (async/await): Se usa para no bloquear la carga de la página. El navegador solicita la IP a un servidor externo (ipify) en segundo plano. Cuando el servidor responde, los datos se inyectan en el DOM.

Modo Oscuro

function modoOscuro() {
    document.body.classList.toggle('dark-mode');
}

classList.toggle: Es la forma más limpia y moderna de manejar estados en JS. Si la clase dark-mode existe, la quita; si no existe, la pone. Esto dispara inmediatamente los cambios de color que analizamos en el CSS.

Resultado

Código Completo

INDEX.HTML

<!DOCTYPE html>
<html lang="es">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title> Repobit | by @curowalther </title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="estilos.css">
</head>

<body>
    <div class="container main-container">
        <!-- Cabecera -->
        <div class="header">
            <span id="public-ip">Mi IP: Cargando...</span>
            <h1> Repobit | by @curowalther </h1>
            <button id="theme-toggle" class="btn btn-dark-mode" onclick="modoOscuro()">🌙</button>
        </div>

        <!-- Contenedor de Calculadora de Subredes (FLSM) -->
        <div class="section-container">
            <h3 class="mb-3">FLSM (mismo tamaño)</h3>
            <div class="form-group">
                <label for="network">Dirección de Red (ejemplo. 192.168.1.0/24)</label>
                <input type="text" class="form-control" id="network" placeholder="Ingrese la dirección de red"
                    oninput="desplegarSubnets()">
                <div id="subnet-error" class="text-danger"></div>
            </div>
            <div class="form-group">
                <label for="subnets">Número de Subredes</label>
                <select class="form-control" id="subnets">
                    <option value="">Seleccione una opción</option>
                </select>
            </div>
            <button class="btn btn-calculate" onclick="calcularSubnets()">Calcular Subredes</button>
            <table class="table table-bordered mt-4">
                <thead>
                    <tr>
                        <th>Subred</th>
                        <th>Dirección de Red</th>
                        <th>Broadcast</th>
                        <th>Máscara de Subred</th>
                    </tr>
                </thead>
                <tbody id="resultados"></tbody>
            </table>
        </div>

        <!-- Contenedor de Calculadora de Subredes (VLSM) -->
        <div class="section-container mt-5">
            <h3 class="mb-3">VLSM (tamaños variables)</h3>
            <div class="form-group">
                <label for="vlsm-network">Dirección de Red base (ejemplo. 192.168.1.0/24)</label>
                <input type="text" class="form-control" id="vlsm-network" placeholder="Ingrese la red base en CIDR">
            </div>
            <div class="form-group">
                <label for="vlsm-hosts">Requerimientos de hosts por subred</label>
                <textarea id="vlsm-hosts" class="form-control" rows="3" placeholder="Ejemplo: 50,20,10,6"></textarea>
                <small class="text-muted">Se asignarán en orden descendente (primero las subredes más grandes).</small>
            </div>
            <button class="btn btn-success" onclick="calcularVLSM()">Calcular VLSM</button>
            <div id="vlsm-error" class="text-danger mt-2"></div>
            <table class="table table-bordered mt-4">
                <thead>
                    <tr>
                        <th>#</th>
                        <th>Hosts requeridos</th>
                        <th>Red / Prefijo</th>
                        <th>Máscara</th>
                        <th>Usables</th>
                        <th>Broadcast</th>
                    </tr>
                </thead>
                <tbody id="vlsm-resultados"></tbody>
            </table>
        </div>

        <!-- Contenedores de Conversores -->
        <div class="converter-container">
            <div class="converter">
                <h3>Binario a Decimal/Hexadecimal</h3>
                <div class="form-group">
                    <label for="binary-input" id="bit-counter">Número Binario</label>
                    <input type="text" class="form-control" id="binary-input" oninput="validarBits(event)"
                        placeholder="Ingrese solo bits (0s y 1s)">
                </div>
                <button class="btn btn-secondary" onclick="convertirBinario()">Convertir</button>
                <table class="table table-bordered mt-3">
                    <thead>
                        <tr>
                            <th>Decimal</th>
                            <th>Hexadecimal</th>
                        </tr>
                    </thead>
                    <tbody id="binario-a-decimal"></tbody>
                </table>
            </div>

            <div class="converter">
                <h3>Decimal a Binario/Hexadecimal</h3>
                <div class="form-group">
                    <label for="decimal-input">Número Decimal</label>
                    <input type="number" class="form-control" id="decimal-input"
                        placeholder="Ingrese un número decimal">
                </div>
                <button class="btn btn-secondary" onclick="convertirDecimal()">Convertir</button>
                <table class="table table-bordered mt-3">
                    <thead>
                        <tr>
                            <th>Binario</th>
                            <th>Hexadecimal</th>
                        </tr>
                    </thead>
                    <tbody id="decimal-a-binario"></tbody>
                </table>
            </div>
        </div>
    </div>

    <!-- Archivos JS al final del body -->
    <script src="logica.js"></script>
</body>

</html>

ESTILOS.CSS

body {
    background-color: #ffffff;
    color: #000000;
    transition: background-color 0.3s, color 0.3s;
}

body.dark-mode {
    background-color: #121212;
    color: #ffffff;
}

.main-container {
    max-width: 800px;
    margin: auto;
    padding: 20px;
}

.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}

.header h1 {
    font-size: 1.5rem;
    margin: 0;
}

#public-ip {
    font-weight: bold;
}

.btn-dark-mode {
    background-color: #e5322b;
    color: #fff;
    border: none;
    border-radius: 50%;
    width: 35px;
    height: 35px;
    font-size: 1.2rem;
    display: flex;
    align-items: center;
    justify-content: center;
}

.section-container {
    background-color: #f9f9f9;
    padding: 20px;
    border-radius: 10px;
    margin-bottom: 20px;
    box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1);
}

.btn-calculate {
    background-color: #e5322b;
    color: #fff;
    border: none;
    margin-top: 10px;
    width: 100%;
}

.converter-container {
    display: flex;
    justify-content: space-between;
    gap: 20px;
}

.converter {
    flex: 1;
    background-color: #f9f9f9;
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1);
}

.converter h3 {
    font-size: 1rem;
    margin-bottom: 15px;
}

.table {
    background-color: #ffffff;
    border-collapse: collapse;
}

.table th, .table td {
    border: 1px solid #ddd;
    text-align: center;
    font-size: 0.9rem;
    padding: 8px;
}

input:focus, select:focus {
    outline: none;
    border-color: #e5322b;
}

.dark-mode .btn-dark-mode {
    background-color: #333;
    color: #e5322b;
}

.dark-mode .btn-calculate,
.dark-mode .btn-secondary {
    background-color: #e5322b;
    color: #fff;
}

.dark-mode .section-container,
.dark-mode .converter {
    background-color: #333;
    color: #fff;
}

/* Media Query para pantallas pequeñas (como iPhone 5) */
@media (max-width: 480px) {
    .main-container {
        padding: 10px;
    }

    .header {
        flex-direction: column;
        align-items: center;
    }

    .header h1 {
        font-size: 1.2rem;
        text-align: center;
    }

    #public-ip {
        text-align: center;
        margin-top: 10px;
    }

    .btn-dark-mode {
        margin-top: 10px;
        width: 30px;
        height: 30px;
        font-size: 1rem;
    }

    .section-container, .converter {
        padding: 15px;
    }

    .converter h3 {
        font-size: 0.9rem;
    }

    .btn-calculate {
        font-size: 0.9rem;
    }

    .table th, .table td {
        font-size: 0.8rem;
        padding: 5px;
    }

    .converter-container {
        flex-direction: column;
    }
}

#descripcion-comando {
    font-size: 0.9rem;
    line-height: 1.6;
    background-color: #f8f9fa;
    padding: 15px;
    border-left: 5px solid #e5322b;
    white-space: pre-wrap;
}

LOGICA.JS

// Obtener IP pública
async function traerIpPublica() {
    try {
        const publicIpResponse = await fetch('https://api.ipify.org?format=json');
        const publicIpData = await publicIpResponse.json();
        document.getElementById('public-ip').innerText = publicIpData.ip;
    } catch (error) {
        document.getElementById('public-ip').innerText = "Error al obtener IP pública.";
    }
}

window.onload = () => {
    traerIpPublica();
};

// Conversión IP ↔ Entero
function esteroaIp(num) {
    return [
        (num >>> 24) & 255,
        (num >>> 16) & 255,
        (num >>> 8) & 255,
        num & 255
    ].join('.');
}

function ipaEntero(ip) {
    return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>> 0;
}

function calcularMascara(prefix) {
    const mask = (0xFFFFFFFF >>> (32 - prefix)) << (32 - prefix);
    return [
        (mask >>> 24) & 255,
        (mask >>> 16) & 255,
        (mask >>> 8) & 255,
        mask & 255
    ].join('.');
}

// ===================== VALIDACIONES =====================

function validarCIDR(cidr) {
    const regex = /^(\d{1,3}\.){3}\d{1,3}\/([0-9]|[1-2][0-9]|3[0-2])$/;
    if (!regex.test(cidr)) return false;
    const [ip] = cidr.split('/');
    return ip.split('.').every(oct => {
        const n = parseInt(oct, 10);
        return n >= 0 && n <= 255;
    });
}

function validarDecimal(valor) {
    return /^[0-9]+$/.test(valor);
}

function validarBinario(valor) {
    return /^[01]+$/.test(valor);
}

function validarHosts(lista) {
    return lista.split(",").every(h => /^[0-9]+$/.test(h.trim()) && parseInt(h.trim()) > 0);
}


// ===================== FLSM =====================
function calcularSubnets() {
    const networkInput = document.getElementById('network').value;
    const subnetCountInput = document.getElementById('subnets');
    const subnetCount = parseInt(subnetCountInput.value);
    const resultBody = document.getElementById('resultados');
    resultBody.innerHTML = "";

    if (!networkInput || !subnetCount) {
        resultBody.innerHTML = "<tr><td colspan='4' class='text-danger'>Por favor ingresa una dirección de red válida y el número de subredes.</td></tr>";
        return;
    }

    if (!validarCIDR(networkInput)) {
        resultBody.innerHTML = "<tr><td colspan='4' class='text-danger'>Formato inválido. Ejemplo válido: 192.168.1.0/24</td></tr>";
        return;
    }

    const [networkAddress, prefix] = networkInput.split('/');
    const prefixInt = parseInt(prefix);
    const newPrefix = prefixInt + Math.ceil(Math.log2(subnetCount));
    const subnetSize = Math.pow(2, 32 - newPrefix);
    const subnetMask = calcularMascara(newPrefix);
    let currentNetwork = ipaEntero(networkAddress);

    for (let i = 0; i < subnetCount; i++) {
        const network = esteroaIp(currentNetwork);
        const broadcast = esteroaIp(currentNetwork + subnetSize - 1);
        resultBody.innerHTML += `<tr>
            <td>Subred ${i + 1}</td>
            <td>${network}/${newPrefix}</td>
            <td>${broadcast}</td>
            <td>${subnetMask}</td>
        </tr>`;
        currentNetwork += subnetSize;
    }

    subnetCountInput.value = subnetCount;
}

// ===================== DESPLEGAR SUBNETS =====================
function desplegarSubnets() {
    const networkInput = document.getElementById('network').value;
    const subnetCountInput = document.getElementById('subnets');
    const subnetError = document.getElementById('subnet-error');
    const prefixMatch = networkInput.match(/\/(\d+)$/);

    if (prefixMatch) {
        const prefix = parseInt(prefixMatch[1]);
        const maxSubnets = Math.pow(2, 32 - prefix);
        subnetCountInput.innerHTML = `<option value="">Seleccione una opción</option>`;
        for (let i = 1; i <= maxSubnets / 2; i *= 2) {
            subnetCountInput.innerHTML += `<option value="${i}">${i}</option>`;
        }
        subnetError.innerText = "";
    } else {
        subnetCountInput.innerHTML = `<option value="">Seleccione una opción</option>`;
        subnetError.innerText = "Por favor ingresa una dirección de red válida con prefijo.";
    }
}

// ===================== CONVERSORES =====================
function validarBits(event) {
    const input = event.target;
    const bits = input.value.replace(/[^01]/g, '');
    input.value = bits;
    document.getElementById('bit-counter').innerText = `Bits ingresados: ${bits.length}`;
}

function convertirBinario() {
    const binaryInput = document.getElementById('binary-input').value;
    const conversionBody = document.getElementById('binario-a-decimal');

    if (!validarBinario(binaryInput)) {
        conversionBody.innerHTML = `<tr><td colspan="2" class="text-danger">Entrada inválida. Solo 0 y 1.</td></tr>`;
        return;
    }

    const decimal = parseInt(binaryInput, 2);
    const hexadecimal = decimal.toString(16).toUpperCase();

    conversionBody.innerHTML = `<tr>
        <td>${decimal}</td>
        <td>${hexadecimal}</td>
    </tr>`;
}

function convertirDecimal() {
    const decimalInput = document.getElementById('decimal-input').value;
    const decimalConversionBody = document.getElementById('decimal-a-binario');

    if (!validarDecimal(decimalInput)) {
        decimalConversionBody.innerHTML = `<tr><td colspan="2" class="text-danger">Entrada inválida. Solo números positivos.</td></tr>`;
        return;
    }

    const number = parseInt(decimalInput);
    const binary = number.toString(2);
    const hexadecimal = number.toString(16).toUpperCase();

    decimalConversionBody.innerHTML = `<tr>
        <td>${binary}</td>
        <td>${hexadecimal}</td>
    </tr>`;
}

// ===================== MODO OSCURO =====================
function modoOscuro() {
    document.body.classList.toggle('dark-mode');
}



// ===================== VLSM =====================
function calcularVLSM() {
    const networkInput = document.getElementById('vlsm-network').value.trim();
    const hostsInput = document.getElementById('vlsm-hosts').value.trim();
    const resultBody = document.getElementById('vlsm-resultados');
    const errorDiv = document.getElementById('vlsm-error');
    resultBody.innerHTML = "";
    errorDiv.innerText = "";

    if (!networkInput || !hostsInput) {
        errorDiv.innerText = "Debes ingresar una red base y la lista de hosts.";
        return;
    }

    if (!validarCIDR(networkInput)) {
        errorDiv.innerText = "Formato inválido de red base. Ejemplo: 192.168.1.0/24";
        return;
    }
    if (!validarHosts(hostsInput)) {
        errorDiv.innerText = "La lista de hosts debe contener solo números positivos separados por comas.";
        return;
    }

    const [networkAddress, prefix] = networkInput.split('/');
    const prefixInt = parseInt(prefix);

    let hosts = hostsInput.split(",").map(h => parseInt(h.trim()));
    hosts.sort((a, b) => b - a);

    let currentIP = ipaEntero(networkAddress);
    const maxHosts = Math.pow(2, 32 - prefixInt);

    for (let i = 0; i < hosts.length; i++) {
        const needed = hosts[i] + 2; 
        let bits = Math.ceil(Math.log2(needed));
        let subnetSize = Math.pow(2, bits);
        let newPrefix = 32 - bits;
        let subnetMask = calcularMascara(newPrefix);

        if (subnetSize > maxHosts) {
            errorDiv.innerText = "No hay suficientes direcciones para asignar todas las subredes.";
            return;
        }

        const network = esteroaIp(currentIP);
        const broadcast = esteroaIp(currentIP + subnetSize - 1);
        const usable = subnetSize - 2;

        resultBody.innerHTML += `<tr>
            <td>${i + 1}</td>
            <td>${hosts[i]}</td>
            <td>${network}/${newPrefix}</td>
            <td>${subnetMask}</td>
            <td>${usable}</td>
            <td>${broadcast}</td>
        </tr>`;

        currentIP += subnetSize;
    }
}