Skip to main content

refactor-nodejs-monolith

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

설치로 이동

소스 정보

저장소
Ntizar/NtizarBrainMasterMind
최근 소스 활동
2026년 6월 26일 12:05
감지된 SKILL.md 언어
스페인어
스타
2
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
6 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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
GitHub에서 보기