NetCalc ISP Suite v3.4 – Topología Detallada
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
body { font-family: 'Inter', sans-serif; }
.ipv6-font { font-family: 'Courier New', Courier, monospace; letter-spacing: -0.5px; font-size: 0.9em; }
.fade-in { animation: fadeIn 0.4s ease-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
1. Red Principal
Ej: 192.168.0.0/24
3. Cola de Planificación
| Nombre | Perfil | Hosts/Prefijo | |
|---|
| No hay segmentos en la cola. |
Error message here.
Análisis de Capacidad ISP
Basado en el prefijo raíz
Tabla de Asignación
IPV4| Nombre | Subred Asignada | Rango / Detalles | Info |
|---|
// --- Configuration & State ---
let currentVersion = 'ipv4';
let requestedSubnets = [];// IPv6 Constants
const IPV6_OPTS = [
{ val: 'site_48', label: 'Delegación Corporativa (/48)', prefix: 48 },
{ val: 'site_56', label: 'Delegación Pyme/Residencial (/56)', prefix: 56 },
{ val: 'lan_64', label: 'Segmento LAN Estándar (/64)', prefix: 64 },
{ val: 'p2p_127', label: 'Infraestructura P2P (/127)', prefix: 127 }
];const IPV4_OPTS = [
{ val: 'lan', label: 'LAN / Oficina (Manual)' },
{ val: 'p2p_wifi', label: 'Enlace Wireless (4 IPs)' },
{ val: 'p2p_cable', label: 'Enlace Fibra/Cable (2 IPs)' },
{ val: 'wan', label: 'WAN Pequeña (4 IPs)' },
{ val: 'loopback', label: 'Loopback / Gestión (1 IP)' }
];// --- IPv4 Helper Functions ---
function ip2long(ip) {
let components = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
if (components) {
let iplong = 0;
let power = 1;
for (let i = 4; i >= 1; i -= 1) {
iplong += power * parseInt(components[i]);
power *= 256;
}
return iplong;
}
else return -1;
}function long2ip(long) {
return [ (long >>> 24) & 0xFF, (long >>> 16) & 0xFF, (long >>> 8) & 0xFF, long & 0xFF ].join('.');
}// --- BigInt Math Helper for IPv6 ---
function parseIPv6(ip) {
let addr = ip.split('/')[0].trim();
if (addr.includes('::')) {
const parts = addr.split('::');
const left = parts[0].split(':').filter(Boolean);
const right = parts[1].split(':').filter(Boolean);
const missing = 8 - (left.length + right.length);
const zeros = Array(missing).fill('0');
addr = [...left, ...zeros, ...right].join(':');
}
const segments = addr.split(':');
while(segments.length {
hexStr += (seg || '0').padStart(4, '0');
});
return BigInt(hexStr);
}function formatIPv6(bigIntVal) {
let hex = bigIntVal.toString(16).padStart(32, '0');
let groups = [];
for(let i=0; i g.replace(/^0+/, '') || '0').join(':');
}// --- UI Logic ---function setVersion(ver) {
currentVersion = ver;
const btn4 = document.getElementById('btn-ipv4');
const btn6 = document.getElementById('btn-ipv6');
const hint = document.getElementById('network-hint');
const rootInput = document.getElementById('root-network');
const select = document.getElementById('use-case');
const lblQty = document.getElementById('lbl-quantity');
const thReq = document.getElementById('th-req-size');if (ver === 'ipv4') {
btn4.classList.replace('text-slate-400', 'text-indigo-600');
btn4.classList.replace('bg-transparent', 'bg-white');
btn4.classList.add('shadow-sm');
btn6.classList.replace('text-indigo-600', 'text-slate-400');
btn6.classList.replace('bg-white', 'bg-transparent');
btn6.classList.remove('shadow-sm');hint.textContent = "Ej: 192.168.0.0/24 o 10.0.0.0/8";
rootInput.placeholder = "Ej: 192.168.1.0/24";
lblQty.textContent = "Hosts Requeridos";
thReq.textContent = "Hosts";
select.innerHTML = IPV4_OPTS.map(o => `${o.label}`).join('');
document.getElementById('isp-capacity-card').classList.add('hidden');
} else {
btn6.classList.replace('text-slate-400', 'text-indigo-600');
btn6.classList.replace('bg-transparent', 'bg-white');
btn6.classList.add('shadow-sm');
btn4.classList.replace('text-indigo-600', 'text-slate-400');
btn4.classList.replace('bg-white', 'bg-transparent');
btn4.classList.remove('shadow-sm');hint.textContent = "Ej: 2001:db8::/32 (ISP) o /48 (Corp)";
rootInput.placeholder = "Ej: 2001:db8::/32";
lblQty.textContent = "Prefijo / Tamaño";
thReq.textContent = "Tamaño (Prefijo)";
select.innerHTML = IPV6_OPTS.map(o => `${o.label}`).join('');
}
updateHostInput();
}function updateHostInput() {
const type = document.getElementById('use-case').value;
const hostInput = document.getElementById('sub-hosts');
const autoMsg = document.getElementById('auto-msg');
const badge = document.getElementById('ipv6-prefix-badge');
let val = "";
let disabled = false;
let showMsg = false;if (currentVersion === 'ipv4') {
badge.classList.add('hidden');
hostInput.type = "number";
switch(type) {
case 'p2p_cable': val = 2; disabled = true; showMsg = true; break;
case 'p2p_wifi': val = 4; disabled = true; showMsg = true; break;
case 'loopback': val = 1; disabled = true; showMsg = true; break;
case 'wan': val = 4; disabled = true; showMsg = true; break;
default: val = ""; disabled = false; showMsg = false;
}
} else {
hostInput.type = "text";
disabled = true;
showMsg = true;
badge.classList.remove('hidden');const opt = IPV6_OPTS.find(o => o.val === type);
if(opt) {
val = opt.prefix;
badge.textContent = `/${opt.prefix}`;
}
}hostInput.value = val;
hostInput.disabled = disabled;
if(disabled) {
hostInput.classList.add('bg-slate-100', 'text-slate-500');
if(showMsg) autoMsg.classList.remove('hidden');
} else {
hostInput.classList.remove('bg-slate-100', 'text-slate-500');
autoMsg.classList.add('hidden');
}
}function addSubnet() {
const name = document.getElementById('sub-name').value.trim() || `Segmento ${requestedSubnets.length + 1}`;
const type = document.getElementById('use-case').value;
const inputVal = document.getElementById('sub-hosts').value;
let dataVal = 0;
if (currentVersion === 'ipv4') {
dataVal = parseInt(inputVal);
if (isNaN(dataVal) || dataVal s.id !== id);
renderRequestList();
}function clearSubnets() {
requestedSubnets = [];
renderRequestList();
document.getElementById('results-area').classList.add('hidden');
document.getElementById('isp-capacity-card').classList.add('hidden');
}function renderRequestList() {
const tbody = document.getElementById('subnet-req-list');
tbody.innerHTML = "";
if (requestedSubnets.length === 0) {
tbody.innerHTML = `
| No hay segmentos en la cola. |
`;
return;
}requestedSubnets.forEach(sub => {
let typeLabel = "";
if(currentVersion === 'ipv4') {
typeLabel = IPV4_OPTS.find(o=>o.val === sub.type)?.label || 'Custom';
} else {
typeLabel = IPV6_OPTS.find(o=>o.val === sub.type)?.label || 'Custom';
}
let sizeDisplay = currentVersion === 'ipv4' ? `${sub.val} Hosts` : `/${sub.val}`;const tr = document.createElement('tr');
tr.className = "border-b border-slate-100 bg-white hover:bg-indigo-50 transition";
tr.innerHTML = `
${sub.name} | ${typeLabel} | ${sizeDisplay} |
|
`;
tbody.appendChild(tr);
});
}// --- Modals ---function showModal(title, content) {
document.getElementById('modal-title').innerHTML = title;
document.getElementById('modal-content').innerHTML = content;
const modal = document.getElementById('topology-modal');
modal.classList.remove('hidden');
modal.classList.add('flex');
}function closeModal() {
const modal = document.getElementById('topology-modal');
modal.classList.add('hidden');
modal.classList.remove('flex');
}// --- UPDATED: Detailed Topology Info ---
function showIpv4Info(netAddr, mask, type) {
// Re-calculate details internally to be robust
const netLong = ip2long(netAddr);
const totalSize = Math.pow(2, 32 - mask);
// Usable Range
const firstUsableLong = netLong + 1;
const lastUsableLong = netLong + totalSize - 2;
const broadcastLong = netLong + totalSize - 1;let contentHtml = "";if (type === 'p2p_wifi') {
// Topology: Router A (1st), Radio A (2nd), Radio B (Penultimate), Router B (Last)
const rtrA = long2ip(firstUsableLong);
const radioA = long2ip(firstUsableLong + 1);
const radioB = long2ip(lastUsableLong - 1);
const rtrB = long2ip(lastUsableLong);contentHtml = `
Topología Wireless
-
Router A (Origen)
${rtrA}
-
Radio A (AP)
${radioA}
-
Radio B (CPE)
${radioB}
-
Router B (Destino)
${rtrB}
Mácara de Subred: /${mask} (${long2ip(-1 << (32 - mask))})
`;
} else if (type === 'p2p_cable') {
// Topology: Equip A (1st), Equip B (Last)
const eqA = long2ip(firstUsableLong);
const eqB = long2ip(lastUsableLong);contentHtml = `
Enlace Fibra / Cable
-
Equipo A (Origen)
${eqA}
-
Equipo B (Destino)
${eqB}
Mácara de Subred: /${mask} (${long2ip(-1 << (32 - mask))})
`;
} else {
// Standard Logic
contentHtml = `
- Gateway (Sug.): ${long2ip(firstUsableLong)}
- Broadcast: ${long2ip(broadcastLong)}
- Hosts Útiles: ${totalSize - 2}
Rango usable: ${long2ip(firstUsableLong)} - ${long2ip(lastUsableLong)}
`;
}
showModal("Detalle IPv4 VLSM", contentHtml);
}function showIpv6Info(net, prefix, type) {
let desc = "";
let icon = "";
if (prefix === 48) { desc = "Prefijo estándar para Sitio Corporativo."; icon = "fa-building"; }
else if (prefix === 56) { desc = "Prefijo estándar para Pymes o Residencial."; icon = "fa-house-signal"; }
else if (prefix === 64) { desc = "Unidad atómica de red IPv6 (LAN)."; icon = "fa-network-wired"; }
else if (prefix === 127) { desc = "Enlace Punto a Punto (P2P)."; icon = "fa-arrows-left-right"; }const html = `
`;
showModal("Detalle de Delegación IPv6", html);
}// --- Core Calc Logic ---function calculateCapacity(rootCidr) {
const calc = (target) => {
if(rootCidr > target) return 0;
return Math.pow(2, target - rootCidr).toLocaleString();
};
document.getElementById('cap-root').textContent = `/${rootCidr}`;
document.getElementById('cap-48').textContent = calc(48);
document.getElementById('cap-56').textContent = calc(56);
document.getElementById('cap-64').textContent = calc(64);
document.getElementById('isp-capacity-card').classList.remove('hidden');
}function calculateVLSM() {
const rootInput = document.getElementById('root-network').value.trim();
const errBox = document.getElementById('error-msg');
const resArea = document.getElementById('results-area');
const tbody = document.getElementById('results-body');errBox.classList.add('hidden');
resArea.classList.add('hidden');
tbody.innerHTML = "";if (!rootInput || requestedSubnets.length === 0) {
document.getElementById('error-text').textContent = "Ingresa la red base y al menos un segmento.";
errBox.classList.remove('hidden'); return;
}try {
if (currentVersion === 'ipv4') {
// --- IPv4 Logic Fixed ---
const cidrSplit = rootInput.split('/');
if(cidrSplit.length !== 2) throw new Error("Formato inválido. Usa: x.x.x.x/máscara (Ej: 192.168.1.0/24)");let ipLong = ip2long(cidrSplit[0]);
let rootMask = parseInt(cidrSplit[1]);if (ipLong === -1) throw new Error("Dirección IP mal formada (Revisa octetos 0-255).");
if (isNaN(rootMask) || rootMask 32) throw new Error("Máscara CIDR inválida (0-32).");
const totalHosts = Math.pow(2, 32 - rootMask);
const hostBits = 32 - rootMask;
const clearMask = (0xFFFFFFFF <>> 0;
const startIp = (ipLong & clearMask) >>> 0;let currentIp = startIp;
const poolEnd = startIp + totalHosts;let sortedV4 = [...requestedSubnets].sort((a, b) => b.val - a.val);sortedV4.forEach(sub => {
let needed = sub.val + 2;
let power = 0;
while(Math.pow(2, power) poolEnd) {
throw new Error(`Espacio insuficiente en la red raíz para el segmento: ${sub.name} (Requiere ${blockSize} IPs)`);
}const netAddr = long2ip(currentIp);
const broadcast = long2ip(currentIp + blockSize - 1);
const gateway = long2ip(currentIp + 1);
const rangeStr = `${long2ip(currentIp + 1)} - ${long2ip(currentIp + blockSize - 2)}`;const tr = document.createElement('tr');
tr.className = "hover:bg-indigo-50 transition border-b border-slate-100";
tr.innerHTML = `
${sub.name} | ${netAddr}/${newMask} |
GW: ${gateway}
Rango: ${rangeStr}
Hosts útiles: ${blockSize - 2} |
|
`;
tbody.appendChild(tr);currentIp += blockSize;
});document.getElementById('res-protocol-badge').textContent = "IPV4 VLSM";
resArea.classList.remove('hidden');} else {
// --- IPv6 Logic ---
const parts = rootInput.split('/');
if(parts.length !== 2) throw new Error("Formato IPv6 inválido (addr/prefix)");
const rootAddr = parts[0];
const rootCidr = parseInt(parts[1]);
if (isNaN(rootCidr) || rootCidr > 128) throw new Error("Prefijo CIDR inválido");
calculateCapacity(rootCidr);
let currentBigInt = parseIPv6(rootAddr);
let sorted = [...requestedSubnets].sort((a, b) => a.val - b.val);
sorted.forEach(sub => {
const subPrefix = sub.val;
if (subPrefix < rootCidr) throw new Error(`El sub-segmento /${subPrefix} es más grande que la red raíz /${rootCidr}`);
const shift = BigInt(128 - subPrefix);
const increment = 1n << shift;
let netAddrStr = formatIPv6(currentBigInt);
let rangeEnd = currentBigInt + increment - 1n;
const tr = document.createElement('tr');
tr.className = "hover:bg-indigo-50 transition border-b border-slate-100";
tr.innerHTML = `
${sub.name} | ${netAddrStr}/${subPrefix} |
Start: ${netAddrStr}
End: ${formatIPv6(rangeEnd)} |
|
`;
tbody.appendChild(tr);
currentBigInt += increment;
});
document.getElementById('res-protocol-badge').textContent = "IPV6 ISP MODE";
resArea.classList.remove('hidden');
}
} catch (e) {
document.getElementById('error-text').textContent = e.message;
errBox.classList.remove('hidden');
}
}
setVersion('ipv4');