Skip to main content

refactor-nodejs-monolith

Refactorizar proyecto Node.js monolítico a arquitectura modular por dominios

Zur Installation springen

Quellinformationen

Repository
Ntizar/NtizarBrainMasterMind
Letzte Quellaktivität
26. Juni 2026 um 12:05
Erkannte Sprache von SKILL.md
Spanisch
Sterne
2
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
6 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
refactor-nodejs-monolith
version
1.0.0
description
Refactorizar proyecto Node.js monolítico a arquitectura modular por dominios
tags
["software-development","refactor","nodejs","architecture"]
# Refactorización de proyecto Node.js monolítico a arquitectura modular ## Cuándo aplicar Cuando un proyecto Node.js/Express tiene un `server.js` >1000 líneas con lógica mezclada (config, clientes HTTP, dominio, rutas, persistencia). ## Pasos ### 1. Crear estructura de directorios ``` src/ config/ → validación de entorno shared/ → utilidades compartidas time/ → utilidades de fecha/hora io/ → repositorios de datos infra/ → clientes externos + cache clients/ → HTTP clients cache/ → cache strategies domains/ → dominios de negocio <domain>/ → service.js, schemas.js, mapper.js jobs/ → trabajos programados ``` ### 2. Extraer configuración - Crear `src/config/env.js` con validación estricta de variables obligatorias - Crear `.env.example` documentado - Fail-fast al arranque si faltan vars obligatorias ### 3. Extraer utilidades compartidas - Centralizar lógica de tiempo/fecha en `src/shared/time/` - Centralizar parseo de datos en `src/shared/` - Reemplazar `fs.sync` por `fs/promises` ### 4. Extraer clientes HTTP - `src/infra/clients/<name>.client.js` con reintentos, backoff exponencial, jitter - Sin lógica de negocio en clientes ### 5. Extraer dominios - `src/domains/<domain>/` con service + schemas + mapper - Cada dominio con interfaz clara y testeable ### 6. Reescribir server.js - Solo bootstrap Express + routing - Importar servicios de dominios - Eliminar autoconsumo HTTP interno (llamar servicios directamente) - Añadir helmet + CORS whitelist + metrics endpoint ### 7. Actualizar Dockerfile - Multi-stage build - Usuario no-root - HEALTHCHECK - Copiar solo artefactos necesarios ### 8. Tests - Jest con `testEnvironment: node` (NO vitest para proyectos CommonJS) - `@jest/globals` para `describe/it/expect` con require() - `--forceExit --detectOpenHandles` para tests de integración con supertest - `delete require.cache[require.resolve(...)]` para recargar módulos con env variables diferentes - Tests de integración: `supertest` + `require('../server')` — el server se queda abierto, `--forceExit` lo maneja - Tests de env: guardar/restaurar `process.env` original, limpiar cache, recargar módulo ### 9. Verificar - Misma respuesta funcional en todos los endpoints - Tests de integración con mocks - `git diff server.js` muestra reducción significativa ## Pitfalls ### Backend - **Módulos ESM vs CommonJS**: Si el proyecto usa `require()`, NO usar `export` en archivos nuevos. Todo debe ser `module.exports`. - **Rutas relativas**: `require('../../shared/time/madrid')` depende de la ubicación del archivo que importa, no del CWD. Verificar cada import. - **Variables de entorno**: El archivo `.env` NO se hace commit. `.env.example` SÍ. - **Autoconsumo HTTP**: Si un endpoint llama a otro endpoint del mismo servidor, reemplazar por llamada directa al servicio. - **Vitest vs Jest**: Vitest no funciona con `require()` — usa `import`. Para proyectos CommonJS existentes, usar Jest con `@jest/globals`. - **Locale en tests**: `toLocaleString('es-ES')` puede no incluir separador de miles en entornos con locale por defecto. Tests deben aceptar ambas formas. - **Server abierto en tests**: Importar `server.js` en supertest deja TCP abierto. Usar `--forceExit --detectOpenHandles` en Jest. ### CSP con helmet - **Google Fonts**: Necesita `styleSrc` + `styleSrcElem` con `https://fonts.googleapis.com` Y `fontSrc` con `https://fonts.gstatic.com`. Solo `styleSrc` no basta. - **Sourcemaps CDN**: Necesita `connectSrc` explícito para `https://cdn.jsdelivr.net` o los sourcemaps se bloquean. - **scriptSrcAttr**: Cuidado con comillas — `'unsafe-inline'` debe tener comilla simple de cierre. `'unsafe-inline"` (falta ') causa error silencioso de CSP. - **scriptSrcElem explícito**: No confiar en el fallback a `scriptSrc`. Definir `scriptSrcElem` explícitamente. - **Verificar CSP generado**: Tras cambiar helmet, hacer `curl -s -D- http://localhost:4000/ | grep Content-Security-Policy` para ver la directiva resultante. Cada recurso externo necesita su directiva específica (styleSrc para CSS, fontSrc para fuentes, connectSrc para fetch/XHR, scriptSrcAttr para atributos inline). ### Frontend modular (extraer de index.html monolítico) - **Variable scoping en módulos**: Al extraer JS de un `<script>` único a módulos separados, verificar NUNCA que variables globales como `tableSort` sean en realidad propiedades de un objeto (`AppState.tableSort`). Buscar TODAS las referencias con `grep -rn 'variableName' public/js/`. - **Duplicación accidental**: Al extraer secciones, verificar que no se haya duplicado la misma constante en dos archivos (`const tableColumns` en render.js Y render-final.js causa SyntaxError). - **Orden de carga en HTML**: Los `<script>` tags deben cargarse en orden de dependencia: config → state → utils → api → ui → data → render → render-charts → render-final. Si un módulo usa algo de otro, debe venir DESPUÉS. - **`DOMContentLoaded` en módulo final**: Solo el último módulo (init) debe tener `document.addEventListener('DOMContentLoaded', ...)`. Los otros módulos solo definen funciones/constantes. - **Verificación post-extracción**: Tras reescribir el HTML limpio, verificar en consola del browser: (1) no hay `ReferenceError`, (2) cada `<script src="...">` devuelve 200, (3) `window.AppState` existe antes de cargar datos. - **⚠️ CRÍTICO: al reescribir utils.js, verificar TODAS las funciones que usan los renderers**. Las funciones `formatNum`, `numericValues`, `average`, `fmtHora`, `hourlySeries`, `priceColor` son llamadas por `render.js`. Si se eliminan al reescribir utils.js, TODOS los renderers fallan con `ReferenceError` → dashboard en blanco. Antes de hacer commit de un rewrite de utils.js: `grep -rn 'function formatNum\|function numericValues\|function average\|function fmtHora\|function hourlySeries\|function priceColor' public/js/utils.js` — si alguno no aparece, está roto. - **⚠️ Funciones de timezone deben existir en frontend Y backend**. `getMadridHour()` se definía en backend (`src/shared/time/madrid.js`) pero los renderers frontend la llamaban directamente. Al modularizar frontend, verificar que TODAS las funciones de timezone estén definidas en `utils.js` frontend, no solo en backend. - **⚠️ Funciones helper NO deben ser llamadas antes de definirse**. `activeTechKeys()` era llamada por `renderGeneracionMix()` pero no existía en `utils.js`. Siempre verificar que cada función llamada por renderers esté definida en algún módulo cargado antes del renderer. ## Resultado esperado - server.js reducido 40-60% - Código separable por dominios - Sin secretos en código - Headers de seguridad (helmet) - CORS por lista blanca - I/O asíncrona - Cache con métricas - Docker hardening - Tests: Jest con `testEnvironment: node` (NO vitest para proyectos CommonJS) ## Frontend modular - Extraer CSS/JS/HTML de monolitos >1500 líneas en módulos separados - Orden de carga crítico: config → state → utils → api → ui → data → render → init - Verificar scoping: `AppState.tableSort` NO `tableSort` - Verificar duplicados de `const` entre módulos - **Cache-busting OBLIGATORIO**: añadir `?v=TIMESTAMP` a todos los `<script src>` en HTML. Sin esto, el navegador sirve JS antiguo y da ReferenceError. - **Cache-busting dinámico preferible**: en lugar de `?v=202605260830` estático, usar un script inline que inyecte `Date.now()` al cargar la página: ```html <script> (function(){var ts=Date.now();document.querySelectorAll('script[src^="js/"]').forEach(function(s){var src=s.src;if(src.indexOf('?')===-1)src+='?v='+ts;else src=src.replace(/v=\d+/,'v='+ts);s.src=src;});})(); </script> <script src="js/config.js"></script> ``` Esto garantiza que cada recarga de página descargue los JS más recientes, incluso con cache del navegador. - **No-cache en servidor**: aplicar `Cache-Control: no-cache, no-store, must-revalidate` a `.js` en middleware Express. El navegador NUNCA debe cacheart JS de dashboard. - **No bloquear render por datos null**: el check `hasRealData = v.some(d => d.precio !== null)` es demasido agresivo. Si los datos vienen null, renderizar igual — los componentes muestran "—" para valores null. Bloquear el render completo por datos null es un error: el usuario ve pantalla en blanco en lugar de un dashboard con campos vacíos. - **⚠️ CRÍTICO: CDN dependencies se pierden al extraer JS inline a archivo separado** — Cuando el HTML tiene `<script>` inline que usa librerías externas (Chart.js, Three.js, Plotly, etc.), el patrón es: `<head>` carga CDN → `<script>` inline usa la librería. Al extraer el inline a `<script src="dashboard.js">`, se olvidan los CDN del `<head>`. El JS externo carga pero falla silenciosamente (`new Chart()` → `ReferenceError` → `.catch()` muestra "Error cargando datos"). **VERIFICACIÓN OBLIGATORIA post-extracción:** 1. `grep -oP 'new\s+(Chart|THREE|Plotly)\b' dashboard.js` — detectar constructores usados 2. `grep 'cdn.jsdelivr\|unpkg\|cdnjs' dashboard.html` — verificar que los CDN correspondientes siguen en `<head>` 3. Si falta algún CDN, añadirlo ANTES de hacer push - **Patrón de verificación universal:** `grep -oP '\bnew\s+([A-Z]\w+)\b' archivo.js` → lista de constructores → verificar que cada uno tiene su CDN/script tag en el HTML - **Ver referencia**: `references/frontend-module-extraction.md` ## Fecha y prediccion (bug 502) - **NUNCA hacer fetch de prediccion para dia futuro** — ESIOS no tiene datos → 502 - Solo pedir `/api/esios/prediccion` si fecha=hoy o ayer (el dia siguiente tiene datos) - Funciones clave: `shouldFetchPrediccion(fecha)`, `isFutureDate(fechaStr)`, `clampDate(fechaStr)` - Input date HTML: `max` dinamico = hoy (set en `loadState()`), `min` = hoy-365 dias - Flechas teclado: clampar a [min,max] con toast informativo - **Validación frontend + backend**: el frontend bloquea selección de fechas futuras, el backend no necesita validación extra si el frontend ya filtra - Ver referencia: `references/fecha-prediccion-502-fix.md` ## Referencias - `references/frontend-module-extraction.md` — patrón detallado para extraer frontend modular, orden de dependencias, reglas de escoping, bug conocido 2026-05-26 - `references/frontend-debug-reference-errors.md` — diagnóstico de ReferenceError en cascada al reescribir utils.js (formatNum, numericValues, average, fmtHora, hourlySeries, priceColor, activeTechKeys, getMadridHour) - `references/csp-helmet-fix.md` — configuración completa de CSP con helmet: Google Fonts, CDN, sourcemaps, pitfalls de comillas - `references/nan-spaces-deploy.md` — patrón de deploy a NaN Spaces, limitaciones (sin CLI), proceso manual, pitfall de verificación - `references/fecha-prediccion-502-fix.md` — fix para 502 en /api/esios/prediccion: validación de fechas, conditional fetch, clampDate, input date max/min
Auf GitHub ansehen