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