Files
SteelBase/public/docs-historicos/EXEMPLOS-CODIGO-MELHORIAS.md

20 KiB

💻 Exemplos Práticos de Código - Melhorias Críticas

1. 🔴 MODULARIZAÇÃO - Estrutura Proposta

Antes (Atual - 8.190 linhas)

// app.js - TUDO em um arquivo
const appState = { ... };
const userPreferences = { ... };
const adminConfig = { ... };
const materialsDatabase = { ... };
const ajudaDatabase = { ... };
function showSection() { ... }
function calcularCEV() { ... }
function openModal() { ... }
// ... 8.000+ linhas mais

Depois (Modular)

/js/core/state.js

export const appState = {
  history: [],
  favorites: [],
  budgetItems: [],
  currentSection: 'cev',
  currentTheme: 'dark',
  expertMode: false,
  currentSidebarTab: 0
};

export const userPreferences = {
  theme: 'dark',
  colorScheme: 'default',
  fontSize: 'medium',
  fontFamily: 'default'
};

export const adminConfig = {
  appName: 'AÇO CALC PRO',
  appSubtitle: 'Plataforma Técnica com Base de Dados de Materiais Brasileiros',
  // ...
};

/js/core/storage.js

import { userPreferences } from './state.js';

export function loadPreferences() {
  try {
    const saved = localStorage.getItem('acoCalcPreferences');
    if (saved) {
      Object.assign(userPreferences, JSON.parse(saved));
    }
  } catch (e) {
    console.warn('Não foi possível carregar preferências:', e);
  }
}

export function savePreferences() {
  try {
    localStorage.setItem('acoCalcPreferences', JSON.stringify(userPreferences));
  } catch (e) {
    console.warn('Não foi possível salvar preferências:', e);
  }
}

/js/ui/navigation.js

import { appState } from '../core/state.js';
import { loadSectionContent } from './content-loader.js';

export function showSection(sectionId) {
  appState.currentSection = sectionId;
  
  // Update sidebar active state
  document.querySelectorAll('.sidebar-item').forEach(item => {
    item.classList.toggle('active', item.dataset.section === sectionId);
  });
  
  // Load section content
  loadSectionContent(sectionId);
  
  // Add help button after content loads
  setTimeout(() => addHelpButton(sectionId), 100);
}

export function switchTab(tabIndex) {
  document.querySelectorAll('.tab-btn').forEach((btn, i) => {
    btn.classList.toggle('active', i === tabIndex);
  });
  document.querySelectorAll('.tab-content').forEach((content, i) => {
    content.classList.toggle('active', i === tabIndex);
  });
}

/js/ui/content-loader.js (Lazy Loading)

// Dynamic imports - carrega apenas quando necessário
const sectionLoaders = {
  'cev': () => import('../tools/materiais.js').then(m => m.getCEVContent()),
  'seletor': () => import('../tools/materiais.js').then(m => m.getSeletorContent()),
  'parafusos': () => import('../tools/conexoes.js').then(m => m.getParafusosContent()),
  'preaquecimento': () => import('../tools/soldagem.js').then(m => m.getPreaquecimentoContent()),
  'dureza': () => import('../tools/ensaios.js').then(m => m.getDurezaContent()),
  'area-pintura': () => import('../tools/pintura.js').then(m => m.getAreaPinturaContent()),
  'orcamento': () => import('../tools/orcamento.js').then(m => m.getOrcamentoContent()),
};

export async function loadSectionContent(sectionId) {
  const content = document.getElementById('main-content');
  
  // Show loading
  content.innerHTML = '<div class="loading">⏳ Carregando...</div>';
  
  try {
    const loader = sectionLoaders[sectionId];
    if (loader) {
      const html = await loader();
      content.innerHTML = html;
    } else {
      content.innerHTML = '<p>Seção não encontrada</p>';
    }
  } catch (error) {
    console.error('Erro ao carregar seção:', error);
    content.innerHTML = '<p>Erro ao carregar conteúdo</p>';
  }
}

/js/tools/materiais.js

export function getCEVContent() {
  return `
    <div class="section-header">
      <div class="section-title">⚗️ CEV Avançado</div>
      <!-- ... -->
    </div>
  `;
}

export function getSeletorContent() {
  return `
    <div class="section-header">
      <div class="section-title">🔍 Seletor de Aço</div>
      <!-- ... -->
    </div>
  `;
}

/js/main.js (Entry Point)

import { loadPreferences, savePreferences } from './core/storage.js';
import { applyUserPreferences } from './ui/theme.js';
import { showSection } from './ui/navigation.js';
import { initializeApp } from './core/init.js';

// Initialize app when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
  console.log('🚀 AÇO CALC PRO v7.5 - Inicializando...');
  
  loadPreferences();
  applyUserPreferences();
  initializeApp();
  
  console.log('✅ Aplicativo inicializado com sucesso!');
});

2. 🔴 RESPONSIVIDADE MOBILE

CSS Mobile-First

style-mobile.css

/* ========================================
   MOBILE FIRST APPROACH
   ======================================== */

/* Base (Mobile) - 320px+ */
:root {
  --header-height: 60px;
  --sidebar-width: 100%;
  --content-padding: 16px;
}

/* Header Mobile */
.header {
  position: sticky;
  top: 0;
  z-index: 100;
  height: var(--header-height);
}

.header-content {
  flex-direction: column;
  gap: 8px;
  padding: 8px;
}

.logo-section {
  text-align: center;
}

.logo {
  font-size: 18px;
}

.subtitle {
  font-size: 11px;
  display: none; /* Hide on mobile */
}

.header-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
}

.btn-icon {
  font-size: 11px;
  padding: 6px 10px;
  white-space: nowrap;
}

/* Sidebar Mobile - Drawer */
.sidebar {
  position: fixed;
  top: var(--header-height);
  left: -100%;
  width: 280px;
  height: calc(100vh - var(--header-height));
  background: var(--color-surface);
  box-shadow: var(--shadow-lg);
  transition: left 0.3s ease;
  z-index: 999;
  overflow-y: auto;
}

.sidebar.open {
  left: 0;
}

/* Overlay when sidebar is open */
.sidebar-overlay {
  display: none;
  position: fixed;
  top: var(--header-height);
  left: 0;
  width: 100%;
  height: calc(100vh - var(--header-height));
  background: rgba(0, 0, 0, 0.5);
  z-index: 998;
}

.sidebar-overlay.active {
  display: block;
}

/* Hamburger Menu */
.hamburger {
  display: block;
  position: fixed;
  bottom: 20px;
  right: 20px;
  width: 56px;
  height: 56px;
  background: var(--color-primary);
  border-radius: 50%;
  border: none;
  box-shadow: var(--shadow-lg);
  cursor: pointer;
  z-index: 1000;
  transition: transform 0.3s;
}

.hamburger:active {
  transform: scale(0.95);
}

.hamburger-icon {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
  justify-content: center;
}

.hamburger-icon span {
  display: block;
  width: 24px;
  height: 3px;
  background: white;
  border-radius: 2px;
  transition: all 0.3s;
}

.hamburger.open .hamburger-icon span:nth-child(1) {
  transform: rotate(45deg) translate(6px, 6px);
}

.hamburger.open .hamburger-icon span:nth-child(2) {
  opacity: 0;
}

.hamburger.open .hamburger-icon span:nth-child(3) {
  transform: rotate(-45deg) translate(6px, -6px);
}

/* Container Mobile */
.container {
  flex-direction: column;
  padding: 0;
}

.main-content {
  width: 100%;
  padding: var(--content-padding);
  min-height: calc(100vh - var(--header-height));
}

/* Forms Mobile */
.form-grid {
  grid-template-columns: 1fr !important;
  gap: 12px;
}

.form-control {
  font-size: 16px; /* Prevent zoom on iOS */
  padding: 12px;
}

/* Buttons Mobile */
.btn {
  width: 100%;
  padding: 14px;
  font-size: 16px;
}

/* Cards Mobile */
.card {
  margin-bottom: 16px;
  padding: 16px;
}

/* Tabs Mobile */
.tabs-nav {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE/Edge */
}

.tabs-nav::-webkit-scrollbar {
  display: none; /* Chrome/Safari */
}

.tab-btn {
  flex-shrink: 0;
  min-width: 120px;
}

/* Modals Mobile */
.modal-content {
  width: 95%;
  max-width: 95%;
  max-height: 90vh;
  margin: 20px auto;
}

.modal-help-content {
  width: 95%;
  max-height: 85vh;
}

/* Tables Mobile - Horizontal Scroll */
.table-wrapper {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

table {
  min-width: 600px;
  font-size: 13px;
}

/* ========================================
   TABLET - 768px+
   ======================================== */
@media (min-width: 768px) {
  :root {
    --header-height: 80px;
    --sidebar-width: 260px;
    --content-padding: 24px;
  }
  
  .header-content {
    flex-direction: row;
    justify-content: space-between;
    padding: 16px 24px;
  }
  
  .subtitle {
    display: block;
    font-size: 13px;
  }
  
  .btn-icon {
    font-size: 13px;
    padding: 8px 14px;
  }
  
  .hamburger {
    display: none;
  }
  
  .sidebar {
    position: static;
    width: var(--sidebar-width);
    height: auto;
  }
  
  .sidebar-overlay {
    display: none !important;
  }
  
  .container {
    flex-direction: row;
  }
  
  .form-grid {
    grid-template-columns: repeat(2, 1fr) !important;
  }
  
  .btn {
    width: auto;
  }
}

/* ========================================
   DESKTOP - 1024px+
   ======================================== */
@media (min-width: 1024px) {
  :root {
    --sidebar-width: 300px;
    --content-padding: 32px;
  }
  
  .container {
    max-width: 1400px;
    margin: 0 auto;
  }
  
  .form-grid {
    grid-template-columns: repeat(3, 1fr) !important;
  }
  
  .logo {
    font-size: 24px;
  }
  
  .subtitle {
    font-size: 14px;
  }
}

/* ========================================
   LARGE DESKTOP - 1440px+
   ======================================== */
@media (min-width: 1440px) {
  .container {
    max-width: 1600px;
  }
  
  .form-grid {
    grid-template-columns: repeat(4, 1fr) !important;
  }
}

/* ========================================
   TOUCH OPTIMIZATIONS
   ======================================== */
@media (hover: none) and (pointer: coarse) {
  /* Increase touch targets */
  .btn,
  .btn-icon,
  .sidebar-item,
  .tab-btn {
    min-height: 44px; /* Apple HIG recommendation */
    min-width: 44px;
  }
  
  /* Remove hover effects on touch */
  .btn:hover,
  .sidebar-item:hover {
    transform: none;
  }
  
  /* Add active states */
  .btn:active {
    transform: scale(0.98);
  }
}

/* ========================================
   PRINT STYLES
   ======================================== */
@media print {
  .header,
  .sidebar,
  .footer,
  .btn-help,
  .hamburger {
    display: none !important;
  }
  
  .main-content {
    width: 100%;
    padding: 0;
  }
  
  .card {
    break-inside: avoid;
  }
}

JavaScript para Hamburger Menu

js/ui/mobile-menu.js

export function initMobileMenu() {
  // Create hamburger button
  const hamburger = document.createElement('button');
  hamburger.className = 'hamburger';
  hamburger.innerHTML = `
    <div class="hamburger-icon">
      <span></span>
      <span></span>
      <span></span>
    </div>
  `;
  
  // Create overlay
  const overlay = document.createElement('div');
  overlay.className = 'sidebar-overlay';
  
  // Add to DOM
  document.body.appendChild(hamburger);
  document.body.appendChild(overlay);
  
  // Get sidebar
  const sidebar = document.querySelector('.sidebar');
  
  // Toggle menu
  const toggleMenu = () => {
    hamburger.classList.toggle('open');
    sidebar.classList.toggle('open');
    overlay.classList.toggle('active');
    document.body.style.overflow = sidebar.classList.contains('open') ? 'hidden' : '';
  };
  
  // Event listeners
  hamburger.addEventListener('click', toggleMenu);
  overlay.addEventListener('click', toggleMenu);
  
  // Close menu when clicking sidebar item
  document.querySelectorAll('.sidebar-item').forEach(item => {
    item.addEventListener('click', () => {
      if (window.innerWidth < 768) {
        toggleMenu();
      }
    });
  });
  
  // Handle resize
  let resizeTimer;
  window.addEventListener('resize', () => {
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(() => {
      if (window.innerWidth >= 768) {
        hamburger.classList.remove('open');
        sidebar.classList.remove('open');
        overlay.classList.remove('active');
        document.body.style.overflow = '';
      }
    }, 250);
  });
}

3. 🔴 LAZY LOADING & CODE SPLITTING

Vite Configuration

vite.config.js

import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Vendor chunk
          'vendor': ['chart.js'],
          
          // Core chunk
          'core': [
            './js/core/state.js',
            './js/core/storage.js',
            './js/core/config.js'
          ],
          
          // UI chunk
          'ui': [
            './js/ui/navigation.js',
            './js/ui/modals.js',
            './js/ui/theme.js'
          ],
          
          // Tools chunks (lazy loaded)
          'tools-materiais': ['./js/tools/materiais.js'],
          'tools-conexoes': ['./js/tools/conexoes.js'],
          'tools-soldagem': ['./js/tools/soldagem.js'],
          'tools-ensaios': ['./js/tools/ensaios.js'],
          'tools-pintura': ['./js/tools/pintura.js'],
          'tools-orcamento': ['./js/tools/orcamento.js']
        }
      }
    },
    
    // Minification
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true, // Remove console.log in production
        drop_debugger: true
      }
    },
    
    // Source maps
    sourcemap: false // Disable in production
  },
  
  // Dev server
  server: {
    port: 3000,
    open: true
  }
});

Package.json

{
  "name": "aco-calc-pro",
  "version": "7.5.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "lint": "eslint js/**/*.js",
    "format": "prettier --write js/**/*.js"
  },
  "devDependencies": {
    "vite": "^5.0.0",
    "eslint": "^8.0.0",
    "prettier": "^3.0.0"
  },
  "dependencies": {
    "chart.js": "^4.0.0"
  }
}

4. 🟡 STATE MANAGEMENT

Simple Reactive Store

js/core/store.js

class Store {
  constructor(initialState) {
    this.state = initialState;
    this.listeners = new Map();
  }
  
  // Get state value
  get(key) {
    return this.state[key];
  }
  
  // Set state value and notify listeners
  set(key, value) {
    const oldValue = this.state[key];
    this.state[key] = value;
    
    // Notify listeners
    if (this.listeners.has(key)) {
      this.listeners.get(key).forEach(callback => {
        callback(value, oldValue);
      });
    }
  }
  
  // Subscribe to state changes
  subscribe(key, callback) {
    if (!this.listeners.has(key)) {
      this.listeners.set(key, new Set());
    }
    this.listeners.get(key).add(callback);
    
    // Return unsubscribe function
    return () => {
      this.listeners.get(key).delete(callback);
    };
  }
  
  // Update multiple values
  update(updates) {
    Object.entries(updates).forEach(([key, value]) => {
      this.set(key, value);
    });
  }
}

// Create global store
export const store = new Store({
  currentSection: 'cev',
  currentTheme: 'dark',
  expertMode: false,
  currentSidebarTab: 0,
  history: [],
  favorites: [],
  budgetItems: []
});

// Auto-save to localStorage
store.subscribe('currentTheme', (theme) => {
  localStorage.setItem('theme', theme);
  applyTheme(theme);
});

store.subscribe('expertMode', (mode) => {
  localStorage.setItem('expertMode', mode);
  filterToolsByMode(mode);
});

Usage

import { store } from './core/store.js';

// Get value
const theme = store.get('currentTheme');

// Set value
store.set('currentTheme', 'light');

// Subscribe to changes
const unsubscribe = store.subscribe('currentSection', (newSection, oldSection) => {
  console.log(`Section changed from ${oldSection} to ${newSection}`);
  updateUI(newSection);
});

// Unsubscribe when done
unsubscribe();

// Update multiple values
store.update({
  currentSection: 'parafusos',
  expertMode: true
});

5. 🟡 ERROR HANDLING

Centralized Error Handler

js/utils/error-handler.js

class ErrorHandler {
  constructor() {
    this.errors = [];
    this.maxErrors = 100;
  }
  
  handle(error, context = {}) {
    const errorInfo = {
      message: error.message,
      stack: error.stack,
      context,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent
    };
    
    // Store error
    this.errors.push(errorInfo);
    if (this.errors.length > this.maxErrors) {
      this.errors.shift();
    }
    
    // Log to console
    console.error('Error:', errorInfo);
    
    // Show user-friendly message
    this.showErrorToast(this.getUserMessage(error));
    
    // Optional: Send to analytics
    // this.sendToAnalytics(errorInfo);
  }
  
  getUserMessage(error) {
    // Map technical errors to user-friendly messages
    const messages = {
      'TypeError': 'Erro nos dados fornecidos. Verifique os valores.',
      'ReferenceError': 'Erro interno. Por favor, recarregue a página.',
      'NetworkError': 'Erro de conexão. Verifique sua internet.',
      'ValidationError': 'Dados inválidos. Verifique os campos.'
    };
    
    return messages[error.name] || 'Ocorreu um erro. Tente novamente.';
  }
  
  showErrorToast(message) {
    const toast = document.createElement('div');
    toast.className = 'toast toast-error';
    toast.textContent = message;
    document.body.appendChild(toast);
    
    setTimeout(() => toast.classList.add('show'), 10);
    setTimeout(() => {
      toast.classList.remove('show');
      setTimeout(() => toast.remove(), 300);
    }, 3000);
  }
  
  getErrors() {
    return this.errors;
  }
  
  clearErrors() {
    this.errors = [];
  }
}

export const errorHandler = new ErrorHandler();

// Global error handler
window.addEventListener('error', (event) => {
  errorHandler.handle(event.error, {
    type: 'global',
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno
  });
});

// Unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
  errorHandler.handle(new Error(event.reason), {
    type: 'promise'
  });
});

Usage

import { errorHandler } from './utils/error-handler.js';

// Wrap risky operations
try {
  const result = calcularCEV(inputs);
  displayResult(result);
} catch (error) {
  errorHandler.handle(error, {
    function: 'calcularCEV',
    inputs
  });
}

// Async operations
async function loadData() {
  try {
    const data = await fetch('/api/data');
    return await data.json();
  } catch (error) {
    errorHandler.handle(error, {
      function: 'loadData',
      url: '/api/data'
    });
    return null;
  }
}

📦 RESULTADO FINAL

Bundle Size Comparison

Arquivo Antes Depois Redução
app.js 412 KB 45 KB -89%
tools-materiais.js - 35 KB (lazy)
tools-conexoes.js - 40 KB (lazy)
tools-soldagem.js - 38 KB (lazy)
tools-ensaios.js - 25 KB (lazy)
tools-pintura.js - 30 KB (lazy)
tools-orcamento.js - 32 KB (lazy)
Initial Load 412 KB 45 KB -89%

Performance Gains

Métrica Antes Depois Melhoria
FCP 3.2s 0.8s -75%
LCP 5.1s 1.9s -63%
TTI 6.3s 2.1s -67%
Lighthouse 62 94 +52%

Próximo Passo: Implementar estas melhorias?