Volver a la wiki

Servicios Centralizados Frontend

Servicios Centralizados Frontend

IMPORTANTE: Antes de crear un nuevo servicio/utility, consulta este documento. Los servicios centralizados evitan duplicación de código y facilitan el mantenimiento.

Última actualización: 02-04-2026 Versión: v1.0.8


Ubicación de Servicios

static/js/
├── editor/                 # Utilidades de editor (compartidas globalmente)
│   └── modal_helper.js   # ✅ Modal open/close/toggle centralizado
├── services/               # Servicios principales (API, logging, notificaciones)
│   ├── ApiService.js      # ✅ HTTP requests con CSRF
│   ├── EChartsService.js  # ✅ Factory para gráficas Apache ECharts
│   ├── ChartGuideService.js # ✅ Modal centralizado de guía de interpretación de gráficas
│   ├── ScopedInsightTab.js # ✅ Reusable CNS (AI Insights) tab — scoped by targetIds (Observatory, Wireless, UPS)
│   ├── InsightIndicatorService.js # ✅ Sidebar pulsing dot indicators for active AI insights
│   ├── QRLabelService.js # ✅ Centralized QR label generation + print (dashboard + editor)
│   ├── NavModuleService.js # ✅ Module nav same-tab/new-tab preference + first-use popover
│   ├── ToastService.js    # ✅ Notificaciones toast
│   └── Logger.js          # ✅ Sistema de logging con niveles
├── utils/                  # Utilidades auxiliares (validación, helpers)
│   ├── TabHelper.js       # ✅ "New Tab" standalone mode
│   ├── IpValidator.js     # ✅ Validación IPs RFC 1918
│   ├── DeviceFilterService.js # ✅ Device list filtering (search, status, group, rack)
│   ├── InsightDetailModal.js  # ✅ AI Insight detail modal + conversation history + revise (shared Observatory + Editor)
│   ├── InsightUtils.js        # ✅ Shared constants (RISK_COLORS, RISK_PRIORITY) + helpers (escHtml, timeAgo)
│   ├── InsightActions.js      # ✅ Shared insight action handlers (handleExplain, handleRevise, buildInsightActions)
│   ├── SidebarResize.js      # ✅ Draggable sidebar resize (Terminal, Observatory)
│   └── GuideService.js       # ✅ App Guide slideshow: open/close/reset, auto-detect slide from URL, keyboard nav
├── state_core/             # Gestión de estado global
│   ├── StateManager.js    # ✅ Undo/Redo global
│   └── PersistenceManager.js # ✅ LocalStorage wrapper
└── constants/              # Constantes globales
    ├── Timing.js          # ✅ Timeouts, debounce
    └── Typography.js      # ✅ Fuentes autorizadas

1. ApiService.js

Ubicación: static/js/services/ApiService.js Propósito: Wrapper centralizado para HTTP requests con manejo automático de CSRF tokens.

Cuándo Usar

Cuándo NO Usar

API

import { ApiService } from '../services/ApiService.js';

// GET request
const data = await ApiService.get('/api/endpoint');

// POST request
const result = await ApiService.post('/api/endpoint', { key: 'value' });

// PUT request
await ApiService.put('/api/endpoint/123', { updated: true });

// DELETE request
await ApiService.delete('/api/endpoint/123');

Características

Ejemplo de Uso Incorrecto

// ❌ NO HACER ESTO
fetch('/api/devices', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
});

// ✅ HACER ESTO
await ApiService.post('/api/devices', data);

2. TabHelper.js

Ubicación: static/js/utils/TabHelper.js Propósito: Utilidad centralizada para abrir páginas en “New Tab” con modo standalone.

Cuándo Usar

Cuándo NO Usar

API

import {
    openInNewTab,
    applyStandaloneMode,
    isStandaloneMode,
    openTerminalInNewTab,
    openObservatoryInNewTab,
    openObservatoryOverviewInNewTab
} from '../utils/TabHelper.js';

// Función genérica
openInNewTab({
    baseUrl: '/my-page/',
    params: { item_id: 123, name: 'Item' },
    onClose: () => closeInternalTab()
});

// Wrappers específicos
openTerminalInNewTab(deviceId, deviceName, () => closeTab(tabId));
openObservatoryInNewTab(deviceId, deviceName, () => closeTab(tabId));
openObservatoryOverviewInNewTab();

// Aplicar standalone mode en init de página
applyStandaloneMode({
    sidebar: '.my-sidebar',
    layout: '.my-layout',
    hideElements: ['.overview-tab', '.menu']
});

// Detectar standalone mode
if (isStandaloneMode()) {
    console.log('Running in standalone mode');
}

Características

Flujo de Uso Completo

1. En la página de origen (ej: Terminal):

// En el botón "New Tab"
button.onclick = () => {
    openTerminalInNewTab(deviceId, deviceName, () => {
        // Cerrar la tab interna después de abrir la nueva
        this.app.closeTab(tabId);
    });
};

2. En la página de destino (ej: Terminal):

// En DOMContentLoaded o init()
applyStandaloneMode({
    sidebar: '.terminal-sidebar',
    layout: '.terminal-layout'
});

Páginas que Usan TabHelper

Agregar a Nueva Página

Paso 1: Import en archivo principal

import { openInNewTab, applyStandaloneMode } from '../utils/TabHelper.js';

Paso 2: Añadir botón “New Tab”

<button onclick="MyApp.openInNewTab()">New Tab</button>

Paso 3: Implementar función

MyApp.openInNewTab() {
    openInNewTab({
        baseUrl: '/my-page/',
        params: { id: this.currentId },
        onClose: () => this.closeInternalView()
    });
}

Paso 4: Aplicar standalone mode en init

document.addEventListener('DOMContentLoaded', () => {
    applyStandaloneMode({
        sidebar: '.my-sidebar',
        layout: '.my-layout'
    });
});

3. EChartsService.js

Ubicación: static/js/services/EChartsService.js Propósito: Factory centralizado para crear, configurar y gestionar gráficas Apache ECharts.

Cuándo Usar

Cuándo NO Usar

API

import { EChartsService } from '../services/EChartsService.js';

// Crear gráfica
const chart = EChartsService.createChart('my-chart-id', {
    xAxis: { type: 'time' },
    yAxis: { type: 'value' },
    series: [{ type: 'line', data: [...] }]
});

// Actualizar datos
EChartsService.updateChart('my-chart-id', newData);

// Reset zoom
EChartsService.resetZoom('my-chart-id');

// Export PNG
EChartsService.exportChart('my-chart-id', 'filename');

// Aplicar estilo
EChartsService.applyChartStyle('my-chart-id', 'smooth');

// Destruir
EChartsService.destroy('my-chart-id');

Características


4. ToastService.js

Ubicación: static/js/services/ToastService.js Propósito: Sistema de notificaciones toast consistente.

Cuándo Usar

Cuándo NO Usar

API

import { ToastService } from '../services/ToastService.js';

ToastService.show('Operation successful', 'success', 3000);
ToastService.show('Error occurred', 'error', 5000);
ToastService.show('Loading...', 'info');

Nota: Disponible globalmente como window.showAppToast().


5. StateManager.js

Ubicación: static/js/state_core/StateManager.js Propósito: Undo/Redo global para editores (Racks, Blueprints).

Cuándo Usar

Cuándo NO Usar

API

import { StateManager } from '../state_core/StateManager.js';

const stateManager = new StateManager(maxHistorySize);

// Guardar snapshot
stateManager.saveState(currentState);

// Undo
const previousState = stateManager.undo();

// Redo
const nextState = stateManager.redo();

6. PersistenceManager.js

Ubicación: static/js/state_core/PersistenceManager.js Propósito: Wrapper seguro para localStorage con namespace y validación.

Cuándo Usar

Cuándo NO Usar

API

import { PersistenceManager } from '../state_core/PersistenceManager.js';

// Save
PersistenceManager.save('myKey', { data: 'value' });

// Load
const data = PersistenceManager.load('myKey', defaultValue);

// Remove
PersistenceManager.remove('myKey');

// Clear all
PersistenceManager.clear();

7. IpValidator.js

Ubicación: static/js/utils/IpValidator.js Propósito: Validación de direcciones IP según RFC 1918 (redes privadas).

Cuándo Usar

API

import { isPrivateIP } from '../utils/IpValidator.js';

if (isPrivateIP('192.168.1.1')) {
    console.log('IP privada');
}

8. Logger.js

Ubicación: static/js/services/Logger.js Propósito: Sistema de logging con niveles (debug, info, warn, error).

Cuándo Usar

API

import { Logger } from '../services/Logger.js';

const logger = new Logger('MyModule');

logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');

9. DeviceFilterService.js

Ubicación: static/js/utils/DeviceFilterService.js Propósito: Filtrado centralizado de listas de dispositivos (search, status, group, rack). Compartido entre Terminal y Observatory.

Cuándo Usar

Cuándo NO Usar

API — Page-level filtering (fixed IDs)

import { DeviceFilterService } from '../utils/DeviceFilterService.js';

// Filter items in a list container
DeviceFilterService.filterDevices('#device-list');

// Clear search input and re-filter
DeviceFilterService.clearSearch(() => myFilterFn());

// Toggle search clear button visibility
DeviceFilterService.toggleClearBtn();

API — Scoped filtering (for modals / embedded panels)

Uses data-filter attributes instead of fixed IDs — avoids DOM conflicts when the page already has #device-search etc.

// Filter .filter-item elements within a scoped container
DeviceFilterService.filterScoped(overlayElement, '#my-list');

// Clear scoped search and re-filter
DeviceFilterService.clearScoped(overlayElement, () => myFilterFn());

// Toggle scoped clear button visibility
DeviceFilterService.toggleClearScoped(overlayElement);

Scoped HTML contract:

<input  data-filter="search">          <!-- Search input -->
<button data-filter="clear">           <!-- Clear button -->
<select data-filter-attr="risk">       <!-- Matches item data-risk -->
<select data-filter-attr="status">     <!-- Matches item data-status -->
<!-- Items: .filter-item with data-name, data-ip, + any data-* matching selects -->

Consumers

Expected HTML IDs (page-level mode)

Expected Data Attributes on .device-item (page-level)

Expected Data Attributes on .filter-item (scoped mode)


10. ModalHelper (modal_helper.js)

Ubicación: static/js/editor/modal_helper.js CSS: static/css/components.css (clases .modal-overlay, .modal-content, .modal-content-md, .modal-content-lg) Propósito: Sistema centralizado de apertura/cierre de modales con drag & drop universal. Todos los modales de la aplicación deben usar este helper.

Cuándo Usar

Cuándo NO Usar

API

import { openModal, closeModal, toggleModal, setupModalCloseHandlers, initModal, closeAllModals } from '../editor/modal_helper.js';

// Abrir modal (display: flex)
openModal('my-modal-id');

// Cerrar modal (display: none)
closeModal('my-modal-id');

// Toggle
toggleModal('my-modal-id');

// Setup completo con close handlers (botón X, Cancel, backdrop)
setupModalCloseHandlers('my-modal-id', {
    closeOnBackdrop: false,  // default: false (click fuera NO cierra)
    onClose: () => console.log('Modal cerrado')
});

// Init = setup + retorna objeto de control
const modal = initModal('my-modal-id');
modal.open();
modal.close();

// Cerrar todos los modales abiertos
closeAllModals();

Acceso Global

Las funciones principales están expuestas en window para scripts que no son ES modules:

window.openModal('my-modal-id');
window.closeModal('my-modal-id');
window.toggleModal('my-modal-id');

Configuración: closeOnBackdrop

Por defecto: false — hacer click fuera del modal NO lo cierra. Los modales solo se cierran con:

Consumers

MóduloImport
dashboard.jsopenModal, closeModal
auth.jsGlobals (window.openModal/closeModal)
base.jsGlobals (window.openModal/closeModal)
network_status.jsGlobals (window.openModal/closeModal)
script_manager.jsopenModal, closeModal
editor/backups.jsopenModal, closeModal
editor/import_export.jsopenModal, closeModal, closeAllModals
editor/ui_panels.jsopenModal, setupModalCloseHandlers
editor/DeviceInfoModal.jssetupModalCloseHandlers + window.openModal
editor/SshConfigModal.jssetupModalCloseHandlers
editor/PortConfigDiff.jsopenModal, closeModal
editor/ssh_integration.jsGlobals (window.openModal/closeModal)
terminal/AgentLifecycle.jsopenModal as _open, closeModal as _close
terminal/ManualConnection.jsopenModal as _open, closeModal as _close
terminal/DeviceManager.jsopenModal, closeModal
network/auto_provision.jsopenModal as _openModal, closeModal as _closeModal
observatory/ObservatoryAlerts.jsopenModal, closeModal
blueprints/MapInteraction.jsopenModal, closeModal

Estructura HTML de un Modal

<div id="my-modal" class="modal-overlay">
    <div class="modal-content modal-content-md">
        <div class="modal-header">
            <h2>Title</h2>
            <button class="modal-close-btn" title="Close">×</button>
        </div>
        <div class="modal-body">
            <!-- Content -->
        </div>
        <div class="modal-footer">
            <button class="btn btn-neutral close-modal" title="Cancel">Cancel</button>
            <button class="btn btn-action" title="Save changes">Save</button>
        </div>
    </div>
</div>

Drag & Drop

Todos los modales son arrastrables desde su header. El sistema detecta automáticamente el drag handle con 3 niveles de fallback:

  1. Clases conocidas: .modal-header, .modal-header-simple, .port-diff-modal-header, .guide-header
  2. Heading directo: Primer <h2>, <h3> o <h4> hijo directo del .modal-content
  3. Div wrapper: Primer <div> hijo que contenga un heading (ej: .terminal-header, .flex-between-center)

Implementación:

No requiere configuración: Funciona automáticamente en todos los modales existentes y futuros.

Backlight Glow (CSS)

Los modales tienen un efecto de iluminación trasera blanca (backlight) definido en components.css:

Ejemplo de Uso Incorrecto

// ❌ NO HACER ESTO
const modal = document.getElementById('my-modal');
modal.style.display = 'flex';
// ...
modal.style.display = 'none';

// ✅ HACER ESTO
import { openModal, closeModal } from '../editor/modal_helper.js';
openModal('my-modal');
closeModal('my-modal');

11. Semantic Button System (components.css)

Ubicación: static/css/components.css (sección SEMANTIC BUTTON ALIASES) Propósito: Sistema centralizado de clases de botón con colores semánticos. Evita duplicación de estilos de botón en CSS de páginas individuales.

Cuándo Usar

Cuándo NO Usar

Clases Disponibles

ClaseColorIntención
btn-actionAzul (#82B1FF)Save, Create, Confirm, Apply
btn-dangerRojo (—danger)Delete, Remove, Disconnect
btn-warningNaranja (—warning)Edit, Modify, Reset, Caution
btn-successVerde (—success)Login, Connect, Accept
btn-exportAmarillo (#e3b341)Export, Print, Download
btn-infoCyan (#58a6ff)Info, Status, Expert Charts
btn-neutralBlancoNavigate, View, Close, Cancel

Tamaños

ClaseAltoUso típico
(base)~38pxModales, formularios
btn-sm28pxToolbars, paneles
btn-xs22pxBadges interactivos

Uso en HTML

<button class="btn btn-sm btn-action" title="Save changes">Save</button>

Uso en JS Dinámico

// Crear botón
const btn = document.createElement('button');
btn.className = 'btn btn-sm btn-action';
btn.textContent = 'Save';
btn.title = 'Save changes';

// Toggle estado
btn.classList.toggle('btn-action', isActive);
btn.classList.toggle('btn-warning', !isActive);

Regla de Oro

Si necesitas propiedades adicionales (width, margin, text-transform), añádelas en el CSS de la página pero SIN redefinir colores, bordes ni fondos. Estos vienen exclusivamente de components.css.

Auditoría (05-03-2026)

Se eliminaron ~158 líneas de CSS duplicado en 12 archivos. Clases eliminadas: .btn-obs, .btn-obs-blue, .btn-obs-orange, .btn-obs-red, .btn-edit, .btn-delete, .chart-guide-btn, .chart-newtab-btn.


12. InsightUtils.js

Ubicación: static/js/utils/InsightUtils.js Propósito: Constantes y helpers compartidos por todas las vistas de AI Insights (CNS).

Exports

ExportTipoDescripción
RISK_COLORSObject{ HIGH: '#ef4444', MEDIUM: '#f59e0b', LOW: '#3b82f6' }
RISK_PRIORITYObject{ HIGH: 3, MEDIUM: 2, LOW: 1 } (para ordenar por severidad)
escHtml(str)FunctionEscapa HTML (<, >, &, ") para prevenir XSS
timeAgo(date)FunctionFormato relativo (“5m ago”, “2h ago”, “3d ago”)

Consumidores

Cuándo Usar


13. InsightDetailModal.js

Ubicación: static/js/utils/InsightDetailModal.js Propósito: Modal unificado para mostrar detalle de AI Insights. Usado por Observatory y Rack Editor.

Exports

ExportTipoDescripción
showInsightDetailModal(insight, actions)FunctionAbre modal con detalle completo del insight
showInsightListModal(insights, actions)FunctionLista de insights → click abre detalle
appendConversation(overlay, entry)FunctionAñade entrada Q&A al historial de conversaciones del modal

Parámetro actions

{
    onApply: (insightId) => { ... },           // Ejecutar fix SSH
    onAcknowledge: (insightId, overlay) => { ... }, // Acknowledgar
    onExplain: (insightId, overlay) => { ... },     // Preguntar a la IA
    onRevise: (insightId, overlay) => { ... },      // Revisar diagnóstico
}

Features del Modal (v4)

Consumidores


14. InsightActions.js

Ubicación: static/js/utils/InsightActions.js Propósito: Handlers centralizados de acciones sobre AI Insights. Evita duplicación entre Observatory, CNS History y Rack Editor.

Exports

ExportTipoDescripción
handleExplain(insightId, overlay, options)async FunctionPOST explain, muestra respuesta, persiste conversación, auto-append
handleRevise(insightId, overlay, onRefresh)async FunctionPOST revise, muestra resultado, callback refresh si revisado
showAcknowledgePrompt(insightId)FunctionMini-modal para acknowledge con notas. Retorna Promise<string|null>
buildInsightActions(options)FunctionFactory que retorna { onApply, onAcknowledge, onExplain, onRevise }

handleExplain options

{
    inputRole: 'explain-input',       // data-role del input
    btnAction: 'explain',             // data-action del botón
    resultRole: 'explain-result',     // data-role del contenedor resultado
    answerRole: 'explain-answer',     // data-role del texto respuesta
    providerRole: 'explain-provider', // data-role del provider
}

buildInsightActions options

{
    onRefresh: () => loadCNSData(), // Callback tras apply/acknowledge/revise exitoso
}

Consumidores


15. Group Service (Backend Centralizado)

Ubicación: monitoring/services/group_service.py Propósito: Servicio compartido para grupos de dispositivos — centraliza queries de membresía, agregación de métricas VictoriaMetrics, y asignación de perfiles a grupos.

Cuándo Usar

Cuándo NO Usar

API (Python)

from monitoring.services.group_service import (
    get_group_profiles,
    get_group_target_ids,
    query_group_metric,
    assign_profiles_to_group,
)

# Obtener perfiles de un grupo filtrados por tipo
profiles = get_group_profiles(group_id, org, ['ups'])

# Extraer target IDs para queries VictoriaMetrics
target_ids = get_group_target_ids(profiles)

# Consultar métrica agregada (sum o avg según tipo)
result = query_group_metric(target_ids, tenant_id, 'battery_charge', hours=4)

# Asignar perfiles a un grupo (add selected, remove unselected)
count = assign_profiles_to_group(org, group_id, profile_ids, ['ups'])

Consumidores

Frontend counterparts


16. ChartGuideService.js

Ubicación: static/js/services/ChartGuideService.js Propósito: Modal centralizado de guía de interpretación de gráficas. Recibe título, registro de métricas y datos de guía; renderiza un modal con secciones por métrica (descripción, tabla de umbrales, tips).

Cuándo Usar

Cuándo NO Usar

API

import { showChartGuide } from '../services/ChartGuideService.js';

// registry: { key: { label, color } }
// guideData: { key: { description, thresholds: [{level,range,color,detail}], tips: [string] } }
showChartGuide('My Guide Title', registry, guideData);

Datos de guía por página

Consumidores

CSS

Reutiliza clases existentes: .chart-guide-overlay, .chart-guide-modal, .chart-guide-header, .chart-guide-body, .chart-guide-section, .expert-threshold-table, .expert-threshold-dot, .expert-tips


17. ScopedInsightTab.js

Ubicación: static/js/services/ScopedInsightTab.js Propósito: Componente reutilizable de pestaña CNS (AI Insights) con scope por targetIds. Renderiza tabla de insights, stats bar, filtros, paginación, export CSV y auto-refresh — todo scoped a un conjunto de dispositivos.

Cuándo Usar

Cuándo NO Usar

API

import { ScopedInsightTab } from '../services/ScopedInsightTab.js';

const cnsTab = new ScopedInsightTab({
    containerId: 'my-cns-container',   // DOM element ID where tab renders
    targetIds: [1, 2, 3],             // MonitoringTarget IDs to scope
    actionNamespace: 'wireless',       // Namespace for button data-action (avoids DOM conflicts)
});

// Render the full CNS tab (stats + filters + table + pagination)
cnsTab.render();

// Refresh data (called on tab activation or WS insight_update)
cnsTab.refresh();

// Cleanup when tab is destroyed
cnsTab.destroy();

Parameters

ParameterTypeDescription
containerIdstringID of the container element (created by caller or auto-created)
targetIdsnumber[]Array of MonitoringTarget IDs — insights are filtered to these targets only
actionNamespacestringPrefix for data-action attributes (e.g., wireless-cns-details) to avoid conflicts when multiple CNS tabs exist

Consumers

Backend dependency


18. InsightIndicatorService.js

Ubicación: static/js/services/InsightIndicatorService.js Propósito: Muestra puntos pulsantes de riesgo (HIGH=rojo, MEDIUM=naranja, LOW=azul) junto a los nombres de dispositivos en sidebars. Soporta actualizaciones en tiempo real via WebSocket.

Cuándo Usar

Cuándo NO Usar

API

import { InsightIndicatorService } from '../services/InsightIndicatorService.js';

const indicators = new InsightIndicatorService({
    containerSelector: '.device-list',   // CSS selector of the sidebar list
    targetAttr: 'data-target-id',        // Attribute on .device-item with target ID
});

// Load initial indicators for given target IDs
await indicators.load([1, 2, 3]);

// Update a single device indicator (e.g., on WS insight_update)
indicators.update(targetId, { risk_level: 'HIGH', count: 2 });

// Clear all indicators
indicators.clear();

CSS

Consumers


19. ObservatoryITSM.js (Centralized ITSM Dashboard)

Ubicación: static/js/pages/observatory/ObservatoryITSM.js Propósito: Dashboard ITSM completo que se puede instanciar en cualquier contexto. Genera su propio HTML skeleton y channel modal, los inyecta en el DOM, y renderiza todas las secciones ITSM (SLA, Notifications, Escalation, Maintenance, Analytics, Known Issues, Runbooks, Patterns, Incident Groups).

Cuándo Usar

Cuándo NO Usar

API

import { ObservatoryITSM } from '../pages/observatory/ObservatoryITSM.js';

const itsm = new ObservatoryITSM({
    containerId: 'itsm-container',     // DOM element ID for the dashboard
    namespace: 'observatory',          // Namespace for data-action attributes
    targetIds: null,                   // Optional: scope analytics/groups/patterns by target IDs
});

// Initialize: generates HTML skeleton, appends channel modal to body, loads data
itsm.init();

// Refresh data (called on tab activation)
itsm.refresh();

Parameters

ParameterTypeDescription
containerIdstringID of the container where ITSM dashboard renders
namespacestringPrefix for data-action attributes to avoid DOM conflicts
targetIdsnumber[] or nullWhen set, Analytics/Incident Groups/Recurring Patterns are scoped to these targets. SLA, Channels, Escalation, Maintenance, Known Issues, Runbooks remain org-wide

Scoping behavior

SectionScoped by targetIdsReason
AnalyticsYesMTTA/MTTR/compliance filtered to relevant devices
Incident GroupsYesOnly groups containing scoped targets
Recurring PatternsYesOnly patterns for scoped targets
SLA PoliciesNoOrg-wide configuration
Notification ChannelsNoOrg-wide configuration
Escalation PoliciesNoOrg-wide configuration
Maintenance WindowsNoOrg-wide configuration
Known IssuesNoOrg-wide knowledge base
RunbooksNoOrg-wide procedures

Consumers


Checklist: ¿Necesito Crear un Nuevo Servicio?

Antes de crear un nuevo servicio, verifica:

Si es específico de un módulo: Crear en subcarpeta del módulo (ej: static/js/terminal/) Si es reutilizable: Añadir a services/ o utils/ y DOCUMENTAR AQUÍ.


Añadir un Nuevo Servicio Centralizado

Paso 1: Crear archivo en services/ o utils/

// static/js/services/MyNewService.js
export class MyNewService {
    static doSomething() { ... }
}

Paso 2: Añadir a index.js correspondiente

// static/js/services/index.js
export { MyNewService } from './MyNewService.js';

Paso 3: DOCUMENTAR EN ESTE ARCHIVO (CENTRALIZED_SERVICES.md)

Paso 4: Actualizar Documentation/INDEX.md

Paso 5: Commit con mensaje claro

git commit -m "feat: add MyNewService centralizado (propósito del servicio)"

Referencias


Mantenido por: Equipo CreaRack Revisión recomendada: Al añadir nuevos servicios o módulos JS

Véase también

Subir