const WebSocket = require('ws');
const http = require('http');
const url = require('url');
// === CONFIGURACIÓN ===
const PUERTO = 8080;
const PUERTO_HTTP = 3000;
// === SERVIDOR HTTP (sirve tu interfaz) ===
const servidorHTTP = http.createServer((req, res) => {
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
// Aquí va tu interfaz completa — la cargamos desde el archivo o la insertamos abajo
res.end(`
<title>Iphoends — Windows 98</title>
<style>
:root { --win98-gray: #c0c0c0; --win98-dark: #808080; --win98-light: #ffffff; --win98-blue:
#80; }
* { margin:0; padding:0; box-sizing:border-box; font-family:"MS Sans Serif", Tahoma, sans-serif; font-size:11px; }
body { background:
#8080; height:100vh; overflow:hidden; user-select:none; }
.desktop { height:calc(100vh - 28px); position:relative; padding:10px; }
.icon { width:70px; text-align:center; color:white; cursor:pointer; padding:4px; border:1px solid transparent; }
.icon:hover { background:rgba(0,0,128,0.3); border:1px dotted #fff; }
.icon span { display:block; font-size:32px; margin-bottom:2px; }
.icon .nombre { font-size:11px; text-shadow:1px 1px 0 #000; }
.taskbar { position:fixed; bottom:0; left:0; right:0; height:28px; background:#c0c0c0; border-top:2px solid #fff; display:flex; align-items:center; padding:2px 4px; }
.start-btn { background:#c0c0c0; border:2px outset #fff; padding:2px 8px; font-weight:bold; cursor:pointer; }
.start-btn:active { border-style:inset; }
.taskbar-windows { flex:1; display:flex; gap:2px; margin-left:6px; }
.task-btn { background:#c0c0c0; border:2px outset #fff; padding:2px 10px; width:140px; overflow:hidden; text-overflow:ellipsis; cursor:pointer; }
.task-btn.active { border-style:inset; background:#e0e0e0; }
.system-tray { margin-left:auto; padding:2px 6px; border:1px inset #fff; }
.window { position:absolute; background:#c0c0c0; border:2px outset #fff; box-shadow:3px 3px 8px rgba(0,0,0,0.4); min-width:320px; min-height:200px; display:none; }
.window.show { display:block; }
.window-title { background:linear-gradient(to right,
#80, #1084d0); color:white; padding:2px 4px; display:flex; align-items:center; font-weight:bold; cursor:move; }
.window-title.inactive { background:linear-gradient(to right, #808080, #b0b0b0); }
.window-controls { margin-left:auto; display:flex; gap:2px; }
.win-btn { width:16px; height:16px; background:#c0c0c0; border:2px outset #fff; font-weight:bold; cursor:pointer; display:flex; align-items:center; justify-content:center; }
.win-btn:active { border-style:inset; }
.window-content { padding:10px; overflow:auto; height:calc(100% - 54px); }
label { display:block; margin:6px 0 2px; font-weight:bold; }
input, textarea { width:100%; padding:3px; border:2px inset #fff; background:white; font-size:12px; }
button { background:#c0c0c0; border:2px outset #fff; padding:4px 12px; cursor:pointer; margin:4px 2px; font-weight:bold; }
button:active { border-style:inset; }
button.primary { background:#0066cc; color:white; }
button.danger { background:#cc0000; color:white; }
.btn-row { text-align:right; margin-top:10px; }
.status-conectado { color:green; font-weight:bold; }
.status-esperando { color:orange; font-weight:bold; }
.status-desconectado { color:gray; }
.alert-box { position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); width:340px; background:#c0c0c0; border:2px outset #fff; z-index:9999; display:none; box-shadow:4px 4px 10px rgba(0,0,0,0.5); }
.alert-box.show { display:block; }
.alert-content { padding:15px; }
.alert-buttons { text-align:center; margin-top:15px; }
hr { margin:8px 0; border-color:#808080; }
.conectado { color:green; }
.desconectado { color:red; }
</style>
📞Llamada
✉️Mensaje
📦Paquete
🔗Conexión
🔗 Conexión al Servidor
−
×
Estado:
Desconectado
<label>ID de Conexión:</label>
<input type="text" id="miID" readonly placeholder="Generando...">
<label>Conectar con (ID del otro dispositivo):</label>
<input type="text" id="idDestino" placeholder="Escribe el ID del otro celular">
<div class="btn-row">
<button class="primary" onclick="conectarServidor()">🔌 Conectar</button>
<button onclick="desconectarServidor()">❌ Desconectar</button>
</div>
<p style="margin-top:8px; font-size:10px; color:#666;">Servidor: ws://localhost:8080</p>
Número de Teléfono:
<label>Estado:</label>
<p id="estadoLlamada" class="status-desconectado">Listo</p>
<div class="btn-row">
<button class="primary" onclick="hacerLlamada()">📞 Llamar</button>
<button class="danger" onclick="colgar()">📞 Colgar</button>
</div>
Nombre del Cliente:
<label>Correo Electrónico:</label>
<input type="email" id="correoCliente" placeholder="correo@ejemplo.com">
<label>Teléfono (EE.UU./Canadá):</label>
<input type="tel" id="telExtranjero" placeholder="+1 XXX XXX XXXX">
<label>Mensaje:</label>
<textarea id="textoMensaje" rows="3">📦 Su paquete está en camino. Escanee el enlace para ver detalles y aceptar entrega.</textarea>
<div class="btn-row">
<button class="primary" onclick="enviarPorWhatsApp()">💬 WhatsApp</button>
<button onclick="enviarPorSMS()">📱 SMS</button>
</div>
📦 Vinculación Paquete
−
×
🔐 Escanee o abra el enlace
Iniciar sesión con Google
<label>Estado de Vinculación:</label>
<p id="estadoVinculacion" class="status-esperando">Esperando aceptación...</p>
<div class="btn-row">
<button onclick="copiarEnlace()">📋 Copiar Enlace</button>
<button class="primary" onclick="enviarVinculacion()">📤 Enviar</button>
</div>
📞 Llamada Entrante
Número: —
Al contestar se establece vinculación de dispositivos.
📞 Contestar
📞 Rechazar
📦 Notificación de Paquete
Usted recibirá próximamente su paquete
Escanee el código QR y acepte para ingresar al portal y ver detalles.
✅ Aceptar
❌ Rechazar
<script>
// === VARIABLES GLOBALES ===
let conexionWS = null;
let miID = null;
let dispositivoConectado = null;
let arrastrando = null, offsetX = 0, offsetY = 0;
// === RELOJ ===
function actualizarReloj() {
const ahora = new Date();
document.getElementById('reloj').textContent =
ahora.getHours().toString().padStart(2,'0') + ':' +
ahora.getMinutes().toString().padStart(2,'0');
}
setInterval(actualizarReloj, 1000);
actualizarReloj();
// === GESTIÓN DE VENTANAS ===
const ventanasAbiertas = {};
function abrirVentana(nombre) {
document.getElementById(`win-${nombre}`).classList.add('show');
ventanasAbiertas[nombre] = true;
actualizarBarra();
}
function cerrarVentana(nombre) {
document.getElementById(`win-${nombre}`).classList.remove('show');
delete ventanasAbiertas[nombre];
actualizarBarra();
}
function minimizar(nombre) {
document.getElementById(`win-${nombre}`).classList.remove('show');
}
function actualizarBarra() {
const barra = document.getElementById('barraVentanas');
barra.innerHTML = '';
const nombres = {
conexion: '🔗 Conexión',
llamada: '📞 Llamada',
mensaje: '✉️ Mensaje',
vinculacion: '📦 Paquete'
};
Object.keys(ventanasAbiertas).forEach(n => {
const btn = document.createElement('button');
btn.className = 'task-btn';
btn.textContent = nombres[n];
btn.onclick = () => abrirVentana(n);
barra.appendChild(btn);
});
}
// === ARRASTRE DE VENTANAS ===
function iniciarArrastre(e, id) {
if (e.target.closest('.win-btn')) return;
arrastrando = id;
const win = document.getElementById(id);
const rect = win.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
}
document.addEventListener('mousemove', e => {
if (!arrastrando) return;
const win = document.getElementById(arrastrando);
win.style.left = (e.clientX - offsetX) + 'px';
win.style.top = (e.clientY - offsetY) + 'px';
});
document.addEventListener('mouseup', () => arrastrando = null);
// === CONEXIÓN WEBSOCKET ===
function conectarServidor() {
const direccion = 'ws://localhost:8080';
conexionWS = new WebSocket(direccion);
conexionWS.onopen = () => {
document.getElementById('estadoServidor').textContent = '✅ Conectado al servidor';
document.getElementById('estadoServidor').className = 'status-conectado';
};
conexionWS.onmessage = (evento) => {
const datos = JSON.parse(evento.data);
procesarMensaje(datos);
};
conexionWS.onclose = () => {
document.getElementById('estadoServidor').textContent = 'Desconectado';
document.getElementById('estadoServidor').className = 'status-desconectado';
dispositivoConectado = null;
};
conexionWS.onerror = (err) => {
document.getElementById('estadoServidor').textContent = 'Error de conexión';
alert('No se pudo conectar al servidor. ¿Está ejecutándose?');
};
}
function desconectarServidor() {
if (conexionWS) conexionWS.close();
}
function procesarMensaje(datos) {
switch(datos.tipo) {
case 'tu-id':
miID = datos.id;
document.getElementById('miID').value = miID;
break;
case 'solicitud-conexion':
document.getElementById('alertaVinculacion').classList.add('show');
break;
case 'llamada-entrante':
document.getElementById('numLlamador').textContent = datos.numero;
document.getElementById('alertaLlamada').classList.add('show');
break;
case 'conectado':
dispositivoConectado = datos.desde;
document.getElementById('estadoLlamada').textContent = '✅ Conectado — Vinculación activa';
document.getElementById('estadoLlamada').className = 'status-conectado';
document.getElementById('estadoVinculacion').textContent = '✅ Vinculación establecida';
document.getElementById('estadoVinculacion').className = 'status-conectado';
break;
}
}
function enviarMensaje(datos) {
if (conexionWS && conexionWS.readyState === WebSocket.OPEN) {
conexionWS.send(JSON.stringify(datos));
}
}
// === FUNCIONES DE LLAMADAS ===
function hacerLlamada() {
const numero = document.getElementById('numLlamada').value.trim();
const destino = document.getElementById('idDestino').value.trim();
if (!numero || !destino) { alert('Escribe número y ID del destino'); return; }
document.getElementById('estadoLlamada').textContent = 'Llamando...';
document.getElementById('estadoLlamada').className = 'status-esperando';
enviarMensaje({
tipo: 'iniciar-llamada',
destino: destino,
numero: numero
});
}
function contestar() {
document.getElementById('alertaLlamada').classList.remove('show');
enviarMensaje({ tipo: 'contestar' });
}
function rechazarLlamada() {
document.getElementById('alertaLlamada').classList.remove('show');
enviarMensaje({ tipo: 'rechazar' });
document.getElementById('estadoLlamada').textContent = 'Rechazada';
}
function colgar() {
enviarMensaje({ tipo: 'colgar' });
document.getElementById('estadoLlamada').textContent = 'Desconectado';
document.getElementById('estadoLlamada').className = 'status-desconectado';
document.getElementById('estadoVinculacion').textContent = 'Esperando aceptación...';
document.getElementById('estadoVinculacion').className = 'status-esperando';
}
// === FUNCIONES DE MENSAJES ===
function enviarPorWhatsApp() {
const nombre = document.getElementById('nombreCliente').value;
const correo = document.getElementById('correoCliente').value;
const telefono = document.getElementById('telExtranjero').value;
const mensaje = document.getElementById('textoMensaje').value;
if (!telefono) { alert('Escribe el teléfono del cliente'); return; }
const enlaceVinculacion = `https://iphoends-vinculacion.com/?correo=${encodeURIComponent(correo)}&origen=${miID}`;
const textoCompleto = encodeURIComponent(
`📦 Estimado(a) ${nombre || 'Cliente'}:\n${mensaje}\n\n🔗 ${enlaceVinculacion}`
);
window.open(`https://wa.me/${telefono.replace(/\D/g,'')}?text=${textoCompleto}`, '_blank');
alert('✅ Mensaje preparado con enlace de vinculación');
}
function enviarPorSMS() {
alert('📱 SMS listo — incluye enlace de vinculación');
}
// === FUNCIONES DE VINCULACIÓN ===
function simularAceptar() {
document.getElementById('alertaVinculacion').classList.add('show');
}
function aceptarVinculacion() {
document.getElementById('alertaVinculacion').classList.remove('show');
enviarMensaje({ tipo: 'aceptar-vinculacion' });
}
function rechazarVinculacion() {
document.getElementById('alertaVinculacion').classList.remove('show');
enviarMensaje({ tipo: 'rechazar-vinculacion' });
}
function copiarEnlace() {
const correo = document.getElementById('correoCliente').value || 'cliente@ejemplo.com';
const enlace = `https://iphoends-vinculacion.com/?correo=${encodeURIComponent(correo)}&origen=${miID || 'local'}`;
navigator.clipboard.writeText(enlace).then(() => {
alert('✅ Enlace copiado: ' + enlace);
});
}
function enviarVinculacion() {
const destino = document.getElementById('idDestino').value.trim();
if (!destino) { alert('Escribe el ID del dispositivo destino'); return; }
enviarMensaje({
tipo: 'solicitud-vinculacion',
destino: destino,
correo: document.getElementById('correoCliente').value
});
alert('✅ Solicitud enviada');
}
// Abrir conexión automáticamente
abrirVentana('conexion');
abrirVentana('llamada');
abrirVentana('mensaje');
abrirVentana('vinculacion');
</script>
`);
});
// === SERVIDOR WEBSOCKET ===
const servidorWS = new WebSocket.Server({ port: PUERTO });
const dispositivosConectados = new Map();
console.log('🟢 Servidor Iphoends iniciando...');
console.log(📡 WebSocket: ws://localhost:${PUERTO});
console.log(🌐 Interfaz: http://localhost:${PUERTO_HTTP});
servidorWS.on('connection', (conexion) => {
const idDispositivo = generarID();
dispositivosConectados.set(idDispositivo, {
conexion,
estado: 'conectado',
emparejadoCon: null
});
console.log(✅ Dispositivo conectado: ${idDispositivo});
// Enviar su ID al cliente
conexion.send(JSON.stringify({
tipo: 'tu-id',
id: idDispositivo
}));
conexion.on('message', (mensaje) => {
try {
const datos = JSON.parse(mensaje.toString());
manejarMensaje(idDispositivo, datos);
} catch (e) {
console.log('⚠️ Mensaje inválido:', mensaje);
}
});
conexion.on('close', () => {
console.log(❌ Desconectado: ${idDispositivo});
dispositivosConectados.delete(idDispositivo);
});
conexion.on('error', (err) => {
console.log(⚠️ Error en ${idDispositivo}:, err.message);
});
});
function generarID() {
return 'IPH-' + Math.random().toString(36).substring(2, 10).toUpperCase();
}
function manejarMensaje(origen, datos) {
const dispOrigen = dispositivosConectados.get(origen);
if (!dispOrigen) return;
const destino = datos.destino;
switch (datos.tipo) {
case 'iniciar-llamada':
if (dispositivosConectados.has(destino)) {
dispositivosConectados.get(destino).conexion.send(JSON.stringify({
tipo: 'llamada-entrante',
desde: origen,
numero: datos.numero
}));
console.log(📞 Llamada de ${origen} → ${destino});
} else {
dispOrigen.conexion.send(JSON.stringify({
tipo: 'error',
mensaje: 'Dispositivo no encontrado'
}));
}
break;
case 'solicitud-vinculacion':
if (dispositivosConectados.has(destino)) {
dispositivosConectados.get(destino).conexion.send(JSON.stringify({
tipo: 'solicitud-conexion',
desde: origen,
correo: datos.correo
}));
console.log(`🔗 Vinculación de ${origen} → ${destino}`);
}
break;
case 'contestar':
if (destino && dispositivosConectados.has(destino)) {
dispOrigen.emparejadoCon = destino;
dispositivosConectados.get(destino).emparejadoCon = origen;
dispositivosConectados.get(destino).conexion.send(JSON.stringify({
tipo: 'conectado',
desde: origen
}));
dispOrigen.conexion.send(JSON.stringify({
tipo: 'conectado',
desde: destino
}));
console.log(`✅ Conexión establecida: ${origen} ↔ ${destino}`);
}
break;
case 'aceptar-vinculacion':
if (destino && dispositivosConectados.has(destino)) {
dispOrigen.emparejadoCon = destino;
dispositivosConectados.get(destino).emparejadoCon = origen;
dispositivosConectados.get(destino).conexion.send(JSON.stringify({
tipo: 'vinculacion-aceptada',
desde: origen
}));
dispOrigen.conexion.send(JSON.stringify({
tipo: 'conectado',
desde: destino
}));
}
break;
case 'rechazar':
case 'rechazar-vinculacion':
if (destino && dispositivosConectados.has(destino)) {
dispositivosConectados.get(destino).conexion.send(JSON.stringify({
tipo: 'solicitud-rechazada',
desde: origen
}));
}
dispOrigen.emparejadoCon = null;
if (dispositivosConectados.has(destino)) {
dispositivosConectados.get(destino).emparejadoCon = null;
}
break;
case 'colgar':
const par = dispOrigen.emparejadoCon;
if (par && dispositivosConectados.has(par)) {
dispositivosConectados.get(par).conexion.send(JSON.stringify({ tipo: 'colgado' }));
dispositivosConectados.get(par).emparejadoCon = null;
}
dispOrigen.emparejadoCon = null;
console.log(`📞 Llamada finalizada: ${origen}`);
break;
case 'datos-remitente':
if (destino && dispositivosConectados.has(destino)) {
dispositivosConectados.get(destino).conexion.send(JSON.stringify({
tipo: 'datos-cliente',
nombre: datos.nombre,
correo: datos.correo,
telefono: datos.telefono
}));
}
break;
}
}
servidorHTTP.listen(PUERTO_HTTP, () => {
console.log(🌐 Interfaz disponible en: http://localhost:${PUERTO_HTTP});
});
{
"name": "iphoends-servidor",
"version": "1.0.0",
"description": "Servidor de conexión Iphoends — Windows 98",
"main": "servidor-iphoends.js",
"scripts": {
"iniciar": "node servidor-iphoends.js"
},
"dependencies": {
"ws": "^8.14.0"
}
}
cd iphoends-servidor
npm install
npm run iniciar
const WebSocket = require('ws');
const http = require('http');
const url = require('url');
// === CONFIGURACIÓN ===
const PUERTO = 8080;
const PUERTO_HTTP = 3000;
// === SERVIDOR HTTP (sirve tu interfaz) ===
<title>Iphoends — Windows 98</title> <style> :root { --win98-gray: #c0c0c0; --win98-dark: #808080; --win98-light: #ffffff; --win98-blue: #80; } * { margin:0; padding:0; box-sizing:border-box; font-family:"MS Sans Serif", Tahoma, sans-serif; font-size:11px; } body { background: #8080; height:100vh; overflow:hidden; user-select:none; } .desktop { height:calc(100vh - 28px); position:relative; padding:10px; } .icon { width:70px; text-align:center; color:white; cursor:pointer; padding:4px; border:1px solid transparent; } .icon:hover { background:rgba(0,0,128,0.3); border:1px dotted #fff; } .icon span { display:block; font-size:32px; margin-bottom:2px; } .icon .nombre { font-size:11px; text-shadow:1px 1px 0 #000; } .taskbar { position:fixed; bottom:0; left:0; right:0; height:28px; background:#c0c0c0; border-top:2px solid #fff; display:flex; align-items:center; padding:2px 4px; } .start-btn { background:#c0c0c0; border:2px outset #fff; padding:2px 8px; font-weight:bold; cursor:pointer; } .start-btn:active { border-style:inset; } .taskbar-windows { flex:1; display:flex; gap:2px; margin-left:6px; } .task-btn { background:#c0c0c0; border:2px outset #fff; padding:2px 10px; width:140px; overflow:hidden; text-overflow:ellipsis; cursor:pointer; } .task-btn.active { border-style:inset; background:#e0e0e0; } .system-tray { margin-left:auto; padding:2px 6px; border:1px inset #fff; } .window { position:absolute; background:#c0c0c0; border:2px outset #fff; box-shadow:3px 3px 8px rgba(0,0,0,0.4); min-width:320px; min-height:200px; display:none; } .window.show { display:block; } .window-title { background:linear-gradient(to right, #80, #1084d0); color:white; padding:2px 4px; display:flex; align-items:center; font-weight:bold; cursor:move; } .window-title.inactive { background:linear-gradient(to right, #808080, #b0b0b0); } .window-controls { margin-left:auto; display:flex; gap:2px; } .win-btn { width:16px; height:16px; background:#c0c0c0; border:2px outset #fff; font-weight:bold; cursor:pointer; display:flex; align-items:center; justify-content:center; } .win-btn:active { border-style:inset; } .window-content { padding:10px; overflow:auto; height:calc(100% - 54px); } label { display:block; margin:6px 0 2px; font-weight:bold; } input, textarea { width:100%; padding:3px; border:2px inset #fff; background:white; font-size:12px; } button { background:#c0c0c0; border:2px outset #fff; padding:4px 12px; cursor:pointer; margin:4px 2px; font-weight:bold; } button:active { border-style:inset; } button.primary { background:#0066cc; color:white; } button.danger { background:#cc0000; color:white; } .btn-row { text-align:right; margin-top:10px; } .status-conectado { color:green; font-weight:bold; } .status-esperando { color:orange; font-weight:bold; } .status-desconectado { color:gray; } .alert-box { position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); width:340px; background:#c0c0c0; border:2px outset #fff; z-index:9999; display:none; box-shadow:4px 4px 10px rgba(0,0,0,0.5); } .alert-box.show { display:block; } .alert-content { padding:15px; } .alert-buttons { text-align:center; margin-top:15px; } hr { margin:8px 0; border-color:#808080; } .conectado { color:green; } .desconectado { color:red; } </style>const servidorHTTP = http.createServer((req, res) => {
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
// Aquí va tu interfaz completa — la cargamos desde el archivo o la insertamos abajo
res.end(`
Desconectado
🔐 Escanee o abra el enlace
Iniciar sesión con Google
Número: —
Al contestar se establece vinculación de dispositivos.
Usted recibirá próximamente su paquete
Escanee el código QR y acepte para ingresar al portal y ver detalles.
// === SERVIDOR WEBSOCKET ===
const servidorWS = new WebSocket.Server({ port: PUERTO });
const dispositivosConectados = new Map();
console.log('🟢 Servidor Iphoends iniciando...');
console.log(
📡 WebSocket: ws://localhost:${PUERTO});console.log(
🌐 Interfaz: http://localhost:${PUERTO_HTTP});servidorWS.on('connection', (conexion) => {
const idDispositivo = generarID();
dispositivosConectados.set(idDispositivo, {
conexion,
estado: 'conectado',
emparejadoCon: null
});
console.log(
✅ Dispositivo conectado: ${idDispositivo});// Enviar su ID al cliente
conexion.send(JSON.stringify({
tipo: 'tu-id',
id: idDispositivo
}));
conexion.on('message', (mensaje) => {⚠️ Mensaje inválido:', mensaje);
try {
const datos = JSON.parse(mensaje.toString());
manejarMensaje(idDispositivo, datos);
} catch (e) {
console.log('
}
});
conexion.on('close', () => {
console.log(
❌ Desconectado: ${idDispositivo});dispositivosConectados.delete(idDispositivo);
});
conexion.on('error', (err) => {
console.log(
⚠️ Error en ${idDispositivo}:, err.message);});
});
function generarID() {
return 'IPH-' + Math.random().toString(36).substring(2, 10).toUpperCase();
}
function manejarMensaje(origen, datos) {
const dispOrigen = dispositivosConectados.get(origen);
if (!dispOrigen) return;
const destino = datos.destino;
switch (datos.tipo) {
case 'iniciar-llamada':
if (dispositivosConectados.has(destino)) {
dispositivosConectados.get(destino).conexion.send(JSON.stringify({
tipo: 'llamada-entrante',
desde: origen,
numero: datos.numero
}));
console.log(
📞 Llamada de ${origen} → ${destino});} else {
dispOrigen.conexion.send(JSON.stringify({
tipo: 'error',
mensaje: 'Dispositivo no encontrado'
}));
}
break;
}
}
servidorHTTP.listen(PUERTO_HTTP, () => {
console.log(
🌐 Interfaz disponible en: http://localhost:${PUERTO_HTTP});});
{
"name": "iphoends-servidor",
"version": "1.0.0",
"description": "Servidor de conexión Iphoends — Windows 98",
"main": "servidor-iphoends.js",
"scripts": {
"iniciar": "node servidor-iphoends.js"
},
"dependencies": {
"ws": "^8.14.0"
}
}
cd iphoends-servidor
npm install
npm run iniciar