Construir una Calculadora de Sub Redes con HTML, Java Script y CSS

En esta ocasión vamos a construir un pequeño proyecto basado en Front-end, crearemos una herramienta sencilla para el cálculo de Sub Redes y de yapa le agregaremos un par de conversores de binario a decimal y de decimal a binario, sin olvidar, obviamente, el Modo Dark.

El bosquejo inicial es

Estructura de Archivos

Index.html –> Estructura Principal.

Estilos.css –> Para manejar los estilos del proyecto.

Logica js -> Mediante Java Script manejaremos la lógica y matemática del proyecto.

Index.html

Este archivo HTML contiene la estructura principal de nuestro proyecto.

1. Estructura General

El archivo comienza con la declaración <!DOCTYPE html>, lo cual indica que el documento es HTML5.

<html lang="es">

Declara que el idioma del contenido es español, importante para accesibilidad y SEO.

<head>

Esta sección contiene meta-información sobre el documento, como la codificación (UTF-8) y la inclusión de archivos externos (CSS y JavaScript).

<body>

En esta sección se coloca el contenido visible para el usuario, incluyendo los formularios, botones y tablas.

2. Recursos y Scripts

CSS y Bootstrap
Se incluyen estilos de Bootstrap desde un CDN para facilitar el diseño responsivo y estilizado.

estilos.css
Archivo de estilos personalizado para modificar la apariencia.

logica.js
Incluye toda la lógica del proyecto.

3. Contenido Principal

El cuerpo del HTML (<body>) está dividido en tres secciones principales:

A. Cabecera

Public IP y Modo Oscuro

<span id="public-ip">Mi IP: Cargando...</span>

Muestra la IP pública del usuario, cargada con JavaScript.

Botón de Modo Oscuro

Con el ID theme-toggle, activa una función JavaScript para cambiar el tema de la aplicación.

B. Calculadora de Subredes

Dirección de Red

<input type="text" id="network">

Entrada para que el usuario ingrese una dirección de red en formato CIDR, como 192.168.1.0/24.

Número de Subredes

<select id="subnets">

Selector para que el usuario elija el número de subredes deseadas.

Botón Calcular

function calcularSubnets()

Llama a una función JavaScript que realiza los cálculos de subredes y muestra los resultados en la tabla.

Tabla de Resultados

<tbody id="resultados"> 

Aquí se insertan las subredes calculadas con su dirección de red, dirección de broadcast y máscara.

C. Conversores de Número

La aplicación también incluye herramientas de conversión entre binario, decimal y hexadecimal:

Binario a Decimal/Hexadecimal

<input type="text" id="binary-input">

Permite que el usuario ingrese un número en binario.

function convertirBinario()

Realiza la conversión cuando el usuario hace clic en el botón Convertir.

Los valores convertidos (decimal y hexadecimal) aparecen en una tabla.

Decimal a Binario/Hexadecimal

<input type="number" id="decimal-input"> 

Permite que el usuario ingrese un número en decimal.

function convertirDecimal()

Realiza la conversión a binario y hexadecimal. Los resultados se muestran en una tabla.

Archivo index.html completo

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Calculadora de Subredes</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="estilos/estilos.css">
    <script src="javascript/binarioadecimal.js"></script>
    <script src="javascript/calcularmascara.js"></script>
    <script src="javascript/calcularsubnet.js"></script>
    <script src="javascript/decimalabinario.js"></script>
    <script src="javascript/desplegarsubnet.js"></script>
    <script src="javascript/enteroaip.js"></script>
    <script src="javascript/modooscuro.js"></script>
    <script src="javascript/traerip.js"></script>
    <script src="javascript/*"></script>
</head>
<body>
    <div class="container main-container">
        <!-- Cabecera -->
        <div class="header">
            <span id="public-ip">Mi IP: Cargando...</span>
            <h1>Calculadora de Subredes</h1>
            <button id="theme-toggle" class="btn btn-dark-mode" onclick="modoOscuro()">🌙</button>
        </div>

        <!-- Contenedor de Calculadora de Subredes -->
        <div class="section-container">
            <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">
                    <!-- Resultados de subredes se insertarán aquí -->
                </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>
</body>
</html>

Estilos.css

Estilos Generales del Cuerpo

body {
    background-color: #ffffff;
    color: #000000;
    transition: background-color 0.3s, color 0.3s;
}
body.dark-mode {
    background-color: #121212;
    color: #ffffff;
}

Modo Oscuro

El archivo CSS permite que el fondo y el color del texto cambien suavemente de claro a oscuro mediante la clase .dark-mode. La propiedad transition crea un cambio gradual cuando se activa o desactiva el modo oscuro.

Estilos de Contenedores

.main-container: 

Limita el ancho de la calculadora a 800px, centrando y agregando un espacio de 20px para hacerla más legible.

.header: 

La cabecera tiene un diseño de flexbox que alinea el título de la calculadora y la IP del usuario en una misma fila, con el botón de modo oscuro en la esquina derecha.

.section-container: 

Este contenedor para la calculadora de subredes tiene un fondo gris claro, bordes redondeados y una sombra para hacer que destaque.

.converter-container: 

Organiza los conversores de binario/decimal/hexadecimal en una fila, con espacio (gap) entre ellos.

Botones y Controles

.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;
}
.btn-calculate {
    background-color: #e5322b;
    color: #fff;
    border: none;
    margin-top: 10px;
    width: 100%;
}

Botón de Modo Oscuro
.btn-dark-mode: 

Es un círculo rojo con un ícono de luna para alternar entre los modos claro y oscuro.

Botón Calcular Subredes
.btn-calculate:

Es un botón rojo de ancho completo para iniciar el cálculo de subredes.

Tablas y Campos de Entrada
.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;
}

La tabla donde se muestran los resultados tiene bordes suaves, con el texto centrado.

Campos de Entrada

Cuando un usuario selecciona (focus) un campo, el borde se pone rojo (#e5322b), resaltando el campo activo.

Estilos en Modo Oscuro
.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;
}

En modo oscuro, los contenedores y botones cambian sus colores para tener un contraste que sea amigable a la vista.

Adaptación a Pantallas Pequeñas (Media Query)
@media (max-width: 480px) { ... }

Esta sección usa un media query para pantallas de 480px o menos (como un móvil pequeño):

Los elementos se redistribuyen para ocupar el espacio disponible de una sola columna en lugar de una fila.

Los textos y los botones se reducen en tamaño font-size para ajustarse mejor en pantallas pequeñas.

Archivo estilos.css completo

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;
    }
}

Archivo Java Script

Leer cuidadosamente cada función construida mediante Java Script

async function traerIpPublica()

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();
};

Usa fetch para enviar una solicitud a https://api.ipify.org?format=json, una API pública que devuelve la IP pública en formato JSON.

Al recibir la respuesta, convierte los datos en un objeto JavaScript y accede al campo ip, que contiene la IP del usuario.

Actualiza el contenido del elemento HTML con id=»public-ip» para mostrar la IP.

Si ocurre un error, muestra «Error al obtener IP pública.»

Activación al Cargar la Página, se ejecuta automáticamente cuando se carga la página mediante el evento window.onload

function esteroaIp(num)

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

Usa operaciones de desplazamiento de bits (>>>) y (& 255) para extraer cada octeto de 8 bits.

Cada octeto representa una parte de la dirección IP en formato decimal.

Combina los cuatro octetos en un arreglo y los une en una cadena de texto separada por puntos.

Ejemplo, si num = 3232235776, la salida sería 192.168.1.0

function calcularMascara(prefix)

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

Crea una máscara binaria con 0xFFFFFFFF y desplaza bits para obtener la cantidad de 1 necesarios.

La máscara se convierte en formato decimal aplicando & 255 para extraer cada octeto.

Los octetos se unen y forman la máscara de subred en notación A.B.C.D.

Por ejemplo, con prefix = 24, la máscara resultante sería 255.255.255.0.

function ipaEntero(ip)

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

Divide la dirección IP en 4 partes usando split(‘.’).

Con reduce, desplaza los bits acumulados hacia la izquierda en 8 bits y suma el octeto actual.

La conversión a entero es útil para realizar operaciones de subred.

Ejemplo, para ip = «192.168.1.0», el entero resultante sería 3232235776

function calcularSubnets()

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;
    }

    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 = enteroaIp(currentNetwork);
        const broadcast = enteroaIp(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;
}

Obtiene la dirección de red y el número de subredes deseado.

Divide la dirección y el prefijo de la red (networkAddress y prefix)

Calcula el nuevo prefijo y el tamaño de cada subred basado en el número de subredes

Itera, calculando cada subred con dirección de red, broadcast y máscara de subred.

Muestra cada subred en una tabla HTML.

function desplegarSubnets()

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.";
    }
}

Verifica que el prefijo sea válido y calcula el número máximo de subredes posibles.

Muestra opciones de cantidad de subredes en el menú desplegable.

Si no hay prefijo válido, muestra un mensaje de error.

function validarBits(event)

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}`;
}

Remueve caracteres no binarios y actualiza el contenido del campo.

Muestra la cantidad de bits ingresados.

function convertirBinario()

function convertirBinario() {
    const binaryInput = document.getElementById('binary-input').value;
    const decimal = parseInt(binaryInput, 2);
    const hexadecimal = decimal.toString(16).toUpperCase();

    const conversionBody = document.getElementById('binario-a-decimal');
    conversionBody.innerHTML = `<tr>
        <td>${decimal}</td>
        <td>${hexadecimal}</td>
    </tr>`;
}

Convierte el binario a decimal y hexadecimal.

Inserta los resultados en una tabla HTML.

function convertirDecimal()

function convertirDecimal() {
    const decimalInput = document.getElementById('decimal-input').value;
    const binary = parseInt(decimalInput).toString(2);
    const hexadecimal = parseInt(decimalInput).toString(16).toUpperCase();

    const decimalConversionBody = document.getElementById('decimal-a-binario');
    decimalConversionBody.innerHTML = `<tr>
        <td>${binary}</td>
        <td>${hexadecimal}</td>
    </tr>`;
}

Convierte el decimal en binario y hexadecimal.

Inserta los resultados en una tabla HTML.

function modoOscuro()

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

Agrega o elimina la clase dark-mode del body, activando el modo oscuro.

Archivo logica.js completo

async function traerIpPublica() {
    try {
        // Obtener la IP pública
        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();
};

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

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

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

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;
    }

    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;
}

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.";
    }
}

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 decimal = parseInt(binaryInput, 2);
    const hexadecimal = decimal.toString(16).toUpperCase();

    const conversionBody = document.getElementById('binario-a-decimal');
    conversionBody.innerHTML = `<tr>
        <td>${decimal}</td>
        <td>${hexadecimal}</td>
    </tr>`;
}

function convertirDecimal() {
    const decimalInput = document.getElementById('decimal-input').value;
    const binary = parseInt(decimalInput).toString(2);
    const hexadecimal = parseInt(decimalInput).toString(16).toUpperCase();

    const decimalConversionBody = document.getElementById('decimal-a-binario');
    decimalConversionBody.innerHTML = `<tr>
        <td>${binary}</td>
        <td>${hexadecimal}</td>
    </tr>`;
}

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

Ya tenemos listo el proyecto

Lo más importante, el modo programador (dark) funcionando.

GitHub del Proyecto

https://github.com/walthercurodelacruz/Calculadora_de_Subredes