La Decisión: ¿Por Qué Sin Frameworks?
No fue por ideología anti-React. Fue por una razón práctica: quería demostrar que entiendo la tecnología base, no solo que sé instalar npm packages. Cualquiera puede hacer npx create-react-app. Pero construir un sistema de componentes, gestión de estado, lazy loading y i18n desde cero demuestra algo distinto.
Además, para un portfolio estático desplegado en Cloudflare Pages, un framework añade complejidad innecesaria: build steps, dependencias de node_modules, tiempo de bundling. Mi sitio carga en menos de 2 segundos porque no tiene que ejecutar un Virtual DOM — es HTML directo al navegador.
El Mapa de los 23.000 Líneas
| Archivo | Líneas | Función |
|---|---|---|
solar-system-pro.js | 8.087 | Simulador astronómico Three.js + Launch Library 2 API |
crm-studio-pro.js | 2.850 | CRM con pipeline y analítica |
weather-pro.js | 1.950 | Dashboard meteorológico |
neural-bricks-3d.js | 1.800 | Motor voxel Three.js |
gadget-popups.css | 1.685 | Sistema expandir gadgets |
sentiment-analyzer-pro.js | 1.600 | NLP con scoring |
pricing-simulator-pro.js | 1.400 | Pricing dinámico ML |
ocr-scanner-pro.js | 1.350 | OCR con Tesseract.js |
script.js | 1.315 | Core + boot screen |
email-pro.js | 1.100 | Reescritura profesional |
styles.css | 2.007 | Estilos principales |
| Otros (i18n, info, popups) | ~1.800 | Infraestructura |
Patrón Compartido: El "Gadget Expand"
Todos los gadgets siguen el mismo patrón: una tarjeta compacta en la grid del portfolio (card mode) y un modo laboratorio expandido (expand mode) que ocupa pantalla completa. El sistema de expand es un overlay gestionado por gadget-popups.js:
function openGadgetExpand(gadgetId) {
const backdrop = document.getElementById('gadget-expand-backdrop');
const content = document.getElementById('gadget-expand-content');
// Lazy init: solo crea el DOM del gadget cuando se abre
if (!content.dataset.initialized) {
content.innerHTML = buildGadgetHTML(gadgetId);
initGadgetLogic(gadgetId);
content.dataset.initialized = gadgetId;
}
backdrop.classList.add('active');
document.body.style.overflow = 'hidden';
// Trap focus dentro del modal para accesibilidad
trapFocus(content);
}
El lazy init es clave: los gadgets pesados (Solar System, Neural Bricks) no crean sus escenas Three.js hasta que el usuario los abre. Esto significa que la página principal carga rápido aunque tenga 9 gadgets registrados.
Gadget por Gadget: Decisiones Técnicas
1. OCR Scanner Pro — Tesseract.js
El escáner usa Tesseract.js v4 para extracción de texto real en el navegador. El reto principal es el rendimiento: OCR en WebAssembly tarda 3-8 segundos según la imagen. La solución fue un pipeline con feedback visual:
async function processImage(imageData) {
updateProgress('Inicializando Tesseract...', 10);
const worker = await Tesseract.createWorker('spa+eng');
updateProgress('Analizando imagen...', 30);
const { data } = await worker.recognize(imageData, {
logger: m => {
if (m.status === 'recognizing text') {
updateProgress('Reconociendo texto...', 30 + m.progress * 60);
}
}
});
updateProgress('Procesando resultados...', 95);
displayResults(data);
await worker.terminate();
}
2. Sentiment Analyzer — NLP Client-Side
El analizador de sentimientos funciona sin API externa. Usa un diccionario de ~2.000 palabras con scores de sentimiento (positivo/negativo/neutro) más heurísticas para detectar negaciones, intensificadores y emojis. No es GPT-4, pero demuestra el concepto de NLP aplicado.
3. Dynamic Pricing — Algoritmo Multi-Factor
El simulador de pricing calcula precios óptimos combinando 4 factores (demanda, competencia, estacionalidad, stock) con pesos configurables. Incluye un modo avanzado con curvas de sensibilidad y análisis what-if:
function calculateOptimalPrice(base, factors) {
const demandMultiplier = 1 + (factors.demand - 50) / 100 * 0.8;
const competitionFactor = 1 - (factors.competition - 50) / 100 * 0.3;
const seasonBoost = 1 + Math.sin(factors.season * Math.PI / 100) * 0.4;
const stockPressure = 1 + (100 - factors.stock) / 100 * 0.5;
return base * demandMultiplier * competitionFactor
* seasonBoost * stockPressure;
}
4. Weather Dashboard — Open-Meteo API
El widget meteorológico consume la API gratuita de Open-Meteo (sin API key). Primero geocodifica la ciudad, luego obtiene forecast de 7 días. El modo expandido muestra gráficos de temperatura, precipitación y viento construidos con Canvas puro — sin Chart.js ni D3.
5. CRM Studio Pro — Estado Complejo sin Redux
Este es el gadget más parecido a una "app real": pipeline Kanban con drag & drop, formularios de contacto, analítica con gráficos, y export a CSV/JSON. Todo el estado vive en un objeto JavaScript en memoria con persistencia opcional en localStorage.
6-7. Neural Bricks + Solar System — Three.js
Los dos gadgets 3D usan Three.js r128 pero con enfoques distintos. Neural Bricks es un motor de voxels con 8 modelos procedurales y animación frame-by-frame. Solar System es una simulación científica con datos NASA reales. Tengo un análisis técnico completo del Solar System aquí.
8. Before/After Slider — Canvas + CSS Filters
El comparador visual parece simple pero tiene profundidad: modo lente circular, crossfade, 12 filtros CSS en tiempo real, modo fullscreen, y exportación de capturas. Todo con Canvas API y manipulación de píxeles.
El Sistema de i18n
El portfolio es bilingüe (ES/EN) con un sistema propio de internacionalización. 195 claves de traducción gestionadas con atributos data-i18n en el HTML:
// i18n.js — 324 líneas
const I18N = {
dict: {
'nav.profile': { es: 'Perfil', en: 'Profile' },
'nav.services': { es: 'Servicios', en: 'Services' },
// ... 193 más
},
_apply() {
document.querySelectorAll('[data-i18n]').forEach(el => {
const val = this.t(el.getAttribute('data-i18n'));
val.includes('<') ? el.innerHTML = val : el.textContent = val;
});
// También actualiza placeholders, aria-labels, <html lang>
}
};
Boot Screen: La Primera Impresión
La pantalla de inicio simula un terminal Linux con matrix rain, carga ASCII art del logo, y hace un typewriter de líneas de "sistema". Es puro teatro, pero cumple una función real: precargar recursos mientras el usuario está entretenido. Al terminar la secuencia, las fuentes, imágenes y scripts ya están en caché.
Lo Que Haría Diferente
Con 23K líneas de vanilla JS, hay cosas que un framework haría mejor:
- TypeScript — 6.000 líneas sin tipos es arriesgado. Los bugs por typos en nombres de propiedades son difíciles de detectar
- CSS-in-JS o CSS Modules — Con 3.600 líneas de CSS, los conflictos de nombres son un riesgo real
- Testing — No hay tests automatizados. Todo se probó manualmente. Para producción real, necesitaría al menos tests de integración
- Componentización — El patrón gadget-expand funciona, pero con Web Components nativos sería más limpio
Pero para un portfolio cuyo objetivo es demostrar capacidad técnica, vanilla JS fue la decisión correcta. Muestra que entiendo qué hacen los frameworks por debajo, y que puedo construir sin ellos.
El mejor framework para un portfolio es ninguno. Demuestra que sabes programar, no que sabes instalar dependencias.
Todos los gadgets están en cristiancorrales.com. Abre cualquiera y pulsa "LABORATORIO" para ver el modo completo.
¿Tu web no trae clientes?
Diseño webs que cargan rápido y convierten, para negocios de Tarragona y alrededores. Precios claros desde el primer día.
Ver el servicio de diseño web →¿Quieres una cifra orientativa ya? Configura tu web y ve el precio al instante →
¿Quieres ver casos reales de IA en pymes?
Te escribo solo cuando tengo un caso con costes y resultados reales, o una herramienta que merece la pena. Sin humo, sin spam y baja en un clic.



