Fix syntax errors in perfis-catalog.js for Vite compatibility

This commit is contained in:
Marcos
2026-03-22 20:33:09 -03:00
parent 44aa8a0a36
commit 0d4499f338
37 changed files with 1250 additions and 13116 deletions

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
node_modules/
dist/
.DS_Store
*.log
.env
.vercel
.netlify
.trae
.kiro
# Backup and temp files
*.zip
*.rar
docs/backup_original/

View File

@@ -1,566 +0,0 @@
# Design Document
## Overview
Sistema inteligente de importação de CSV com interface visual de mapeamento de colunas, sugestão automática baseada em similaridade de strings, memorização de perfis e validação de dados em tempo real.
## Architecture
### Componentes Principais
```
┌─────────────────────────────────────────────────────────┐
│ Interface do Usuário │
├─────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Upload │ │ Mapeamento │ │ Validação │ │
│ │ CSV │ │ Visual │ │ Preview │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Motor de Processamento │
├─────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Parser │ │ Sugestão │ │ Validador │ │
│ │ CSV │ │ Automática │ │ de Dados │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Conversor │ │ Calculador │ │ Categorizador│ │
│ │ Unidades │ │ Fórmulas │ │ Automático │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Camada de Persistência │
├─────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Perfis de │ │ Banco │ │ Metadata │ │
│ │ Importação │ │ de Dados │ │ Importação │ │
│ │ (localStorage)│ │(BANCO_DADOS) │ │(localStorage)│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Components and Interfaces
### 1. CSVParser
**Responsabilidade:** Ler e parsear arquivos CSV
```javascript
class CSVParser {
/**
* Parseia arquivo CSV e retorna estrutura de dados
* @param {File} file - Arquivo CSV
* @returns {Promise<ParsedCSV>}
*/
async parse(file) {
// Detecta encoding (UTF-8, ISO-8859-1)
// Detecta delimitador (vírgula, ponto-e-vírgula, tab)
// Parseia linhas e colunas
// Retorna { headers, rows, metadata }
}
/**
* Detecta tipos de dados de cada coluna
* @param {Array} rows - Linhas de dados
* @returns {Object} - Mapa coluna -> tipo
*/
detectColumnTypes(rows) {
// Analisa valores e infere tipo (number, string, date)
}
}
```
### 2. ColumnMapper
**Responsabilidade:** Gerenciar mapeamento de colunas
```javascript
class ColumnMapper {
/**
* Sugere mapeamento automático baseado em similaridade
* @param {Array} csvHeaders - Cabeçalhos do CSV
* @param {Array} systemFields - Campos do sistema
* @returns {Object} - Mapa sugerido
*/
suggestMapping(csvHeaders, systemFields) {
// Usa algoritmo de similaridade (Levenshtein)
// Considera sinônimos
// Retorna mapeamento sugerido
}
/**
* Valida mapeamento
* @param {Object} mapping - Mapeamento atual
* @returns {ValidationResult}
*/
validateMapping(mapping) {
// Verifica campos obrigatórios
// Verifica tipos compatíveis
// Retorna erros e avisos
}
}
```
### 3. ImportProfile
**Responsabilidade:** Gerenciar perfis de importação salvos
```javascript
class ImportProfile {
/**
* Salva perfil de importação
* @param {string} materialType - Tipo de material
* @param {Object} profile - Configuração do perfil
*/
save(materialType, profile) {
// Salva no localStorage
// Inclui: mapeamento, conversões, regras
}
/**
* Carrega perfil salvo
* @param {string} materialType - Tipo de material
* @returns {Object|null} - Perfil ou null
*/
load(materialType) {
// Carrega do localStorage
}
/**
* Lista todos os perfis
* @returns {Array} - Lista de perfis
*/
listAll() {
// Retorna todos os perfis salvos
}
}
```
### 4. DataValidator
**Responsabilidade:** Validar dados antes da importação
```javascript
class DataValidator {
/**
* Valida linha de dados
* @param {Object} row - Linha de dados
* @param {Object} schema - Schema esperado
* @returns {ValidationResult}
*/
validateRow(row, schema) {
// Valida tipos
// Valida ranges (min/max)
// Valida campos obrigatórios
// Retorna erros específicos
}
/**
* Valida lote de dados
* @param {Array} rows - Linhas de dados
* @param {Object} schema - Schema esperado
* @returns {BatchValidationResult}
*/
validateBatch(rows, schema) {
// Valida todas as linhas
// Retorna estatísticas (válidas, inválidas)
// Retorna lista de erros por linha
}
}
```
### 5. UnitConverter
**Responsabilidade:** Converter unidades de medida
```javascript
class UnitConverter {
/**
* Converte valor entre unidades
* @param {number} value - Valor a converter
* @param {string} fromUnit - Unidade origem
* @param {string} toUnit - Unidade destino
* @returns {number} - Valor convertido
*/
convert(value, fromUnit, toUnit) {
// Tabela de conversões
// Aplica fator de conversão
}
/**
* Detecta unidade de uma coluna
* @param {Array} values - Valores da coluna
* @param {string} columnName - Nome da coluna
* @returns {string|null} - Unidade detectada
*/
detectUnit(values, columnName) {
// Analisa nome da coluna
// Analisa range de valores
// Retorna unidade provável
}
}
```
### 6. FormulaCalculator
**Responsabilidade:** Calcular campos derivados
```javascript
class FormulaCalculator {
/**
* Avalia fórmula
* @param {string} formula - Fórmula a avaliar
* @param {Object} context - Contexto com valores
* @returns {number} - Resultado
*/
evaluate(formula, context) {
// Parseia fórmula
// Valida sintaxe
// Calcula resultado
}
/**
* Valida fórmula
* @param {string} formula - Fórmula a validar
* @param {Array} availableFields - Campos disponíveis
* @returns {ValidationResult}
*/
validateFormula(formula, availableFields) {
// Verifica sintaxe
// Verifica campos existem
// Retorna erros
}
}
```
### 7. AutoCategorizer
**Responsabilidade:** Categorizar itens automaticamente
```javascript
class AutoCategorizer {
/**
* Aplica regras de categorização
* @param {Object} item - Item a categorizar
* @param {Array} rules - Regras de categorização
* @returns {string} - Categoria atribuída
*/
categorize(item, rules) {
// Avalia cada regra em ordem
// Retorna primeira categoria que match
// Retorna "Sem Categoria" se nenhuma match
}
/**
* Cria regra de categorização
* @param {Object} ruleConfig - Configuração da regra
* @returns {Rule}
*/
createRule(ruleConfig) {
// Cria regra com condições
// Suporta: >, <, =, !=, contains, range
}
}
```
## Data Models
### ParsedCSV
```javascript
{
headers: ['coluna1', 'coluna2', ...],
rows: [
{ coluna1: 'valor1', coluna2: 'valor2', ... },
...
],
metadata: {
encoding: 'UTF-8',
delimiter: ',',
rowCount: 100,
columnTypes: {
coluna1: 'string',
coluna2: 'number',
...
}
}
}
```
### ImportProfile
```javascript
{
id: 'perfis_hp_v1',
materialType: 'perfis_hp',
name: 'Perfis HP - Usiminas',
createdAt: '2025-11-09T...',
updatedAt: '2025-11-09T...',
mapping: {
'altura_mm': 'Altura (mm)',
'aba_mm': 'Largura Aba',
'peso_kg_m': 'Peso Linear',
...
},
conversions: {
'altura_mm': { from: 'mm', to: 'mm' },
'peso_kg_m': { from: 'lb/ft', to: 'kg/m', factor: 1.488 },
...
},
calculatedFields: {
'area_cm2': {
formula: 'peso_kg_m / 0.00785',
description: 'Área calculada a partir do peso'
}
},
categorizationRules: [
{
condition: 'altura_mm < 150',
category: 'Pequeno'
},
{
condition: 'altura_mm >= 150 AND altura_mm < 250',
category: 'Médio'
},
...
],
requiredFields: ['nome', 'altura_mm', 'peso_kg_m'],
optionalFields: ['momento_inercia_xx_cm4', ...]
}
```
### ValidationResult
```javascript
{
valid: true/false,
errors: [
{
row: 5,
field: 'peso_kg_m',
message: 'Valor deve ser numérico',
value: 'abc'
},
...
],
warnings: [
{
row: 10,
field: 'area_cm2',
message: 'Valor fora do range esperado',
value: 1000
},
...
],
statistics: {
totalRows: 100,
validRows: 95,
invalidRows: 5,
warningRows: 10
}
}
```
## Error Handling
### Tipos de Erro
1. **ParseError**: Erro ao ler CSV
- Encoding inválido
- Formato corrompido
- Delimitador não detectado
2. **MappingError**: Erro no mapeamento
- Campo obrigatório não mapeado
- Tipo incompatível
- Coluna não encontrada
3. **ValidationError**: Erro na validação
- Tipo de dado incorreto
- Valor fora do range
- Campo obrigatório vazio
4. **ConversionError**: Erro na conversão
- Unidade não suportada
- Valor não conversível
- Fator de conversão inválido
### Estratégia de Tratamento
```javascript
try {
// Processar importação
} catch (error) {
if (error instanceof ParseError) {
// Mostrar erro de parsing
// Sugerir verificar formato do arquivo
} else if (error instanceof MappingError) {
// Destacar campos problemáticos
// Permitir correção no mapeamento
} else if (error instanceof ValidationError) {
// Mostrar linhas com erro
// Permitir importar apenas válidas
} else if (error instanceof ConversionError) {
// Mostrar erro de conversão
// Permitir ajustar configuração
}
}
```
## Testing Strategy
### Testes Unitários
1. **CSVParser**
- Testar parsing de diferentes formatos
- Testar detecção de encoding
- Testar detecção de tipos
2. **ColumnMapper**
- Testar sugestão de mapeamento
- Testar validação de mapeamento
- Testar similaridade de strings
3. **DataValidator**
- Testar validação de tipos
- Testar validação de ranges
- Testar campos obrigatórios
4. **UnitConverter**
- Testar conversões conhecidas
- Testar detecção de unidades
- Testar erros de conversão
### Testes de Integração
1. **Fluxo Completo de Importação**
- Upload → Mapeamento → Validação → Importação
- Testar com CSVs reais
- Testar com perfis salvos
2. **Importação em Lote**
- Múltiplos arquivos
- Diferentes tipos de materiais
- Relatório consolidado
### Testes de Interface
1. **Drag & Drop de Colunas**
- Arrastar coluna CSV para campo
- Visual feedback
- Desfazer mapeamento
2. **Preview de Dados**
- Mostrar valores originais
- Mostrar valores convertidos
- Mostrar valores calculados
3. **Validação em Tempo Real**
- Destacar erros
- Mostrar estatísticas
- Atualizar ao modificar mapeamento
## Interface Design
### Tela Principal de Importação
```
┌─────────────────────────────────────────────────────────────┐
│ 📥 Importador Inteligente de CSV │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1⃣ Selecionar Arquivo │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 📁 Escolher CSV... │ │
│ │ ✅ tubos_rhs_fornecedor_x.csv (35 linhas, 15 cols) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2⃣ Tipo de Material │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ [Dropdown: Tubos RHS ▼] │ │
│ │ 💾 Perfil salvo encontrado: "Fornecedor X" │ │
│ │ [Usar Perfil] [Criar Novo] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3⃣ Mapeamento de Colunas │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CSV → Sistema │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ 📊 "Largura (mm)" → largura_mm ✅ │ │
│ │ 📊 "Altura (mm)" → altura_mm ✅ │ │
│ │ 📊 "Espessura" → espessura_mm ✅ │ │
│ │ 📊 "Peso Linear" → peso_kg_m ✅ │ │
│ │ 🔄 Converter: lb/ft → kg/m │ │
│ │ 📊 "Aplicação" → aplicacao ✅ │ │
│ │ 📊 "Coluna Extra" → [Ignorar] ⚠️ │ │
│ │ │ │
│ │ ⚠️ Campo obrigatório não mapeado: tipo │ │
│ │ 💡 Sugestão: Criar regra de categorização │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 4⃣ Preview e Validação │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 📊 Estatísticas: │ │
│ │ • Total: 35 linhas │ │
│ │ • Válidas: 33 ✅ │ │
│ │ • Com erro: 2 ❌ │ │
│ │ • Com aviso: 5 ⚠️ │ │
│ │ │ │
│ │ [Ver Erros] [Ver Preview] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ [Cancelar] [💾 Salvar Perfil] [📥 Importar Dados] │
└─────────────────────────────────────────────────────────────┘
```
### Modal de Mapeamento Visual
```
┌─────────────────────────────────────────────────────────────┐
│ 🎯 Mapeamento Visual de Colunas │
├─────────────────────────────────────────────────────────────┤
│ │
│ Arraste as colunas do CSV para os campos do sistema │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Colunas CSV │ │ Campos Sistema │ │
│ ├──────────────────┤ ├──────────────────┤ │
│ │ 📊 Largura │────────→│ ✅ largura_mm │ │
│ │ 📊 Altura │────────→│ ✅ altura_mm │ │
│ │ 📊 Espessura │────────→│ ✅ espessura_mm │ │
│ │ 📊 Peso Linear │────────→│ ✅ peso_kg_m │ │
│ │ 📊 Aplicação │────────→│ ✅ aplicacao │ │
│ │ 📊 Coluna Extra │ │ ⚠️ tipo │ │
│ │ │ │ (obrigatório) │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ 💡 Dica: Clique em "Sugerir Automático" para mapear │
│ │
│ [Sugerir Automático] [Limpar Tudo] [Confirmar] │
└─────────────────────────────────────────────────────────────┘
```
## Performance Considerations
1. **Parsing de CSV Grande**
- Usar Web Workers para não bloquear UI
- Processar em chunks de 1000 linhas
- Mostrar progresso
2. **Validação em Tempo Real**
- Debounce de 300ms ao modificar mapeamento
- Validar apenas primeiras 100 linhas para preview
- Validação completa apenas ao importar
3. **Armazenamento de Perfis**
- Comprimir perfis grandes (>100KB)
- Limitar a 50 perfis salvos
- Limpar perfis não usados há >6 meses
4. **Importação em Lote**
- Processar arquivos em paralelo (máx 3)
- Usar IndexedDB para arquivos >5MB
- Liberar memória após cada arquivo

View File

@@ -1,137 +0,0 @@
# Requirements Document
## Introduction
Sistema inteligente de importação de CSV que permite ao usuário mapear colunas de forma visual e memoriza as configurações para futuras importações automáticas.
## Glossary
- **CSV**: Arquivo de valores separados por vírgula contendo dados tabulares
- **Mapeamento de Colunas**: Correlação entre colunas do CSV e campos do sistema
- **Perfil de Importação**: Configuração salva de mapeamento para um tipo específico de material
- **Campo Obrigatório**: Campo que deve ser preenchido para importação válida
- **Campo Opcional**: Campo que pode ficar vazio sem impedir a importação
- **Sistema**: Aplicação AÇO CALC PRO
- **Usuário**: Engenheiro ou técnico que importa dados de materiais
## Requirements
### Requirement 1
**User Story:** Como usuário, quero importar um CSV com qualquer estrutura de colunas, para que eu possa usar dados de diferentes fornecedores sem precisar reformatar os arquivos.
#### Acceptance Criteria
1. WHEN o usuário seleciona um arquivo CSV, THE Sistema SHALL detectar automaticamente todas as colunas presentes
2. WHEN o Sistema detecta as colunas, THE Sistema SHALL exibir uma interface de mapeamento visual
3. WHEN o usuário visualiza o mapeamento, THE Sistema SHALL mostrar preview dos dados de cada coluna
4. WHERE existe um perfil de importação salvo, THE Sistema SHALL aplicar o mapeamento automaticamente
5. IF o CSV tem colunas não mapeadas, THEN THE Sistema SHALL permitir que o usuário as ignore ou mapeie
### Requirement 2
**User Story:** Como usuário, quero mapear visualmente as colunas do CSV para os campos do sistema, para que eu possa garantir que os dados sejam importados corretamente.
#### Acceptance Criteria
1. THE Sistema SHALL exibir lado a lado as colunas do CSV e os campos esperados
2. WHEN o usuário arrasta uma coluna CSV, THE Sistema SHALL permitir soltar sobre um campo do sistema
3. WHEN o usuário mapeia uma coluna, THE Sistema SHALL mostrar preview dos valores que serão importados
4. THE Sistema SHALL destacar campos obrigatórios que ainda não foram mapeados
5. WHEN todos os campos obrigatórios estão mapeados, THE Sistema SHALL habilitar o botão de importação
### Requirement 3
**User Story:** Como usuário, quero que o sistema sugira automaticamente o mapeamento de colunas, para que eu economize tempo em importações repetitivas.
#### Acceptance Criteria
1. WHEN o Sistema detecta colunas do CSV, THE Sistema SHALL comparar nomes com campos conhecidos
2. WHEN existe similaridade entre nomes, THE Sistema SHALL sugerir mapeamento automático
3. THE Sistema SHALL usar algoritmo de similaridade de strings (Levenshtein ou similar)
4. WHEN o Sistema sugere mapeamento, THE Sistema SHALL permitir que o usuário aceite ou modifique
5. THE Sistema SHALL considerar sinônimos comuns (ex: "diametro" = "diameter" = "diam")
### Requirement 4
**User Story:** Como usuário, quero que o sistema memorize meus mapeamentos de colunas, para que importações futuras do mesmo tipo sejam automáticas.
#### Acceptance Criteria
1. WHEN o usuário completa um mapeamento, THE Sistema SHALL salvar o perfil no localStorage
2. THE Sistema SHALL associar o perfil ao tipo de material (cantoneiras, tubos, etc)
3. WHEN o usuário importa novamente o mesmo tipo, THE Sistema SHALL aplicar o perfil automaticamente
4. THE Sistema SHALL permitir que o usuário edite ou delete perfis salvos
5. THE Sistema SHALL exportar/importar perfis de mapeamento em JSON
### Requirement 5
**User Story:** Como usuário, quero validar os dados antes da importação, para que eu possa corrigir erros antes de salvar no banco de dados.
#### Acceptance Criteria
1. WHEN o usuário mapeia as colunas, THE Sistema SHALL validar tipos de dados (números, texto)
2. WHEN existe erro de tipo, THE Sistema SHALL destacar a linha com erro
3. THE Sistema SHALL mostrar estatísticas de validação (X linhas válidas, Y com erro)
4. WHEN o usuário confirma importação, THE Sistema SHALL importar apenas linhas válidas
5. THE Sistema SHALL gerar relatório de erros com linhas que falharam
### Requirement 6
**User Story:** Como usuário, quero converter unidades automaticamente durante a importação, para que eu possa usar CSVs com diferentes sistemas de medida.
#### Acceptance Criteria
1. WHEN o Sistema detecta coluna numérica, THE Sistema SHALL permitir especificar unidade
2. THE Sistema SHALL suportar conversões: mm↔pol, kg↔lb, MPa↔ksi
3. WHEN o usuário seleciona conversão, THE Sistema SHALL aplicar fator de conversão automaticamente
4. THE Sistema SHALL mostrar preview dos valores convertidos
5. THE Sistema SHALL salvar preferência de conversão no perfil de importação
### Requirement 7
**User Story:** Como usuário, quero criar campos calculados durante a importação, para que eu possa derivar dados que não estão no CSV.
#### Acceptance Criteria
1. THE Sistema SHALL permitir criar campos calculados usando fórmulas
2. WHEN o usuário cria fórmula, THE Sistema SHALL validar sintaxe
3. THE Sistema SHALL suportar operações: +, -, *, /, potência, raiz
4. WHEN a fórmula é válida, THE Sistema SHALL calcular valores automaticamente
5. THE Sistema SHALL mostrar preview dos valores calculados
### Requirement 8
**User Story:** Como usuário, quero categorizar automaticamente os itens durante a importação, para que eu não precise adicionar manualmente a categoria de cada item.
#### Acceptance Criteria
1. THE Sistema SHALL permitir definir regras de categorização
2. WHEN o usuário define regra, THE Sistema SHALL usar condições (SE...ENTÃO)
3. THE Sistema SHALL suportar condições baseadas em valores numéricos e texto
4. WHEN a regra é aplicada, THE Sistema SHALL atribuir categoria automaticamente
5. THE Sistema SHALL mostrar preview das categorias atribuídas
### Requirement 9
**User Story:** Como usuário, quero importar múltiplos CSVs de uma vez, para que eu possa atualizar todo o banco de dados rapidamente.
#### Acceptance Criteria
1. THE Sistema SHALL permitir selecionar múltiplos arquivos CSV
2. WHEN múltiplos arquivos são selecionados, THE Sistema SHALL processar em lote
3. THE Sistema SHALL aplicar perfil de importação correspondente a cada arquivo
4. THE Sistema SHALL mostrar progresso de importação em tempo real
5. WHEN a importação em lote termina, THE Sistema SHALL gerar relatório consolidado
### Requirement 10
**User Story:** Como usuário, quero exportar o banco de dados atual para CSV, para que eu possa fazer backup ou compartilhar dados.
#### Acceptance Criteria
1. THE Sistema SHALL permitir exportar cada tipo de material para CSV
2. WHEN o usuário exporta, THE Sistema SHALL incluir todas as colunas do banco
3. THE Sistema SHALL usar formato compatível com reimportação
4. THE Sistema SHALL permitir exportar todos os tipos de uma vez (ZIP)
5. THE Sistema SHALL incluir metadata (data de exportação, versão)

View File

@@ -1,419 +0,0 @@
# Design Document - Sistema de Cache de Perfis
## Overview
Sistema de cache intermediário usando IndexedDB para armazenar dados de perfis estruturais. O sistema fornece uma camada de abstração entre os arquivos CSV originais e o aplicativo, permitindo carregamento rápido, sincronização sob demanda, e gerenciamento flexível de fontes de dados.
## Architecture
### High-Level Architecture
```
┌─────────────────────────────────────────────────────────┐
│ Application Layer │
│ (UI Components, Sections, Calculations) │
└────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Data Service API │
│ getPerfis() | searchPerfis() | getPerfilById() │
└────────────────────┬────────────────────────────────────┘
┌────────────┴────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Cache Layer │ │ CSV Loader │
│ (IndexedDB) │◄────────►│ (Fetch API) │
└──────────────┘ └──────────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Browser │ │ BD/perfis/ │
│ Storage │ │ *.csv │
└──────────────┘ └──────────────┘
```
### Component Diagram
```
┌─────────────────────────────────────────────────────────┐
│ CacheManager │
│ - init() │
│ - checkHealth() │
│ - clearAll() │
└────────────────────┬────────────────────────────────────┘
┌────────────┼────────────┬────────────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ DataStore│ │SyncManager│ │CSVParser │ │DataService│
│ │ │ │ │ │ │ │
│ -get() │ │ -sync() │ │ -parse() │ │ -query() │
│ -set() │ │ -check() │ │ -validate│ │ -filter()│
│ -delete()│ │ -update()│ │ │ │ -search()│
└──────────┘ └──────────┘ └──────────┘ └──────────┘
```
## Components and Interfaces
### 1. CacheManager (Core)
**Responsabilidade:** Gerenciar ciclo de vida do cache e coordenar componentes
```javascript
class CacheManager {
constructor(config) {
this.dbName = 'AcoCalcProDB';
this.version = 1;
this.db = null;
this.config = config;
}
async init() {
// Inicializar IndexedDB
// Criar object stores
// Verificar versão
}
async checkHealth() {
// Verificar integridade do cache
// Retornar estatísticas
}
async clearAll() {
// Limpar todo o cache
}
getStats() {
// Retornar estatísticas de uso
}
}
```
### 2. DataStore (Storage Layer)
**Responsabilidade:** Interface com IndexedDB
```javascript
class DataStore {
constructor(db) {
this.db = db;
}
async get(storeName, key) {
// Buscar item por chave
}
async getAll(storeName) {
// Buscar todos os itens
}
async set(storeName, data) {
// Armazenar dados
}
async delete(storeName, key) {
// Deletar item
}
async clear(storeName) {
// Limpar store
}
async count(storeName) {
// Contar itens
}
}
```
### 3. SyncManager (Synchronization)
**Responsabilidade:** Sincronizar dados entre CSV e cache
```javascript
class SyncManager {
constructor(dataStore, csvParser) {
this.dataStore = dataStore;
this.csvParser = csvParser;
this.sources = {}; // Mapeamento tipo -> caminho CSV
}
async syncAll(progressCallback) {
// Sincronizar todos os tipos
}
async syncType(tipo, progressCallback) {
// Sincronizar tipo específico
}
async checkUpdates() {
// Verificar se há atualizações disponíveis
}
async getLastSync(tipo) {
// Retornar timestamp da última sincronização
}
async calculateHash(csvText) {
// Calcular hash MD5 do CSV
}
}
```
### 4. CSVParser (Data Processing)
**Responsabilidade:** Parse e validação de arquivos CSV
```javascript
class CSVParser {
async parse(csvText, schema) {
// Parse CSV para objetos
// Validar contra schema
// Retornar dados processados
}
validate(data, schema) {
// Validar estrutura dos dados
}
transform(data, transformFn) {
// Aplicar transformações
}
}
```
### 5. DataService (API Layer)
**Responsabilidade:** API pública para acesso aos dados
```javascript
class DataService {
constructor(cacheManager, syncManager) {
this.cache = cacheManager;
this.sync = syncManager;
}
async getPerfis(tipo, options = {}) {
// Buscar perfis do cache ou CSV
// options: { forceRefresh, includeMetadata }
}
async searchPerfis(tipo, filters) {
// Buscar com filtros
// filters: { nome, peso_min, peso_max, etc }
}
async getPerfilById(tipo, id) {
// Buscar perfil específico
}
async getMetadata(tipo) {
// Retornar metadados (última sync, count, etc)
}
}
```
## Data Models
### IndexedDB Schema
```javascript
const DB_SCHEMA = {
name: 'AcoCalcProDB',
version: 1,
stores: {
// Store para cada tipo de perfil
cantoneiras: {
keyPath: 'id',
indexes: [
{ name: 'nome', keyPath: 'nome', unique: false },
{ name: 'tipo', keyPath: 'tipo', unique: false },
{ name: 'peso_kg_m', keyPath: 'peso_kg_m', unique: false }
]
},
perfis_w: {
keyPath: 'id',
indexes: [
{ name: 'nome', keyPath: 'nome', unique: false },
{ name: 'peso', keyPath: 'peso', unique: false }
]
},
// ... outros tipos
// Store para metadados
_metadata: {
keyPath: 'tipo',
indexes: []
},
// Store para configuração
_config: {
keyPath: 'key',
indexes: []
}
}
};
```
### Metadata Model
```javascript
{
tipo: 'cantoneiras',
lastSync: 1699564800000, // timestamp
hash: 'a1b2c3d4e5f6...', // MD5 hash do CSV
count: 39,
version: '1.0',
source: 'BD/perfis/cantoneiras_brasil_completo.csv',
size: 12345 // bytes
}
```
### Config Model
```javascript
{
key: 'csv_sources',
value: {
cantoneiras: 'BD/perfis/cantoneiras_brasil_completo.csv',
perfis_w: 'BD/perfis/perfis_w_brasil_completo.csv',
// ... outros
}
}
```
## Error Handling
### Error Types
```javascript
class CacheError extends Error {
constructor(message, code, details) {
super(message);
this.name = 'CacheError';
this.code = code;
this.details = details;
}
}
// Códigos de erro
const ERROR_CODES = {
DB_NOT_AVAILABLE: 'DB_NOT_AVAILABLE',
SYNC_FAILED: 'SYNC_FAILED',
CSV_PARSE_ERROR: 'CSV_PARSE_ERROR',
DATA_NOT_FOUND: 'DATA_NOT_FOUND',
QUOTA_EXCEEDED: 'QUOTA_EXCEEDED'
};
```
### Fallback Strategy
```
1. Tentar carregar do cache
↓ (falha)
2. Tentar carregar do CSV
↓ (falha)
3. Usar dados em memória (se disponível)
↓ (falha)
4. Exibir erro ao usuário
```
## Testing Strategy
### Unit Tests
- DataStore: CRUD operations
- CSVParser: Parse e validação
- SyncManager: Lógica de sincronização
- DataService: API pública
### Integration Tests
- Fluxo completo: CSV → Cache → UI
- Sincronização com múltiplos tipos
- Fallback quando IndexedDB não disponível
### Performance Tests
- Tempo de carregamento do cache
- Tempo de sincronização
- Uso de memória
- Tamanho do banco de dados
## Migration Strategy
### Phase 1: Implementação Base
- Criar estrutura do cache
- Implementar DataStore e CSVParser
- Testes unitários
### Phase 2: Integração
- Integrar com código existente
- Manter compatibilidade
- Testes de integração
### Phase 3: UI Admin
- Painel de administração
- Botões de sincronização
- Estatísticas
### Phase 4: Otimizações
- Web Workers para parsing
- Compressão de dados
- Lazy loading
## Configuration
### Default Configuration
```javascript
const DEFAULT_CONFIG = {
dbName: 'AcoCalcProDB',
version: 1,
autoSync: false, // Sincronizar automaticamente na inicialização
syncInterval: null, // Intervalo de sincronização automática (ms)
cacheExpiry: 7 * 24 * 60 * 60 * 1000, // 7 dias
enableCompression: false,
enableWebWorkers: false,
debug: false,
sources: {
cantoneiras: 'BD/perfis/cantoneiras_brasil_completo.csv',
barras: 'BD/perfis/barras_brasil_completo.csv',
// ... outros
}
};
```
## Performance Considerations
### Optimization Techniques
1. **Indexing**: Criar índices para campos frequentemente buscados
2. **Lazy Loading**: Carregar apenas dados necessários
3. **Compression**: Comprimir dados grandes antes de armazenar
4. **Web Workers**: Parse de CSV em background thread
5. **Batch Operations**: Inserir múltiplos registros de uma vez
### Expected Performance
- Cache hit: < 100ms
- Cache miss + CSV load: < 2s
- Sync all types: < 10s
- Search with filters: < 50ms
## Security Considerations
- IndexedDB é isolado por origem (same-origin policy)
- Dados não são criptografados (não contêm informações sensíveis)
- Validar dados do CSV antes de armazenar
- Limitar tamanho máximo do cache (50MB)
## Future Enhancements
1. **Sync com servidor remoto**: Permitir sincronizar com API REST
2. **Versionamento de dados**: Manter histórico de versões
3. **Diff e merge**: Detectar conflitos entre versões
4. **Export/Import**: Exportar cache para backup
5. **Compression**: Comprimir dados automaticamente
6. **Service Worker**: Cache de arquivos CSV para offline completo

View File

@@ -1,152 +0,0 @@
# Requirements Document - Sistema de Cache de Perfis
## Introduction
Sistema de cache intermediário para armazenar dados de perfis estruturais carregados de arquivos CSV. O sistema permitirá carregar dados uma vez, armazená-los localmente no navegador (IndexedDB), e fornecer uma interface para atualizar/sincronizar quando os arquivos CSV forem modificados.
## Glossary
- **IndexedDB**: Banco de dados NoSQL do navegador para armazenamento local persistente
- **CSV Source**: Arquivos CSV originais em `BD/perfis/`
- **Cache Layer**: Camada intermediária que armazena dados processados
- **Sync Manager**: Componente responsável por sincronizar dados entre CSV e cache
- **Data Service**: API unificada para acesso aos dados de perfis
- **Admin Panel**: Interface administrativa para gerenciar o cache
## Requirements
### Requirement 1: Armazenamento Local com IndexedDB
**User Story:** Como desenvolvedor, quero armazenar dados de perfis localmente no navegador, para que o aplicativo não precise carregar CSVs repetidamente.
#### Acceptance Criteria
1. WHEN o aplicativo inicializa, THE Sistema SHALL criar um banco IndexedDB chamado "AcoCalcProDB"
2. THE Sistema SHALL criar object stores separadas para cada tipo de perfil (cantoneiras, perfis_w, perfis_i, etc.)
3. THE Sistema SHALL armazenar metadados incluindo timestamp de última atualização e versão dos dados
4. THE Sistema SHALL persistir dados entre sessões do navegador
5. THE Sistema SHALL suportar até 50MB de dados armazenados
### Requirement 2: Carregamento Inteligente de Dados
**User Story:** Como usuário, quero que o aplicativo carregue dados rapidamente, para que eu não precise esperar toda vez que abrir uma seção.
#### Acceptance Criteria
1. WHEN o usuário acessa uma seção de perfil, THE Sistema SHALL verificar se dados existem no cache local
2. IF dados existem no cache, THEN THE Sistema SHALL carregar do cache em menos de 100ms
3. IF dados não existem no cache, THEN THE Sistema SHALL carregar do CSV e armazenar no cache
4. THE Sistema SHALL exibir indicador de loading durante carregamento inicial
5. THE Sistema SHALL funcionar offline após primeiro carregamento
### Requirement 3: Interface de Sincronização
**User Story:** Como administrador, quero atualizar os dados do cache quando modificar arquivos CSV, para que as alterações sejam refletidas no aplicativo.
#### Acceptance Criteria
1. THE Sistema SHALL fornecer botão "🔄 Atualizar Dados" no painel administrativo
2. WHEN usuário clica em atualizar, THE Sistema SHALL recarregar todos os CSVs
3. THE Sistema SHALL mostrar progresso da sincronização (0-100%)
4. THE Sistema SHALL exibir timestamp da última sincronização
5. THE Sistema SHALL permitir atualização seletiva por tipo de perfil
### Requirement 4: Versionamento e Validação
**User Story:** Como desenvolvedor, quero detectar quando dados estão desatualizados, para que o sistema possa atualizar automaticamente.
#### Acceptance Criteria
1. THE Sistema SHALL armazenar hash MD5 de cada arquivo CSV
2. WHEN aplicativo inicializa, THE Sistema SHALL comparar hashes dos arquivos
3. IF hash mudou, THEN THE Sistema SHALL marcar dados como desatualizados
4. THE Sistema SHALL exibir notificação quando dados estiverem desatualizados
5. THE Sistema SHALL permitir configurar atualização automática ou manual
### Requirement 5: API Unificada de Acesso
**User Story:** Como desenvolvedor, quero uma API simples para acessar dados, para que não precise me preocupar se vêm do cache ou CSV.
#### Acceptance Criteria
1. THE Sistema SHALL fornecer função `getPerfis(tipo)` que retorna dados do cache ou CSV
2. THE Sistema SHALL fornecer função `searchPerfis(tipo, filtros)` para busca com filtros
3. THE Sistema SHALL fornecer função `getPerfilById(tipo, id)` para busca por ID
4. THE Sistema SHALL retornar Promises para todas operações assíncronas
5. THE Sistema SHALL tratar erros gracefully com fallback para CSV
### Requirement 6: Gerenciamento de Espaço
**User Story:** Como usuário, quero gerenciar o espaço usado pelo cache, para que não ocupe muito armazenamento do navegador.
#### Acceptance Criteria
1. THE Sistema SHALL exibir espaço total usado pelo cache no painel admin
2. THE Sistema SHALL permitir limpar cache de tipos específicos de perfis
3. THE Sistema SHALL permitir limpar todo o cache com um botão
4. THE Sistema SHALL avisar quando espaço disponível for menor que 10MB
5. THE Sistema SHALL funcionar mesmo se IndexedDB não estiver disponível (fallback para CSV)
### Requirement 7: Migração e Compatibilidade
**User Story:** Como desenvolvedor, quero migrar dados existentes para o novo sistema, para que não haja perda de funcionalidade.
#### Acceptance Criteria
1. THE Sistema SHALL detectar se é primeira execução
2. WHEN primeira execução, THE Sistema SHALL carregar todos os CSVs e popular cache
3. THE Sistema SHALL manter compatibilidade com código existente
4. THE Sistema SHALL permitir desabilitar cache via configuração
5. THE Sistema SHALL migrar automaticamente entre versões do schema
### Requirement 8: Monitoramento e Debug
**User Story:** Como desenvolvedor, quero monitorar operações do cache, para que possa debugar problemas.
#### Acceptance Criteria
1. THE Sistema SHALL logar todas operações de cache no console (modo debug)
2. THE Sistema SHALL exibir estatísticas de hit/miss do cache
3. THE Sistema SHALL permitir exportar dados do cache para JSON
4. THE Sistema SHALL permitir importar dados de JSON para cache
5. THE Sistema SHALL fornecer ferramenta de diagnóstico no painel admin
### Requirement 9: Performance e Otimização
**User Story:** Como usuário, quero que o aplicativo seja rápido, para que eu possa trabalhar eficientemente.
#### Acceptance Criteria
1. THE Sistema SHALL carregar dados do cache em menos de 100ms
2. THE Sistema SHALL indexar campos comuns (nome, tipo) para busca rápida
3. THE Sistema SHALL usar Web Workers para parsing de CSV em background
4. THE Sistema SHALL implementar lazy loading para perfis não utilizados
5. THE Sistema SHALL comprimir dados antes de armazenar (se >1MB)
### Requirement 10: Configuração Flexível de Fontes
**User Story:** Como administrador, quero configurar caminhos dos arquivos CSV, para que possa reorganizar estrutura de pastas.
#### Acceptance Criteria
1. THE Sistema SHALL permitir configurar caminho base dos CSVs no painel admin
2. THE Sistema SHALL permitir configurar nome de arquivo para cada tipo de perfil
3. THE Sistema SHALL validar se arquivos existem antes de tentar carregar
4. THE Sistema SHALL salvar configuração no localStorage
5. THE Sistema SHALL fornecer configuração padrão funcional
## Technical Constraints
- Deve funcionar em navegadores modernos (Chrome 60+, Firefox 60+, Safari 12+, Edge 79+)
- Deve usar apenas JavaScript vanilla (sem frameworks)
- Deve ser compatível com código existente
- Deve funcionar offline após primeiro carregamento
- Deve ter fallback para CSV se IndexedDB não disponível
## Non-Functional Requirements
- **Performance**: Carregamento do cache < 100ms
- **Reliability**: Taxa de sucesso > 99.9%
- **Usability**: Interface intuitiva no painel admin
- **Maintainability**: Código modular e bem documentado
- **Scalability**: Suportar até 10.000 registros por tipo de perfil

View File

@@ -1,168 +0,0 @@
# Implementation Plan - Sistema de Cache de Perfis
## Overview
Este plano implementa um sistema de cache intermediário usando IndexedDB para armazenar dados de perfis estruturais, permitindo carregamento rápido, sincronização sob demanda, e gerenciamento flexível de fontes de dados.
## Tasks
- [ ] 1. Criar estrutura base do sistema de cache
- Criar arquivo `js/core/cache-manager.js` com classe CacheManager
- Implementar inicialização do IndexedDB
- Criar schema do banco de dados com stores para cada tipo de perfil
- Implementar tratamento de erros e fallback
- _Requirements: 1.1, 1.2, 1.3, 1.4_
- [ ] 2. Implementar DataStore (camada de armazenamento)
- Criar arquivo `js/core/data-store.js` com classe DataStore
- Implementar operações CRUD (get, getAll, set, delete, clear)
- Implementar contagem de registros
- Adicionar suporte a índices para busca rápida
- _Requirements: 1.2, 1.3, 9.2_
- [ ] 3. Implementar CSVParser (processamento de dados)
- Criar arquivo `js/core/csv-parser.js` com classe CSVParser
- Implementar parse de CSV para objetos JavaScript
- Adicionar validação de schema
- Implementar transformações de dados (trim, parseFloat, etc)
- Tratar linhas vazias e caracteres especiais
- _Requirements: 2.3, 4.2_
- [ ] 4. Implementar SyncManager (sincronização)
- Criar arquivo `js/core/sync-manager.js` com classe SyncManager
- Implementar sincronização de todos os tipos de perfis
- Implementar sincronização seletiva por tipo
- Adicionar cálculo de hash MD5 para detecção de mudanças
- Implementar callback de progresso
- Armazenar metadados de sincronização (timestamp, hash, count)
- _Requirements: 3.1, 3.2, 3.3, 4.1, 4.2, 4.3_
- [ ] 5. Implementar DataService (API pública)
- Criar arquivo `js/core/data-service.js` com classe DataService
- Implementar `getPerfis(tipo, options)` com fallback para CSV
- Implementar `searchPerfis(tipo, filters)` com suporte a múltiplos filtros
- Implementar `getPerfilById(tipo, id)` para busca por ID
- Implementar `getMetadata(tipo)` para informações de sincronização
- Adicionar cache em memória para dados frequentemente acessados
- _Requirements: 2.1, 2.2, 5.1, 5.2, 5.3, 5.5_
- [ ] 6. Integrar sistema de cache com código existente
- Atualizar `app.js` para inicializar CacheManager na inicialização
- Modificar `carregarCantoneiras()` para usar DataService
- Adicionar tratamento de erros com fallback para CSV
- Manter compatibilidade com código existente
- Testar carregamento de cantoneiras com cache
- _Requirements: 2.1, 2.2, 2.4, 7.3, 7.4_
- [ ] 7. Criar interface de administração do cache
- Adicionar seção "Cache Manager" no painel administrativo
- Criar botão "🔄 Sincronizar Todos" com barra de progresso
- Exibir estatísticas de cache (espaço usado, última sync, count)
- Adicionar botões para sincronizar tipos individuais
- Implementar botão "🗑️ Limpar Cache" com confirmação
- Exibir timestamp da última sincronização por tipo
- _Requirements: 3.1, 3.2, 3.3, 3.4, 6.1, 6.2, 6.3_
- [ ] 8. Implementar configuração de fontes de dados
- Adicionar seção "Configurar Fontes CSV" no painel admin
- Permitir editar caminho base dos CSVs
- Permitir editar nome de arquivo para cada tipo
- Validar se arquivos existem antes de salvar
- Salvar configuração no localStorage
- Fornecer botão "Restaurar Padrões"
- _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5_
- [ ] 9. Implementar detecção automática de atualizações
- Calcular hash MD5 dos arquivos CSV na inicialização
- Comparar com hash armazenado no cache
- Exibir notificação quando dados estiverem desatualizados
- Adicionar opção de atualização automática ou manual
- Implementar configuração de intervalo de verificação
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
- [ ] 10. Adicionar ferramentas de diagnóstico e debug
- Criar seção "Diagnóstico" no painel admin
- Exibir estatísticas de hit/miss do cache
- Implementar botão "Exportar Cache" (JSON)
- Implementar botão "Importar Cache" (JSON)
- Adicionar modo debug com logs detalhados no console
- Exibir informações de saúde do IndexedDB
- _Requirements: 8.1, 8.2, 8.3, 8.4, 8.5_
- [ ] 11. Implementar gerenciamento de espaço
- Calcular espaço total usado pelo cache
- Exibir espaço disponível no navegador
- Implementar aviso quando espaço < 10MB
- Adicionar botão para limpar tipos específicos
- Implementar limpeza automática de dados antigos (opcional)
- _Requirements: 6.1, 6.2, 6.3, 6.4_
- [ ] 12. Implementar fallback e compatibilidade
- Detectar se IndexedDB está disponível
- Implementar fallback completo para CSV quando IndexedDB não disponível
- Adicionar opção para desabilitar cache via configuração
- Garantir que aplicativo funciona sem cache
- Testar em navegadores sem IndexedDB
- _Requirements: 6.5, 7.4, 7.5_
- [ ] 13. Otimizações de performance
- Implementar lazy loading para perfis não utilizados
- Adicionar cache em memória para dados recentes
- Otimizar queries com índices apropriados
- Implementar batch insert para sincronização rápida
- Medir e otimizar tempo de carregamento
- _Requirements: 9.1, 9.2, 9.4, 9.5_
- [ ]* 14. Implementar Web Workers para parsing (opcional)
- Criar Web Worker para parse de CSV em background
- Mover processamento pesado para worker thread
- Implementar comunicação via postMessage
- Testar performance com arquivos grandes
- _Requirements: 9.3_
- [ ]* 15. Implementar compressão de dados (opcional)
- Adicionar biblioteca de compressão (pako.js ou similar)
- Comprimir dados antes de armazenar se > 1MB
- Descomprimir ao carregar
- Medir ganho de espaço vs overhead de CPU
- _Requirements: 9.5_
- [ ] 16. Criar documentação do sistema
- Documentar API pública do DataService
- Criar guia de uso para desenvolvedores
- Documentar configurações disponíveis
- Adicionar exemplos de uso
- Documentar troubleshooting comum
- _Requirements: Todos_
- [ ] 17. Aplicar sistema de cache a todos os tipos de perfis
- Atualizar funções de carregamento de barras redondas
- Atualizar funções de carregamento de tubos circulares
- Atualizar funções de carregamento de perfis I
- Atualizar funções de carregamento de perfis W
- Atualizar funções de carregamento de tubos RHS
- Atualizar funções de carregamento de chapas
- Atualizar funções de carregamento de perfis HP
- Atualizar funções de carregamento de barras roscadas
- Atualizar funções de carregamento de barras chatas
- _Requirements: 2.1, 2.2, 5.1_
- [ ] 18. Testes de integração e validação final
- Testar carregamento inicial (primeira vez)
- Testar carregamento do cache (segunda vez)
- Testar sincronização manual
- Testar detecção de atualizações
- Testar fallback quando CSV não disponível
- Testar limpeza de cache
- Testar configuração de fontes
- Validar performance (< 100ms para cache hit)
- _Requirements: Todos_
## Notes
- Tarefas marcadas com * são opcionais e podem ser implementadas posteriormente
- Cada tarefa deve ser testada individualmente antes de prosseguir
- Manter compatibilidade com código existente durante toda implementação
- Priorizar funcionalidade básica antes de otimizações avançadas

View File

@@ -1,24 +0,0 @@
# Product Overview
**AÇO CALC PRO** is a professional structural steel engineering calculation platform designed for Brazilian engineers and construction professionals.
## Purpose
A comprehensive web-based tool for structural steel calculations, material selection, welding analysis, and cost estimation. The application provides technical calculations following Brazilian (NBR), American (ASTM/AWS), and European (EN) standards.
## Key Features
- **Materials Analysis**: CEV calculations, steel selection, international equivalencies, material comparisons
- **Connections**: Bolted connections, drilling layouts, bolt vs weld comparisons
- **Welding**: Preheat calculations, fillet weld design, heat input analysis, electrode consumption
- **Testing**: Hardness conversion, Charpy analysis, certificate checklists, ultrasound interpretation
- **Coating**: Paint area calculations, consumption estimates, galvanization analysis, cost estimation
- **Budgeting**: Detailed cost estimation, weight and rigging calculations, technical references
## Target Users
Structural engineers, welding engineers, construction managers, and technical professionals working with steel structures in Brazil.
## Language
Portuguese (pt-BR) - All UI, calculations, and documentation are in Brazilian Portuguese.

View File

@@ -1,100 +0,0 @@
# Project Structure
## File Organization
```
/
├── index.html # Main application entry point
├── app.js # UI logic and navigation
├── calculations.js # Calculation engine
├── style.css # Complete design system
├── BD/ # Material database (CSV)
│ ├── perfis_w.csv
│ ├── perfis_i.csv
│ ├── cantoneiras.csv
│ ├── tubos_circulares.csv
│ ├── tubos_rhs.csv
│ ├── chapas.csv
│ ├── barras.csv
│ ├── eletrodos.csv
│ ├── parafusos.csv
│ └── tintas.csv
├── ORIGINAL/ # Backup of original files
└── aco-calc-pro-v7-5.zip # Archive
```
## Code Organization
### app.js
- Application state management (`appState`, `adminConfig`)
- Navigation and section switching
- Modal management (history, favorites, admin, help)
- Theme toggling (light/dark)
- Expert mode functionality
- CSV loading and parsing
- Material database (in-memory fallback)
- Content generation functions for each section
### calculations.js
- Calculation functions for all engineering tools
- Result formatting and display
- Chart generation (Charpy curves)
- History tracking
- Input validation
### style.css
- Design system with CSS variables
- Theme support (light/dark modes)
- Responsive layout
- Component styles (buttons, forms, cards, modals)
- Utility classes
## Key Patterns
### Section Loading
Each tool section has a content generator function (e.g., `getCEVContent()`, `getParafusosContent()`) that returns HTML strings dynamically injected into the main content area.
### State Management
Global objects store application state:
- `appState`: Current section, theme, mode, history, favorites, budget items
- `adminConfig`: Branding, tool visibility, preferences
### CSV Data Loading
Materials are loaded from CSV files with fallback to in-memory database:
1. Attempt to fetch CSV from `/BD/` directory
2. Parse CSV into JavaScript objects
3. Fall back to `materialsDatabase` if fetch fails
### Calculation Flow
1. User inputs values in form
2. Calculation function reads inputs
3. Performs calculations following engineering standards
4. Generates formatted result HTML
5. Adds entry to history
## Naming Conventions
- **Functions**: camelCase (e.g., `calcularPreaquecimento`, `showSection`)
- **Variables**: camelCase (e.g., `currentChart`, `appState`)
- **CSS Classes**: kebab-case (e.g., `sidebar-item`, `result-box`)
- **IDs**: kebab-case (e.g., `main-content`, `history-modal`)
- **Constants**: camelCase for objects (e.g., `steelDatabase`, `regionalPricing`)
## Modular Sections
The application is organized into 6 main categories:
1. **MATERIAIS** (Materials): CEV, steel selector, equivalencies, comparisons
2. **CONEXÕES** (Connections): Bolted connections, drilling layouts, bolt vs weld
3. **SOLDAGEM** (Welding): Preheat, fillet welds, heat input, electrode consumption
4. **ENSAIOS** (Testing): Hardness, Charpy, certificates, ultrasound
5. **PINTURA** (Coating): Area calculation, paint consumption, galvanization, costs
6. **ORÇAMENTO** (Budget): Detailed budgets, weight/rigging, technical references
## Extension Points
To add a new calculation tool:
1. Add sidebar item in `index.html`
2. Create content generator function in `app.js` (e.g., `getNewToolContent()`)
3. Add calculation function in `calculations.js`
4. Add section case to `loadSectionContent()` switch
5. Update `adminConfig.toolsVisibility` if needed

View File

@@ -1,71 +0,0 @@
# Technical Stack
## Architecture
Single-page application (SPA) with vanilla JavaScript - no build system or framework dependencies.
## Core Technologies
- **HTML5**: Semantic markup, modern web standards
- **CSS3**: Custom design system with CSS variables, dark/light theme support
- **Vanilla JavaScript**: ES6+ features, no frameworks
- **Chart.js**: Data visualization (loaded via CDN)
## External Dependencies
```html
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
```
## Data Storage
- **In-memory state**: All application state stored in JavaScript objects (`appState`, `adminConfig`)
- **CSV files**: Material database stored in `/BD/*.csv` directory
- **No backend**: Pure client-side application, no server required
- **No localStorage**: Data is not persisted between sessions
## File Structure
```
/
├── index.html # Main HTML structure
├── app.js # UI logic, navigation, modals
├── calculations.js # Calculation functions
├── style.css # Complete styling with design system
└── BD/ # Material database (CSV files)
├── perfis_w.csv
├── perfis_i.csv
├── cantoneiras.csv
├── tubos_circulares.csv
├── tubos_rhs.csv
├── chapas.csv
├── barras.csv
├── eletrodos.csv
├── parafusos.csv
└── tintas.csv
```
## Running the Application
**Development**: Open `index.html` directly in a browser or use a local web server:
```bash
# Python 3
python -m http.server 8000
# Node.js (http-server)
npx http-server
# PHP
php -S localhost:8000
```
**Production**: Deploy to any static hosting service (GitHub Pages, Netlify, Vercel, etc.)
## Browser Compatibility
Modern browsers with ES6+ support required (Chrome 60+, Firefox 60+, Safari 12+, Edge 79+).
## No Build Process
The application runs directly in the browser without compilation, bundling, or transpilation.

View File

@@ -1,300 +0,0 @@
# 📋 Requisitos Técnicos - Sistema de Persistência Robustas
## 🎯 Objetivo
Implementar um sistema de persistência robusto que garanta que todas as personalizações e configurações do painel administrativo do Aço Calc Pro permaneçam inalteradas durante o uso e recarregamento do aplicativo.
## 📊 Situação Atual
### **Sistema Existente**
- ✅ Data Manager com cache localStorage (24h TTL)
- ✅ Storage Manager para preferências básicas
- ✅ Cache Manager com IndexedDB
-**Admin Panel sem persistência de configurações**
### **Problemas Identificados**
1. Configurações do admin panel não são salvas
2. Sem sistema de backup/restauração
3. Sem migração de dados entre versões
4. Sem monitoramento de integridade
5. Falta feedback visual de persistência
## 🏗️ Requisitos Funcionais
### **RF01 - Sistema de Configurações Administrativas**
- [ ] Criar sistema centralizado de configurações admin
- [ ] Persistir nome do aplicativo
- [ ] Persistir subtítulo do aplicativo
- [ ] Persistir texto do rodapé
- [ ] Persistir tema padrão (escuro/claro)
- [ ] Persistir modo padrão (simples/experto)
- [ ] Persistir visibilidade de cada ferramenta (20+ ferramentas)
- [ ] Persistir intervalo de atualização de dados
- [ ] Persistir configurações de backup automático
### **RF02 - Interface de Configuração**
- [ ] Adicionar seção de configurações no painel admin
- [ ] Criar formulários para cada tipo de configuração
- [ ] Implementar salvamento automático (onchange)
- [ ] Adicionar botão de reset para configurações padrão
- [ ] Mostrar preview das mudanças em tempo real
- [ ] Adicionar validação de inputs
### **RF03 - Sistema de Backup/Restauração**
- [ ] Criar backup automático antes de mudanças significativas
- [ ] Implementar backup manual via botão
- [ ] Criar lista de backups disponíveis
- [ ] Implementar restauração seletiva por backup
- [ ] Adicionar confirmação antes de restaurar
- [ ] Limitar número de backups (máx 5)
- [ ] Exportar backup como arquivo JSON
### **RF04 - Persistência de Estado da UI**
- [ ] Salvar última aba ativa do sidebar
- [ ] Salvar estado de colapsos/expansões
- [ ] Salvar filtros aplicados em cada seção
- [ ] Salvar ordenação de tabelas
- [ ] Salvar tamanho de colunas redimensionadas
- [ ] Salvar posição de scroll quando relevante
### **RF05 - Sistema de Migração**
- [ ] Detectar versão atual vs salva
- [ ] Executar migrações automáticas quando necessário
- [ ] Criar logs de migração
- [ ] Implementar rollback em caso de falha
- [ ] Notificar usuário sobre migrações realizadas
### **RF06 - Monitoramento e Debugging**
- [ ] Criar dashboard de saúde do sistema
- [ ] Mostrar uso de storage (localStorage/IndexedDB)
- [ ] Exibir últimas operações realizadas
- [ ] Adicionar sistema de logs persistentes
- [ ] Exportar logs para troubleshooting
- [ ] Mostrar erros de forma amigável
### **RF07 - Feedback Visual**
- [ ] Adicionar toast notifications para ações
- [ ] Mostrar indicador de salvamento
- [ ] Adicionar animações de sucesso
- [ ] Mostrar progresso de operações longas
- [ ] Adicionar indicadores de erro
## 🔧 Requisitos Técnicos
### **RT01 - Armazenamento**
- **localStorage**: Para configurações pequenas (< 5MB)
- **IndexedDB**: Para dados grandes e backups
- **Fallback**: Funcionar sem storage (modo básico)
- **Quota**: Monitorar e respeitar limites do navegador
- **Validação**: Validar dados antes de salvar
### **RT02 - Performance**
- **Salvamento**: < 100ms para configurações simples
- **Carregamento**: < 200ms para restaurar estado completo
- **Backup**: < 1s para criar backup completo
- **Memória**: < 10MB adicionais em memória
- **CPU**: Operações assíncronas, não bloquear UI
### **RT03 - Segurança**
- **Sanitização**: Sanitizar todos os inputs
- **Validação**: Validar tipos e ranges de dados
- **Escopo**: Isolar dados por aplicação
- **Limpeza**: Remover dados sensíveis de logs
### **RT04 - Compatibilidade**
- **Browsers**: Chrome 60+, Firefox 55+, Safari 11+, Edge 79+
- **Mobile**: Suporte para navegadores mobile modernos
- **Storage**: Detectar e lidar com storage indisponível
- **Fallback**: Funcionalidade básica sem storage
### **RT05 - Manutenibilidade**
- **Modular**: Cada sistema em classe separada
- **Documentado**: JSDoc completo para todas funções
- **Testável**: Funções puras quando possível
- **Logs**: Logging adequado para debugging
## 📐 Arquitetura Proposta
### **Estrutura de Classes**
```javascript
// Gerenciadores principais
class AdminConfigManager // Configurações administrativas
class BackupManager // Backup e restauração
class MigrationManager // Migração entre versões
class PersistentLogger // Logs persistentes
class SystemHealthMonitor // Monitoramento de saúde
// Integração com existente
class EnhancedDataManager extends DataManager
class EnhancedStorage extends Storage
```
### **Estrutura de Dados**
```javascript
// Config admin persistida
interface AdminConfig {
version: string;
appName: string;
appSubtitle: string;
footerText: string;
themeDefault: 'escuro' | 'claro';
modeDefault: 'simples' | 'experto';
toolsVisibility: Record<string, boolean>;
dataRefreshInterval: number; // horas
autoBackup: boolean;
backupInterval: number; // dias
lastBackup: number | null;
uiState: {
activeSidebarTab: number;
collapsedSections: string[];
appliedFilters: Record<string, any>;
tableSort: Record<string, { column: string; direction: 'asc' | 'desc' }>;
columnWidths: Record<string, number>;
scrollPositions: Record<string, number>;
};
}
// Backup completo
interface BackupData {
timestamp: number;
version: string;
config: AdminConfig;
preferences: UserPreferences;
cacheStats: CacheStats;
logs: LogEntry[];
}
```
## 🎨 Interface de Usuário
### **Novos Elementos no Admin Panel**
1. **Aba "Configurações"**
- Formulário de config gerais
- Toggle switches para ferramentas
- Inputs para textos customizados
- Selects para temas e modos
2. **Aba "Backup & Restore"**
- Botão "Criar Backup Agora"
- Lista de backups existentes
- Botões "Restaurar" para cada backup
- Botão "Exportar Config"
- Botão "Importar Config"
3. **Aba "System Health"**
- Uso de storage (gráfico/barra)
- Status de cada componente
- Últimas operações
- Botão "Exportar Logs"
- Botão "Limpar Tudo"
### **Toast Notifications**
```javascript
// Novo sistema de notificações
class ToastManager {
show(message, type = 'info', duration = 3000) {
// Criar e exibir toast
}
success(message) { this.show(message, 'success'); }
error(message) { this.show(message, 'error', 5000); }
warning(message) { this.show(message, 'warning', 4000); }
info(message) { this.show(message, 'info'); }
}
```
## 🧪 Testes Requeridos
### **Testes Unitários**
- [ ] AdminConfigManager.saveConfig() persiste corretamente
- [ ] AdminConfigManager.getConfig() retorna valores salvos
- [ ] BackupManager.createBackup() cria backup válido
- [ ] BackupManager.restoreBackup() restaura corretamente
- [ ] MigrationManager detecta e executa migrações
### **Testes de Integração**
- [ ] Configurações sobrevivem a reload da página
- [ ] Backup é criado antes de mudanças críticas
- [ ] Restauração funciona de backups antigos
- [ ] UI reflete mudanças de configuração
- [ ] Performance dentro dos limites especificados
### **Testes de Aceitação**
- [ ] Usuário consegue alterar nome do app
- [ ] Usuário consegue mostrar/ocultar ferramentas
- [ ] Usuário consegue criar e restaurar backup
- [ ] Usuário recebe feedback de ações
- [ ] Sistema funciona após reload do navegador
## 📋 Checklist de Implementação
### **Fase 1 - Core (Semana 1)**
- [ ] Criar AdminConfigManager
- [ ] Adicionar seção de config no admin panel
- [ ] Implementar salvamento de config básicas
- [ ] Adicionar feedback visual básico
### **Fase 2 - Backup (Semana 2)**
- [ ] Criar BackupManager
- [ ] Adicionar UI de backup/restauração
- [ ] Implementar backup automático
- [ ] Adicionar exportação/importação
### **Fase 3 - Estado UI (Semana 3)**
- [ ] Implementar persistência de estado da UI
- [ ] Adicionar sistema de logs
- [ ] Criar dashboard de saúde
- [ ] Adicionar monitoramento
### **Fase 4 - Polish (Semana 4)**
- [ ] Adicionar sistema de migração
- [ ] Implementar toast notifications completas
- [ ] Adicionar animações e transições
- [ ] Escrever testes e documentação
## 🚨 Riscos e Mitigação
### **Risco: Storage Insuficiente**
- **Mitigação**: Monitorar uso, limpar dados antigos, usar IndexedDB
- **Fallback**: Funcionalidade básica sem persistência
### **Risco: Performance Degradada**
- **Mitigação**: Operações assíncronas, lazy loading, cache
- **Monitoramento**: Métricas de performance em tempo real
### **Risco: Dados Corrompidos**
- **Mitigação**: Validação rigorosa, backups frequentes, rollback
- **Recuperação**: Sistema de migração e restauração
### **Risco: Complexidade Excessiva**
- **Mitigação**: Implementação incremental, código modular
- **Documentação**: Documentação clara e exemplos
## 📊 Métricas de Sucesso
### **Funcionais**
- 100% das configurações persistem após reload
- 0% perda de dados durante operações normais
- < 1 segundo para criar backup completo
- < 200ms para aplicar configurações
### **Qualidade**
- 0 erros críticos em produção
- > 90% cobertura de testes para core
- Código com complexidade ciclomática < 10
- Documentação 100% atualizada
### **Usabilidade**
- Usuário consegue completar tarefas sem documentação
- Feedback visual em < 100ms
- Interface intuitiva e consistente
- Erros claros e acionáveis
---
**Documento de Requisitos** - Versão 1.0
**Data**: Janeiro 2025
**Status**: Aprovado para Implementação

View File

@@ -1,549 +0,0 @@
# 🗄️ Sistema de Persistência de Dados - Aço Calc Pro
## 📋 Visão Geral
Este documento técnico descreve a arquitetura completa do sistema de persistência de dados do Aço Calc Pro, garantindo que todas as personalizações e configurações do painel administrativo permaneçam inalteradas durante o uso e recarregamento do aplicativo.
## 🏗️ Arquitetura Atual de Persistência
### 1. **Camadas de Armazenamento**
```mermaid
graph TD
A[Aplicação] --> B[State Manager]
B --> C[Storage Layer]
C --> D[localStorage]
C --> E[IndexedDB]
C --> F[Cache Manager]
subgraph "Persistência Local"
D
E
F
end
G[Admin Panel] --> H[Data Manager]
H --> C
I[User Preferences] --> C
```
### 2. **Componentes Principais**
#### **Data Manager** (`js/database/data-manager.js`)
- **Função**: Gerenciamento central de dados e cache inteligente
- **Armazenamento**: localStorage com prefixo `acoCalcPro_cache_`
- **TTL**: 24 horas para atualização automática
- **Versionamento**: Detecta mudanças e atualiza automaticamente
```javascript
// Estrutura de cache
localStorage.setItem('acoCalcPro_cache_cantoneiras', JSON.stringify(dados));
localStorage.setItem('acoCalcPro_metadata', JSON.stringify(metadata));
```
#### **Storage Manager** (`js/core/storage.js`)
- **Função**: Gerenciamento de preferências do usuário
- **Armazenamento**: localStorage com chave `acoCalcPreferences`
- **Dados**: tema, esquema de cores, tamanho de fonte, família de fonte
```javascript
// Estrutura de preferências
const userPreferences = {
theme: 'dark',
colorScheme: 'default',
fontSize: 'medium',
fontFamily: 'default'
};
```
#### **Cache Manager** (`js/core/cache-manager.js`)
- **Função**: Gerenciamento avançado com IndexedDB
- **Armazenamento**: IndexedDB para grandes volumes de dados
- **Fallback**: CSV embutido quando IndexedDB não disponível
- **Stores**: cantoneiras, barras, tubos, perfis, etc.
#### **State Manager** (`js/core/state.js`)
- **Função**: Estado global da aplicação
- **Dados**: histórico, favoritos, orçamento, seção atual
- **Persistência**: Integrado com Storage Manager
#### **Admin Panel** (`js/database/admin-panel.js`)
- **Função**: Interface de administração e configurações
- **Estado**: Não persiste configurações atualmente
- **Ações**: Atualizar dados, limpar cache, exportar/importar
## 🔍 Análise de Persistência Atual
### **Pontos Fortes**
1.**Cache Inteligente**: Dados carregados uma vez, reusados múltiplas vezes
2.**Versionamento Automático**: Detecta e aplica atualizações
3.**Multipla Camadas**: localStorage, IndexedDB e CSV como fallback
4.**Performance Otimizada**: Carregamento rápido após primeiro acesso
### **Pontos de Fragilidade**
1.**Admin Panel Sem Persistência**: Configurações administrativas não são salvas
2.**Dependência de localStorage**: Limitado a ~5-10MB
3.**Sem Sincronização**: Sem backup em nuvem ou sincronização entre dispositivos
4.**Falta de Migração**: Sem sistema de migração de dados entre versões
## 🎯 Proposta de Arquitetura Robustas
### 1. **Sistema de Configurações Persistentes**
```javascript
// Novo sistema de configurações administrativas
class AdminConfigManager {
constructor() {
this.configKey = 'acoCalcPro_admin_config';
this.version = '1.0.0';
this.defaultConfig = {
appName: 'AÇO CALC PRO',
appSubtitle: 'Plataforma Técnica com Base de Dados de Materiais Brasileiros',
footerText: '© 2025 AÇO CALC PRO v7.5 PROFESSIONAL EDITION',
themeDefault: 'escuro',
modeDefault: 'simples',
toolsVisibility: {
'cev': true,
'seletor': true,
'equivalencias': false,
'comparativo': false,
'parafusos': true,
'layout': true,
// ... todas as ferramentas
},
dataRefreshInterval: 24, // horas
autoBackup: true,
backupInterval: 7, // dias
lastBackup: null,
version: this.version
};
}
saveConfig(config) {
const configToSave = { ...this.getConfig(), ...config, version: this.version };
localStorage.setItem(this.configKey, JSON.stringify(configToSave));
return configToSave;
}
getConfig() {
try {
const saved = localStorage.getItem(this.configKey);
return saved ? JSON.parse(saved) : this.defaultConfig;
} catch {
return this.defaultConfig;
}
}
resetConfig() {
localStorage.setItem(this.configKey, JSON.stringify(this.defaultConfig));
return this.defaultConfig;
}
}
```
### 2. **Sistema de Backup e Restauração**
```javascript
class BackupManager {
constructor() {
this.backupKey = 'acoCalcPro_backup';
this.maxBackups = 5;
}
async createBackup() {
const backup = {
timestamp: Date.now(),
version: '1.0.0',
data: {
preferences: this.getPreferences(),
adminConfig: this.getAdminConfig(),
cacheStats: window.dataManager.getCacheStats(),
appState: this.getAppState()
}
};
// Salvar em localStorage
const backups = this.getBackups();
backups.unshift(backup);
// Limitar número de backups
if (backups.length > this.maxBackups) {
backups.splice(this.maxBackups);
}
localStorage.setItem(this.backupKey, JSON.stringify(backups));
return backup;
}
getBackups() {
try {
const saved = localStorage.getItem(this.backupKey);
return saved ? JSON.parse(saved) : [];
} catch {
return [];
}
}
restoreBackup(backup) {
// Restaurar preferências
if (backup.data.preferences) {
localStorage.setItem('acoCalcPreferences', JSON.stringify(backup.data.preferences));
}
// Restaurar configurações admin
if (backup.data.adminConfig) {
localStorage.setItem('acoCalcPro_admin_config', JSON.stringify(backup.data.adminConfig));
}
// Aplicar mudanças
this.applyRestoredSettings();
return true;
}
}
```
### 3. **Sistema de Migração de Dados**
```javascript
class MigrationManager {
constructor() {
this.migrationKey = 'acoCalcPro_migration_version';
this.currentVersion = '1.0.0';
}
checkAndRunMigrations() {
const savedVersion = localStorage.getItem(this.migrationKey) || '0.0.0';
if (savedVersion < this.currentVersion) {
console.log(`🔄 Executando migração de ${savedVersion} para ${this.currentVersion}`);
this.runMigrations(savedVersion);
localStorage.setItem(this.migrationKey, this.currentVersion);
}
}
runMigrations(fromVersion) {
// Migrar de versões antigas
if (fromVersion < '1.0.0') {
this.migrateToV1();
}
}
migrateToV1() {
// Converter configurações antigas
const oldConfig = localStorage.getItem('acoCalcPro_config');
if (oldConfig) {
try {
const parsed = JSON.parse(oldConfig);
// Converter para novo formato
const newConfig = this.convertToNewFormat(parsed);
localStorage.setItem('acoCalcPro_admin_config', JSON.stringify(newConfig));
localStorage.removeItem('acoCalcPro_config');
} catch (e) {
console.warn('⚠️ Falha ao migrar configurações antigas');
}
}
}
}
```
### 4. **Integração com Admin Panel**
```javascript
// Atualizar admin-panel.js para persistir configurações
function abrirPainelDados() {
const adminConfig = window.adminConfigManager.getConfig();
const stats = window.dataManager.getCacheStats();
// Adicionar seções de configuração no modal
const configSection = `
<div class="card" style="background: var(--color-bg-1); margin-bottom: 20px;">
<h3 style="color: var(--color-primary); margin: 0 0 16px 0;">⚙️ Configurações do Sistema</h3>
<div style="display: grid; gap: 12px;">
<div>
<label>Nome do Aplicativo:</label>
<input type="text" id="admin-app-name" value="${adminConfig.appName}"
onchange="salvarConfigAdmin('appName', this.value)">
</div>
<div>
<label>Tema Padrão:</label>
<select id="admin-theme-default" onchange="salvarConfigAdmin('themeDefault', this.value)">
<option value="escuro" ${adminConfig.themeDefault === 'escuro' ? 'selected' : ''}>Escuro</option>
<option value="claro" ${adminConfig.themeDefault === 'claro' ? 'selected' : ''}>Claro</option>
</select>
</div>
<div>
<label>Intervalo de Atualização (horas):</label>
<input type="number" id="admin-refresh-interval" value="${adminConfig.dataRefreshInterval}"
onchange="salvarConfigAdmin('dataRefreshInterval', parseInt(this.value))">
</div>
<div>
<label>
<input type="checkbox" ${adminConfig.autoBackup ? 'checked' : ''}
onchange="salvarConfigAdmin('autoBackup', this.checked)">
Auto Backup
</label>
</div>
</div>
</div>
<div class="card" style="background: var(--color-bg-1); margin-bottom: 20px;">
<h3 style="color: var(--color-primary); margin: 0 0 16px 0;">🛠️ Visibilidade de Ferramentas</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 8px;">
${Object.entries(adminConfig.toolsVisibility).map(([tool, visible]) => `
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" ${visible ? 'checked' : ''}
onchange="salvarConfigAdminTool('${tool}', this.checked)">
${tool}
</label>
`).join('')}
</div>
</div>
<div class="card" style="background: var(--color-bg-1); margin-bottom: 20px;">
<h3 style="color: var(--color-primary); margin: 0 0 16px 0;">💾 Backup e Restauração</h3>
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
<button class="btn btn-success" onclick="criarBackup()">
📥 Criar Backup
</button>
<button class="btn btn-warning" onclick="restaurarUltimoBackup()">
📤 Restaurar Último Backup
</button>
<button class="btn btn-info" onclick="exportarConfig()">
📋 Exportar Configurações
</button>
<button class="btn btn-primary" onclick="importarConfig()">
📋 Importar Configurações
</button>
</div>
</div>
`;
// Incluir configSection no modalHTML...
}
// Funções para salvar configurações
function salvarConfigAdmin(chave, valor) {
const config = window.adminConfigManager.getConfig();
config[chave] = valor;
window.adminConfigManager.saveConfig(config);
console.log(`✅ Config ${chave} salva:`, valor);
}
function salvarConfigAdminTool(tool, visible) {
const config = window.adminConfigManager.getConfig();
config.toolsVisibility[tool] = visible;
window.adminConfigManager.saveConfig(config);
console.log(`✅ Tool ${tool} visibilidade:`, visible);
// Aplicar mudança imediatamente na UI
aplicarVisibilidadeFerramentas();
}
```
## 🔄 Fluxo Completo de Persistência
```mermaid
sequenceDiagram
participant User
participant AdminPanel
participant AdminConfig
participant Storage
participant Backup
participant App
User->>AdminPanel: Abre configurações
AdminPanel->>AdminConfig: Carrega config atual
AdminConfig->>Storage: Lê localStorage
Storage-->>AdminConfig: Config salva
AdminConfig-->>AdminPanel: Config atual
AdminPanel-->>User: Mostra interface
User->>AdminPanel: Altera config
AdminPanel->>AdminConfig: Salva nova config
AdminConfig->>Storage: Atualiza localStorage
AdminConfig-->>AdminPanel: Confirmação
AdminPanel-->>App: Aplica mudanças
App-->>User: Interface atualizada
AdminConfig->>Backup: Cria backup automático
Backup->>Storage: Salva histórico
Note over User,Storage: Em caso de reload...
App->>AdminConfig: Carrega config salva
AdminConfig->>Storage: Lê persistência
Storage-->>AdminConfig: Config intacta
AdminConfig-->>App: Aplica config
App-->>User: Estado restaurado
```
## 📊 Monitoramento e Debugging
### **Sistema de Logs Persistente**
```javascript
class PersistentLogger {
constructor() {
this.logKey = 'acoCalcPro_logs';
this.maxLogs = 100;
}
log(level, message, data = null) {
const logs = this.getLogs();
const entry = {
timestamp: Date.now(),
level,
message,
data,
userAgent: navigator.userAgent
};
logs.unshift(entry);
if (logs.length > this.maxLogs) {
logs.splice(this.maxLogs);
}
localStorage.setItem(this.logKey, JSON.stringify(logs));
// Também log no console
console[level] || console.log(`[${level.toUpperCase()}] ${message}`, data);
}
getLogs() {
try {
const saved = localStorage.getItem(this.logKey);
return saved ? JSON.parse(saved) : [];
} catch {
return [];
}
}
exportLogs() {
const logs = this.getLogs();
const blob = new Blob([JSON.stringify(logs, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `aco-calc-pro-logs-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
}
```
## 🛡️ Estratégias de Resiliência
### 1. **Graceful Degradation**
- Sistema funciona mesmo sem localStorage (modo básico)
- Fallback para CSV quando IndexedDB falhar
- Mensagens claras para usuário quando recursos não disponíveis
### 2. **Validação e Sanitização**
- Validação de dados antes de salvar
- Sanitização de inputs do admin
- Verificação de integridade ao carregar
### 3. **Limite de Armazenamento**
- Monitoramento de uso de localStorage
- Limpeza automática de dados antigos
- Compressão de dados grandes
### 4. **Recuperação de Erros**
- Try-catch em todas as operações de I/O
- Logs detalhados de erros
- Sistema de recuperação automática
## 📈 Métricas e Monitoramento
### **Dashboard de Saúde do Sistema**
```javascript
function getSystemHealth() {
const health = {
storage: {
localStorageAvailable: isStorageAvailable(),
usage: getStorageUsage(),
quota: getStorageQuota()
},
cache: {
dataManager: window.dataManager.getCacheStats(),
indexedDB: window.cacheManager ? window.cacheManager.checkHealth() : null
},
config: {
adminConfig: window.adminConfigManager.getConfig(),
userPreferences: userPreferences
},
timestamp: Date.now()
};
return health;
}
```
## 🚀 Implementação Passo a Passo
### **Fase 1: Sistema de Configurações (Prioridade Alta)**
1. Criar `AdminConfigManager` class
2. Integrar com `admin-panel.js`
3. Adicionar UI de configurações no painel
4. Implementar funções de salvamento/restauração
### **Fase 2: Sistema de Backup (Prioridade Média)**
1. Criar `BackupManager` class
2. Implementar criação automática de backups
3. Adicionar UI de backup/restauração
4. Criar sistema de exportação/importação
### **Fase 3: Sistema de Migração (Prioridade Média)**
1. Criar `MigrationManager` class
2. Implementar detecção de versão
3. Criar scripts de migração
4. Adicionar rollback em caso de falha
### **Fase 4: Monitoramento (Prioridade Baixa)**
1. Implementar `PersistentLogger`
2. Criar dashboard de saúde
3. Adicionar alertas de problema
4. Implementar telemetria anônima
## 📋 Checklist de Validação
- ✅ Configurações do admin persistem após reload
- ✅ Backup automático funciona corretamente
- ✅ Restauração mantém integridade dos dados
- ✅ Sistema de migração trata versões antigas
- ✅ Logs persistentes ajudam no debugging
- ✅ Dashboard mostra saúde do sistema
- ✅ Fallbacks funcionam quando storage falha
- ✅ UI reflete mudanças imediatamente
- ✅ Performance não é degradada
- ✅ Usuário recebe feedback de ações
## 🔧 Manutenção e Troubleshooting
### **Comandos de Diagnóstico**
```javascript
// Verificar integridade do sistema
console.log('Health Check:', getSystemHealth());
// Verificar configurações atuais
console.log('Admin Config:', window.adminConfigManager.getConfig());
// Verificar backups disponíveis
console.log('Backups:', window.backupManager.getBackups());
// Exportar logs para análise
window.persistentLogger.exportLogs();
```
### **Procedimentos de Recuperação**
1. **Configurações corrompidas**: Usar `resetConfig()`
2. **Cache corrompido**: Limpar e recarregar dados
3. **Storage cheio**: Limpar logs antigos e backups
4. **Erros críticos**: Restaurar último backup válido
---
**Status**: Documentação Técnica Completa
**Versão**: 1.0.0
**Data**: 2025-01-01
**Responsável**: Sistema de Documentação Aço Calc Pro

View File

@@ -1 +0,0 @@
{"projectId":"prj_w6vDwmiwgABCctzGbk5dUigI550V","orgId":"team_KY59uhtbyLyWDtIuz7Ew3uEk","projectName":"trae_jenzyix7","neverMindDeployCard":true}

View File

@@ -1,8 +0,0 @@
node_modules
.git
.vscode
.kiro
ORIGINAL
*.zip
*.md
!README.md

View File

@@ -1,4 +1,15 @@
FROM nginx:alpine
COPY . /usr/share/nginx/html
EXPOSE 80
# Build stage
FROM bitnami/node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Production stage
FROM nginxinc/nginx-unprivileged:alpine
COPY --from=build /app/dist /usr/share/nginx/html
# Copy manual scripts/assets if they are not picked up by Vite build
# (though they should be if referenced in index.html)
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]

File diff suppressed because it is too large Load Diff

View File

@@ -1,908 +0,0 @@
// ========================================
// CONTINUATION OF CALCULATION FUNCTIONS
// ========================================
// Bolts Calculation
function calcularParafusos() {
const fy = parseFloat(document.getElementById('bolt-type').value) || 400;
const d = parseFloat(document.getElementById('bolt-d').value) || 20;
const qty = parseInt(document.getElementById('bolt-qty').value) || 1;
const planes = parseInt(document.getElementById('bolt-planes').value) || 1;
const force = parseFloat(document.getElementById('bolt-force').value) || 0;
const area = Math.PI * Math.pow(d / 2, 2);
const Fv = 0.6 * fy * area * planes / 1000;
const capacity = Fv * qty;
const utilization = (force / capacity) * 100;
let alertClass = 'alert-success';
let status = '✅ OK - Capacidade adequada';
if (utilization > 100) {
alertClass = 'alert-error';
status = '❌ FALHA - Capacidade insuficiente';
} else if (utilization > 80) {
alertClass = 'alert-warning';
status = '⚠️ ATENÇÃO - Utilização elevada';
}
document.getElementById('bolt-result').innerHTML = `
<div class="result-box">
<div class="result-title">Verificação ao Cisalhamento</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Área Parafuso</div>
<div class="result-value">${area.toFixed(0)} mm²</div>
</div>
<div class="result-item">
<div class="result-label">Fv por Parafuso</div>
<div class="result-value">${Fv.toFixed(1)} kN</div>
</div>
<div class="result-item">
<div class="result-label">Capacidade Total</div>
<div class="result-value">${capacity.toFixed(1)} kN</div>
</div>
<div class="result-item">
<div class="result-label">Utilização</div>
<div class="result-value">${utilization.toFixed(1)}%</div>
</div>
</div>
<div class="alert ${alertClass}" style="margin-top: 16px;">
<strong>${status}</strong>
</div>
</div>
`;
addToHistory('Ligações Parafusadas', `${qty} parafusos Ø${d}mm, Utilização = ${utilization.toFixed(1)}%`);
}
// Layout Verification
function verificarLayout() {
const d = parseFloat(document.getElementById('layout-d').value) || 20;
const edge = parseFloat(document.getElementById('layout-edge').value) || 0;
const spacing = parseFloat(document.getElementById('layout-spacing').value) || 0;
const minEdge = 1.5 * d;
const maxEdge = 12 * 10;
const minSpacing = 2.67 * d;
const maxSpacing = 300;
let edgeStatus = '✅ Conforme';
let edgeClass = 'alert-success';
if (edge < minEdge) {
edgeStatus = `❌ Abaixo do mínimo (${minEdge.toFixed(1)}mm)`;
edgeClass = 'alert-error';
} else if (edge > maxEdge) {
edgeStatus = `⚠️ Acima do máximo (${maxEdge}mm)`;
edgeClass = 'alert-warning';
}
let spacingStatus = '✅ Conforme';
let spacingClass = 'alert-success';
if (spacing < minSpacing) {
spacingStatus = `❌ Abaixo do mínimo (${minSpacing.toFixed(1)}mm)`;
spacingClass = 'alert-error';
} else if (spacing > maxSpacing) {
spacingStatus = `⚠️ Acima do máximo (${maxSpacing}mm)`;
spacingClass = 'alert-warning';
}
document.getElementById('layout-result').innerHTML = `
<div class="card">
<div class="card-title">Verificação NBR 8800</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Dist. Borda Mín</div>
<div class="result-value" style="font-size: 18px;">${minEdge.toFixed(1)} mm</div>
</div>
<div class="result-item">
<div class="result-label">Dist. Borda Máx</div>
<div class="result-value" style="font-size: 18px;">${maxEdge} mm</div>
</div>
<div class="result-item">
<div class="result-label">Espaç. Mínimo</div>
<div class="result-value" style="font-size: 18px;">${minSpacing.toFixed(1)} mm</div>
</div>
<div class="result-item">
<div class="result-label">Espaç. Máximo</div>
<div class="result-value" style="font-size: 18px;">${maxSpacing} mm</div>
</div>
</div>
<div class="alert ${edgeClass}" style="margin-top: 16px;">
<strong>Distância de Borda: ${edgeStatus}</strong>
</div>
<div class="alert ${spacingClass}">
<strong>Espaçamento: ${spacingStatus}</strong>
</div>
</div>
`;
addToHistory('Layout de Furação', `Ø${d}mm, Borda: ${edge}mm, Espaç: ${spacing}mm`);
}
// Bolt vs Weld Comparison
function compararParafusoSolda() {
const force = parseFloat(document.getElementById('comp-force').value) || 0;
const length = parseFloat(document.getElementById('comp-length').value) || 0;
const fy = parseFloat(document.getElementById('comp-fy').value) || 345;
// Parafusos A325 Ø20mm
const boltCapacity = 60;
const boltQty = Math.ceil(force / boltCapacity);
const boltCost = boltQty * 15;
// Solda
const weldLeg = (force * 1000) / (0.707 * length * 0.65 * fy);
const weldLegRounded = Math.ceil(weldLeg);
const weldCost = (weldLegRounded * length / 1000) * 25;
document.getElementById('comparison-result').innerHTML = `
<div class="card">
<div class="card-title">Comparação de Soluções</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div style="background: var(--color-bg-1); padding: 20px; border-radius: 12px;">
<h3 style="color: var(--color-primary); margin-bottom: 16px;">🔩 Solução Parafusada</h3>
<p><strong>Tipo:</strong> A325 Ø20mm</p>
<p><strong>Quantidade:</strong> ${boltQty} parafusos</p>
<p><strong>Capacidade:</strong> ${(boltQty * boltCapacity).toFixed(1)} kN</p>
<p><strong>Custo estimado:</strong> R$ ${boltCost.toFixed(2)}</p>
<p><strong>Vantagens:</strong> Desmontável, inspeção visual</p>
<p><strong>Desvantagens:</strong> Maior tempo de instalação</p>
</div>
<div style="background: var(--color-bg-2); padding: 20px; border-radius: 12px;">
<h3 style="color: var(--color-warning); margin-bottom: 16px;">🔥 Solução Soldada</h3>
<p><strong>Tipo:</strong> Solda de filete</p>
<p><strong>Perna:</strong> ${weldLegRounded} mm</p>
<p><strong>Comprimento:</strong> ${length} mm</p>
<p><strong>Custo estimado:</strong> R$ ${weldCost.toFixed(2)}</p>
<p><strong>Vantagens:</strong> Melhor rigidez, econômica</p>
<p><strong>Desvantagens:</strong> Permanente, requer qualificação</p>
</div>
</div>
<div class="alert alert-success" style="margin-top: 20px;">
<strong>Recomendação:</strong> ${weldCost < boltCost ? 'Solda de filete é mais econômica' : 'Parafusos mais econômicos'}
</div>
</div>
`;
addToHistory('Parafuso vs Solda', `${boltQty} parafusos vs solda ${weldLegRounded}mm`);
}
// Preheat Calculation
function calcularPreaquecimento() {
const cev = parseFloat(document.getElementById('preheat-cev').value) || 0;
const thickness = parseFloat(document.getElementById('preheat-thickness').value) || 0;
const ambient = parseFloat(document.getElementById('preheat-ambient').value) || 20;
const preheatTemp = 50 + (cev * 100) + (thickness / 10 * 20) + ((20 - ambient) / 2);
const maxInterpass = preheatTemp + 100;
let pwhtRecommendation = '';
if (thickness > 50 || cev > 0.60) {
pwhtRecommendation = '⚠️ PWHT (Tratamento Térmico Pós-Soldagem) recomendado';
} else {
pwhtRecommendation = '✅ PWHT não obrigatório';
}
document.getElementById('preheat-result').innerHTML = `
<div class="result-box">
<div class="result-title">Temperatura de Pré-Aquecimento (AWS D1.1)</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Temp. Mínima</div>
<div class="result-value">${Math.round(preheatTemp)}°C</div>
</div>
<div class="result-item">
<div class="result-label">Temp. Interpasse Máx</div>
<div class="result-value">${Math.round(maxInterpass)}°C</div>
</div>
</div>
<div class="alert alert-warning" style="margin-top: 16px;">
<strong>${pwhtRecommendation}</strong>
</div>
<div class="expert-only" style="margin-top: 16px; padding: 16px; background: var(--color-bg-3); border-radius: 8px;">
<strong>Procedimento:</strong><br>
1. Aquecer uniformemente até ${Math.round(preheatTemp)}°C<br>
2. Medir temperatura a 75mm da junta<br>
3. Manter durante toda a soldagem<br>
4. Temperatura interpasse máxima: ${Math.round(maxInterpass)}°C
</div>
</div>
`;
addToHistory('Pré-Aquecimento', `CEV=${cev}, esp=${thickness}mm → ${Math.round(preheatTemp)}°C`);
}
// Weld Fillet Calculation
function calcularSoldaFilete() {
const force = parseFloat(document.getElementById('weld-force').value) || 0;
const length = parseFloat(document.getElementById('weld-length').value) || 0;
const fy = parseFloat(document.getElementById('weld-fy').value) || 345;
const fyWeld = fy * 0.6;
const leg = (force * 1000) / (0.707 * length * 0.65 * fyWeld);
const throat = leg * 0.707;
const legCommercial = Math.ceil(leg);
const passes = legCommercial <= 5 ? 1 : legCommercial <= 8 ? 2 : 3;
document.getElementById('weld-result').innerHTML = `
<div class="result-box">
<div class="result-title">Dimensionamento da Solda de Filete</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Perna Calculada</div>
<div class="result-value">${leg.toFixed(2)} mm</div>
</div>
<div class="result-item">
<div class="result-label">Perna Adotada</div>
<div class="result-value">${legCommercial} mm</div>
</div>
<div class="result-item">
<div class="result-label">Garganta Efetiva</div>
<div class="result-value">${throat.toFixed(2)} mm</div>
</div>
<div class="result-item">
<div class="result-label">Número de Passes</div>
<div class="result-value">${passes}</div>
</div>
</div>
<div class="alert alert-success" style="margin-top: 16px;">
<strong>Eletrodo recomendado:</strong> E${Math.round(fy * 1.15)} (resistência compatível com o aço base)
</div>
</div>
`;
addToHistory('Solda de Filete', `Perna ${legCommercial}mm, ${passes} passe(s)`);
}
// Heat Input Calculation
function calcularEnergiaSoldagem() {
const voltage = parseFloat(document.getElementById('hi-voltage').value) || 0;
const current = parseFloat(document.getElementById('hi-current').value) || 0;
const speed = parseFloat(document.getElementById('hi-speed').value) || 0;
const heatInput = (voltage * current * 60) / (speed * 1000);
let interpretation = '';
let alertClass = '';
if (heatInput < 1.0) {
interpretation = 'Energia baixa - Risco de falta de fusão ou trincas a frio';
alertClass = 'alert-warning';
} else if (heatInput <= 2.0) {
interpretation = 'Energia adequada - Dentro da faixa recomendada';
alertClass = 'alert-success';
} else {
interpretation = 'Energia alta - Risco de fragilização da ZTA e distorção';
alertClass = 'alert-error';
}
document.getElementById('hi-result').innerHTML = `
<div class="result-box">
<div class="result-title">Energia de Soldagem (Heat Input)</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Energia</div>
<div class="result-value">${heatInput.toFixed(2)} kJ/mm</div>
</div>
</div>
<div class="alert ${alertClass}" style="margin-top: 16px;">
<strong>${interpretation}</strong>
</div>
<div class="expert-only" style="margin-top: 16px; padding: 16px; background: var(--color-bg-4); border-radius: 8px;">
<strong>Fórmula:</strong> HI = (V × I × 60) / (v × 1000)<br>
<strong>Recomendações:</strong><br>
• Aços carbono: 0.8 - 2.0 kJ/mm<br>
• Alta resistência: 0.8 - 1.5 kJ/mm<br>
• Baixa liga: 1.0 - 2.5 kJ/mm
</div>
</div>
`;
addToHistory('Energia de Soldagem', `HI = ${heatInput.toFixed(2)} kJ/mm`);
}
// Electrode Consumption
function calcularConsumoEletrodos() {
const leg = parseFloat(document.getElementById('elec-leg').value) || 0;
const length = parseFloat(document.getElementById('elec-length').value) || 0;
const factor = parseFloat(document.getElementById('elec-type').value) || 1.10;
const loss = parseFloat(document.getElementById('elec-loss').value) || 15;
const throat = leg * 0.707;
const volume = throat * leg * length * 1000;
const mass = (volume / 1000000) * 7850 / 1000;
const consumption = mass * factor * (1 + loss / 100);
document.getElementById('elec-result').innerHTML = `
<div class="result-box">
<div class="result-title">Consumo de Eletrodos</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Volume de Solda</div>
<div class="result-value">${(volume / 1000).toFixed(1)} cm³</div>
</div>
<div class="result-item">
<div class="result-label">Massa de Solda</div>
<div class="result-value">${mass.toFixed(2)} kg</div>
</div>
<div class="result-item">
<div class="result-label">Consumo Total</div>
<div class="result-value">${consumption.toFixed(2)} kg</div>
</div>
</div>
<div style="margin-top: 16px; padding: 16px; background: var(--color-bg-5); border-radius: 8px;">
<strong>Estimativa de embalagens:</strong><br>
Eletrodos Ø3,25mm (1 kg cada): ${Math.ceil(consumption)} embalagens
</div>
</div>
`;
addToHistory('Consumo de Eletrodos', `${consumption.toFixed(2)} kg para ${length}m de solda`);
}
// Hardness Converter
function converterDureza(source) {
let HB = 0;
if (source === 'hb') {
HB = parseFloat(document.getElementById('hard-hb').value) || 0;
} else if (source === 'hrc') {
const HRC = parseFloat(document.getElementById('hard-hrc').value) || 0;
HB = (HRC + 9.8) / 0.0338;
document.getElementById('hard-hb').value = Math.round(HB);
} else if (source === 'hv') {
const HV = parseFloat(document.getElementById('hard-hv').value) || 0;
HB = HV / 0.95;
document.getElementById('hard-hb').value = Math.round(HB);
}
if (HB === 0) return;
const HRC = HB * 0.0338 - 9.8;
const HV = HB * 0.95;
const fu = HB * 10;
const fy = fu * 0.7;
if (source !== 'hrc') document.getElementById('hard-hrc').value = HRC.toFixed(1);
if (source !== 'hv') document.getElementById('hard-hv').value = Math.round(HV);
document.getElementById('hardness-result').innerHTML = `
<div class="result-box">
<div class="result-title">Conversão de Dureza</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">HB (Brinell)</div>
<div class="result-value">${Math.round(HB)}</div>
</div>
<div class="result-item">
<div class="result-label">HRC (Rockwell C)</div>
<div class="result-value">${HRC.toFixed(1)}</div>
</div>
<div class="result-item">
<div class="result-label">HV (Vickers)</div>
<div class="result-value">${Math.round(HV)}</div>
</div>
</div>
<div style="margin-top: 16px; padding: 16px; background: var(--color-bg-6); border-radius: 8px;">
<strong>Estimativa de Resistência:</strong><br>
fu ≈ ${fu.toFixed(0)} MPa<br>
fy ≈ ${fy.toFixed(0)} MPa (aproximado)
</div>
</div>
`;
}
// Charpy Analysis
function analisarCharpy() {
const temps = [
parseFloat(document.getElementById('charpy-t1').value),
parseFloat(document.getElementById('charpy-t2').value),
parseFloat(document.getElementById('charpy-t3').value),
parseFloat(document.getElementById('charpy-t4').value)
];
const energies = [
parseFloat(document.getElementById('charpy-e1').value),
parseFloat(document.getElementById('charpy-e2').value),
parseFloat(document.getElementById('charpy-e3').value),
parseFloat(document.getElementById('charpy-e4').value)
];
const validPoints = temps.map((t, i) => ({ temp: t, energy: energies[i] }))
.filter(p => !isNaN(p.temp) && !isNaN(p.energy))
.sort((a, b) => a.temp - b.temp);
if (validPoints.length < 2) {
alert('Insira pelo menos 2 pontos válidos');
return;
}
let ttdf = null;
for (let i = 0; i < validPoints.length - 1; i++) {
if ((validPoints[i].energy >= 27 && validPoints[i+1].energy < 27) ||
(validPoints[i].energy < 27 && validPoints[i+1].energy >= 27)) {
const t1 = validPoints[i].temp;
const e1 = validPoints[i].energy;
const t2 = validPoints[i+1].temp;
const e2 = validPoints[i+1].energy;
ttdf = t1 + (27 - e1) * (t2 - t1) / (e2 - e1);
break;
}
}
document.getElementById('charpy-result').innerHTML = `
<div class="card">
<div class="card-title">Curva de Transição Dúctil-Frágil</div>
<div class="chart-container">
<canvas id="charpy-chart"></canvas>
</div>
${ttdf !== null ? `
<div class="alert alert-success" style="margin-top: 16px;">
<strong>TTDF (Temperatura de Transição):</strong> ${ttdf.toFixed(1)}°C<br>
Temperatura onde a energia de impacto = 27J
</div>
` : `
<div class="alert alert-warning" style="margin-top: 16px;">
<strong>Não foi possível calcular TTDF</strong><br>
A curva não intercepta 27J no intervalo medido
</div>
`}
</div>
`;
if (currentChart) {
currentChart.destroy();
}
const ctx = document.getElementById('charpy-chart').getContext('2d');
currentChart = new Chart(ctx, {
type: 'line',
data: {
labels: validPoints.map(p => p.temp + '°C'),
datasets: [{
label: 'Energia (J)',
data: validPoints.map(p => p.energy),
borderColor: '#1FB8CD',
backgroundColor: '#1FB8CD40',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Curva de Transição Charpy'
}
},
scales: {
y: {
title: {
display: true,
text: 'Energia (J)'
}
},
x: {
title: {
display: true,
text: 'Temperatura (°C)'
}
}
}
}
});
addToHistory('Análise Charpy', `${validPoints.length} pontos, TTDF = ${ttdf ? ttdf.toFixed(1) : 'N/A'}°C`);
}
// Certificate Checklist
function gerarChecklistCertificado() {
const norm = document.getElementById('cert-norm').value;
const requirements = certRequirements[norm] || [];
document.getElementById('cert-result').innerHTML = `
<div class="card">
<div class="card-title">Checklist de Requisitos - ${norm.toUpperCase().replace('_', ' ')}</div>
${requirements.map((req, index) => `
<div style="padding: 12px; background: var(--color-background); border-radius: 8px; margin-bottom: 8px; display: flex; align-items: center; gap: 12px;">
<input type="checkbox" id="req-${index}" style="width: 20px; height: 20px; cursor: pointer;">
<label for="req-${index}" style="cursor: pointer; flex: 1;">${req}</label>
</div>
`).join('')}
</div>
`;
}
// Paint Area Calculation
function updatePaintFields() {
const type = document.getElementById('paint-type').value;
const field3 = document.getElementById('paint-field3');
if (type === 'chapa') {
document.getElementById('paint-label1').textContent = 'Comprimento (mm)';
document.getElementById('paint-label2').textContent = 'Largura (mm)';
field3.style.display = 'none';
} else if (type === 'perfilW') {
document.getElementById('paint-label1').textContent = 'Comprimento (mm)';
document.getElementById('paint-label2').textContent = 'Altura (mm)';
field3.style.display = 'none';
} else if (type === 'tubo') {
document.getElementById('paint-label1').textContent = 'Comprimento (mm)';
document.getElementById('paint-label2').textContent = 'Diâmetro (mm)';
field3.style.display = 'none';
} else if (type === 'rhs') {
document.getElementById('paint-label1').textContent = 'Comprimento (mm)';
document.getElementById('paint-label2').textContent = 'Largura (mm)';
document.getElementById('paint-label3').textContent = 'Altura (mm)';
field3.style.display = 'block';
}
}
function calcularAreaPintura() {
const type = document.getElementById('paint-type').value;
const dim1 = parseFloat(document.getElementById('paint-dim1').value) || 0;
const dim2 = parseFloat(document.getElementById('paint-dim2').value) || 0;
const dim3 = parseFloat(document.getElementById('paint-dim3').value) || 0;
const qty = parseInt(document.getElementById('paint-qty').value) || 1;
let area = 0;
if (type === 'chapa') {
area = (dim1 * dim2 * 2) / 1000000;
} else if (type === 'perfilW') {
const perimeter = dim2 * 3.5;
area = (perimeter * dim1) / 1000000;
} else if (type === 'tubo') {
area = (Math.PI * dim2 * dim1) / 1000000;
} else if (type === 'rhs') {
const perimeter = 2 * (dim2 + dim3);
area = (perimeter * dim1) / 1000000;
}
const totalArea = area * qty;
document.getElementById('paint-area-result').innerHTML = `
<div class="result-box">
<div class="result-title">Área de Pintura</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Área Unitária</div>
<div class="result-value">${area.toFixed(2)} m²</div>
</div>
<div class="result-item">
<div class="result-label">Área Total</div>
<div class="result-value">${totalArea.toFixed(2)} m²</div>
</div>
</div>
</div>
`;
document.getElementById('tinta-area').value = totalArea.toFixed(2);
addToHistory('Área de Pintura', `${totalArea.toFixed(2)}m² (${qty} unidades)`);
}
// Paint Consumption
function calcularConsumoTinta() {
const area = parseFloat(document.getElementById('tinta-area').value) || 0;
const dft = parseFloat(document.getElementById('tinta-dft').value) || 0;
const solids = parseFloat(document.getElementById('tinta-solids').value) || 0;
const loss = parseFloat(document.getElementById('tinta-loss').value) || 0;
const coats = parseInt(document.getElementById('tinta-coats').value) || 1;
const cost = parseFloat(document.getElementById('tinta-cost').value) || 0;
const volumeTheoretical = (dft * area) / (1000 * (solids / 100));
const volumeWithLoss = volumeTheoretical / (1 - loss / 100);
const volumePerCoat = volumeWithLoss;
const volumeTotal = volumePerCoat * coats;
const totalCost = volumeTotal * cost;
document.getElementById('tinta-result').innerHTML = `
<div class="result-box">
<div class="result-title">Consumo de Tinta</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Volume Teórico</div>
<div class="result-value">${volumeTheoretical.toFixed(2)} L</div>
</div>
<div class="result-item">
<div class="result-label">Volume por Demão</div>
<div class="result-value">${volumePerCoat.toFixed(2)} L</div>
</div>
<div class="result-item">
<div class="result-label">Volume Total</div>
<div class="result-value">${volumeTotal.toFixed(2)} L</div>
</div>
<div class="result-item">
<div class="result-label">Custo Total</div>
<div class="result-value">R$ ${totalCost.toFixed(2)}</div>
</div>
</div>
<div style="margin-top: 16px; padding: 16px; background: var(--color-bg-7); border-radius: 8px;">
<strong>Recomendação de embalagens:</strong><br>
${volumeTotal > 20 ? `Galões 20L: ${Math.ceil(volumeTotal / 20)} unidades` :
volumeTotal > 5 ? `Galões 5L: ${Math.ceil(volumeTotal / 5)} unidades` :
`Latas 1L: ${Math.ceil(volumeTotal)} unidades`}
</div>
</div>
`;
addToHistory('Consumo de Tinta', `${volumeTotal.toFixed(2)}L para ${area}`);
}
// Galvanization
function calcularGalvanizacao() {
const env = document.getElementById('galv-env').value;
const area = parseFloat(document.getElementById('galv-area').value) || 0;
const thickness = parseFloat(document.getElementById('galv-thickness').value) || 85;
const corrosionRate = {
'interno': 0.5,
'urbano': 1.5,
'marinho': 3.0,
'industrial': 2.5
};
const rate = corrosionRate[env] || 1.5;
const lifeYears = thickness / rate;
const zincDensity = 7140;
const zincMass = (area * thickness / 1000) * (zincDensity / 1000000);
const zincCost = zincMass * 12;
document.getElementById('galv-result').innerHTML = `
<div class="result-box">
<div class="result-title">Galvanização a Quente</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Taxa de Corrosão</div>
<div class="result-value">${rate.toFixed(1)} μm/ano</div>
</div>
<div class="result-item">
<div class="result-label">Vida Útil Estimada</div>
<div class="result-value">${lifeYears.toFixed(0)} anos</div>
</div>
<div class="result-item">
<div class="result-label">Consumo de Zinco</div>
<div class="result-value">${zincMass.toFixed(2)} kg</div>
</div>
<div class="result-item">
<div class="result-label">Custo Estimado</div>
<div class="result-value">R$ ${(zincCost + area * 180).toFixed(2)}</div>
</div>
</div>
<div class="alert alert-success" style="margin-top: 16px;">
<strong>Normas aplicáveis:</strong> ASTM A123, ISO 1461, NBR 6323
</div>
</div>
`;
addToHistory('Galvanização', `${area}m², ${thickness}μm → ${lifeYears.toFixed(0)} anos`);
}
// Budget
function adicionarItemOrcamento() {
const type = document.getElementById('budget-type').value;
const spec = document.getElementById('budget-spec').value;
const qty = parseFloat(document.getElementById('budget-qty').value) || 0;
const unit = document.getElementById('budget-unit').value;
const price = parseFloat(document.getElementById('budget-price').value) || 0;
if (!spec || qty <= 0 || price <= 0) {
alert('Preencha todos os campos corretamente');
return;
}
const item = {
id: Date.now(),
type: type,
spec: spec,
qty: qty,
unit: unit,
price: price,
total: qty * price
};
appState.budgetItems.push(item);
atualizarTabelaOrcamento();
document.getElementById('budget-spec').value = '';
document.getElementById('budget-qty').value = '1';
document.getElementById('budget-price').value = '0';
}
function removerItemOrcamento(id) {
appState.budgetItems = appState.budgetItems.filter(item => item.id !== id);
atualizarTabelaOrcamento();
}
function atualizarTabelaOrcamento() {
const tbody = document.getElementById('budget-tbody');
if (appState.budgetItems.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center; color: var(--color-text-secondary);">Nenhum item adicionado</td></tr>';
} else {
tbody.innerHTML = appState.budgetItems.map(item => `
<tr>
<td>${item.type}</td>
<td>${item.spec}</td>
<td>${item.qty.toFixed(2)}</td>
<td>${item.unit}</td>
<td>R$ ${item.price.toFixed(2)}</td>
<td><strong>R$ ${item.total.toFixed(2)}</strong></td>
<td><button class="btn btn-secondary" style="padding: 6px 12px; font-size: 12px;" onclick="removerItemOrcamento(${item.id})">Remover</button></td>
</tr>
`).join('');
}
atualizarTotalOrcamento();
}
function atualizarTotalOrcamento() {
const subtotal = appState.budgetItems.reduce((sum, item) => sum + item.total, 0);
const bdi = parseFloat(document.getElementById('budget-bdi').value) || 0;
const total = subtotal * (1 + bdi / 100);
const resultDiv = document.getElementById('budget-total');
resultDiv.innerHTML = `
<div class="result-box">
<div class="result-grid">
<div class="result-item">
<div class="result-label">Subtotal</div>
<div class="result-value">R$ ${subtotal.toFixed(2)}</div>
</div>
<div class="result-item">
<div class="result-label">BDI (${bdi}%)</div>
<div class="result-value">R$ ${(total - subtotal).toFixed(2)}</div>
</div>
<div class="result-item">
<div class="result-label">TOTAL GERAL</div>
<div class="result-value" style="color: var(--color-success);">R$ ${total.toFixed(2)}</div>
</div>
</div>
</div>
`;
}
// Weight & Rigging
function updateWeightFields() {
const type = document.getElementById('weight-type').value;
const field3 = document.getElementById('weight-field3');
const field4 = document.getElementById('weight-field4');
if (type === 'perfilW') {
document.getElementById('weight-label1').textContent = 'Altura (mm)';
document.getElementById('weight-label2').textContent = 'Comprimento (m)';
field3.style.display = 'none';
field4.style.display = 'none';
} else if (type === 'chapa') {
document.getElementById('weight-label1').textContent = 'Largura (mm)';
document.getElementById('weight-label2').textContent = 'Altura (mm)';
document.getElementById('weight-label3').textContent = 'Espessura (mm)';
field3.style.display = 'block';
field4.style.display = 'none';
} else if (type === 'tubo') {
document.getElementById('weight-label1').textContent = 'Diâmetro Externo (mm)';
document.getElementById('weight-label2').textContent = 'Comprimento (m)';
document.getElementById('weight-label3').textContent = 'Espessura Parede (mm)';
field3.style.display = 'block';
field4.style.display = 'none';
} else if (type === 'barra') {
document.getElementById('weight-label1').textContent = 'Diâmetro (mm)';
document.getElementById('weight-label2').textContent = 'Comprimento (m)';
field3.style.display = 'none';
field4.style.display = 'none';
}
}
function calcularPeso() {
const type = document.getElementById('weight-type').value;
const dim1 = parseFloat(document.getElementById('weight-dim1').value) || 0;
const dim2 = parseFloat(document.getElementById('weight-dim2').value) || 0;
const dim3 = parseFloat(document.getElementById('weight-dim3').value) || 0;
let weight = 0;
if (type === 'perfilW') {
weight = (dim1 / 100) * 31.8 * dim2;
} else if (type === 'chapa') {
weight = (dim1 / 1000) * (dim2 / 1000) * (dim3 / 1000) * 7850;
} else if (type === 'tubo') {
const dExt = dim1;
const dInt = dExt - 2 * dim3;
const area = Math.PI * (Math.pow(dExt/2, 2) - Math.pow(dInt/2, 2));
weight = area / 1000000 * dim2 * 7850;
} else if (type === 'barra') {
const area = Math.PI * Math.pow(dim1/2, 2);
weight = area / 1000000 * dim2 * 7850;
}
document.getElementById('weight-result').innerHTML = `
<div class="result-box">
<div class="result-title">Cálculo de Peso</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Peso Total</div>
<div class="result-value">${weight.toFixed(2)} kg</div>
</div>
</div>
</div>
`;
document.getElementById('rigging-weight').value = weight.toFixed(0);
addToHistory('Cálculo de Peso', `${weight.toFixed(2)}kg`);
}
function calcularRigging() {
const weight = parseFloat(document.getElementById('rigging-weight').value) || 0;
const points = parseInt(document.getElementById('rigging-points').value) || 2;
const angle = parseInt(document.getElementById('rigging-angle').value) || 60;
const fs = parseFloat(document.getElementById('rigging-fs').value) || 4;
const angleRad = angle * Math.PI / 180;
const forcePerCable = (weight * 9.81 / 1000) / (points * Math.cos(angleRad)) * fs;
const cableSteelCapacity = 21;
const chainCapacity = 15;
const syntheticCapacity = 12;
let recommendation = '';
let alertClass = 'alert-success';
if (forcePerCable <= syntheticCapacity) {
recommendation = '✅ Cabo sintético (12 kN)';
} else if (forcePerCable <= chainCapacity) {
recommendation = '✅ Corrente grau 80 (15 kN)';
} else if (forcePerCable <= cableSteelCapacity) {
recommendation = '⚠️ Cabo de aço (21 kN)';
alertClass = 'alert-warning';
} else {
recommendation = '❌ Requer cabo especial ou mais pontos de içamento';
alertClass = 'alert-error';
}
document.getElementById('rigging-result').innerHTML = `
<div class="result-box">
<div class="result-title">Plano de Rigging</div>
<div class="result-grid">
<div class="result-item">
<div class="result-label">Força por Cabo</div>
<div class="result-value">${forcePerCable.toFixed(1)} kN</div>
</div>
<div class="result-item">
<div class="result-label">Ângulo</div>
<div class="result-value">${angle}°</div>
</div>
<div class="result-item">
<div class="result-label">Fator Segurança</div>
<div class="result-value">${fs}</div>
</div>
</div>
<div class="alert ${alertClass}" style="margin-top: 16px;">
<strong>Recomendação: ${recommendation}</strong>
</div>
<div style="margin-top: 16px; padding: 16px; background: var(--color-bg-8); border-radius: 8px;">
<strong>Capacidades de Referência:</strong><br>
• Cabo sintético: 12 kN<br>
• Corrente grau 80: 15 kN<br>
• Cabo de aço: 21 kN
</div>
</div>
`;
addToHistory('Rigging', `${weight}kg, ${points} pontos, ${angle}° → ${forcePerCable.toFixed(1)}kN/cabo`);
}
// ========================================
// INITIALIZATION
// ========================================
document.addEventListener('DOMContentLoaded', function() {
showSection('cev');
mostrarEquivalencias();
gerarChecklistCertificado();
updatePaintFields();
updateWeightFields();
});

View File

@@ -1,305 +0,0 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🏗️ AÇO CALC PRO v5.0 - Plataforma Técnica de Engenharia Estrutural</title>
<link rel="stylesheet" href="style.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="header">
<div class="header-content">
<div class="logo-section">
<div class="logo" id="appLogo">🏗️ AÇO CALC PRO</div>
<div class="subtitle" id="appSubtitle">Plataforma Técnica de Engenharia Estrutural v6.5 - Base de Materiais Integrada</div>
<span class="badge">PROFESSIONAL EDITION</span>
</div>
<div class="header-actions">
<button class="btn-icon" onclick="openManualUsuario()" title="Manual do Usuário">📖 Manual</button>
<button class="btn-icon" onclick="openHistoryModal()" title="Histórico">📋 Histórico</button>
<button class="btn-icon" onclick="openFavoritesModal()" title="Favoritos">⭐ Favoritos</button>
<button class="btn-icon" onclick="openAdminModal()" title="Admin" id="admin-toggle">⚙️ Admin</button>
<button class="btn-icon" onclick="toggleExpertMode()" title="Modo Expert" id="expert-toggle">🎯 Expert</button>
<button class="btn-icon" onclick="toggleTheme()" title="Alternar Tema" id="theme-toggle">🌙 Escuro</button>
</div>
</div>
</div>
<div class="container">
<aside class="sidebar">
<div class="sidebar-tabs">
<button class="sidebar-tab active" onclick="switchSidebarTab(0)">📦 MATERIAIS</button>
<button class="sidebar-tab" onclick="switchSidebarTab(1)">🔗 CONEXÕES</button>
<button class="sidebar-tab" onclick="switchSidebarTab(2)">🔥 SOLDAGEM</button>
<button class="sidebar-tab" onclick="switchSidebarTab(3)">✅ ENSAIOS</button>
<button class="sidebar-tab" onclick="switchSidebarTab(4)">🎨 PINTURA</button>
<button class="sidebar-tab" onclick="switchSidebarTab(5)">💰 ORÇAMENTO</button>
</div>
<div class="sidebar-content active" id="sidebar-0">
<div class="sidebar-section">
<div class="sidebar-item" onclick="showSection('cev')" data-section="cev">
🔬 CEV Avançado (IIW + Pcm)
<button class="star-btn" onclick="toggleFavorite(event, 'cev')"></button>
</div>
<div class="sidebar-item" onclick="showSection('seletor')" data-section="seletor">
🎯 Seletor de Aço Inteligente
<button class="star-btn" onclick="toggleFavorite(event, 'seletor')"></button>
</div>
<div class="sidebar-item" onclick="showSection('equivalencias')" data-section="equivalencias">
📊 Equivalências Internacionais
<button class="star-btn" onclick="toggleFavorite(event, 'equivalencias')"></button>
</div>
<div class="sidebar-item" onclick="showSection('comparativo')" data-section="comparativo">
📈 Comparativo de Aços
<button class="star-btn" onclick="toggleFavorite(event, 'comparativo')"></button>
</div>
</div>
</div>
<div class="sidebar-content" id="sidebar-1">
<div class="sidebar-section">
<div class="sidebar-item" onclick="showSection('parafusos')" data-section="parafusos">
🔩 Ligações Parafusadas
<button class="star-btn" onclick="toggleFavorite(event, 'parafusos')"></button>
</div>
<div class="sidebar-item" onclick="showSection('layout')" data-section="layout">
🎯 Layout de Furação
<button class="star-btn" onclick="toggleFavorite(event, 'layout')"></button>
</div>
<div class="sidebar-item" onclick="showSection('parafuso-vs-solda')" data-section="parafuso-vs-solda">
⚙️ Parafuso vs Solda
<button class="star-btn" onclick="toggleFavorite(event, 'parafuso-vs-solda')"></button>
</div>
</div>
</div>
<div class="sidebar-content" id="sidebar-2">
<div class="sidebar-section">
<div class="sidebar-item" onclick="showSection('preaquecimento')" data-section="preaquecimento">
🔥 Ferramentas de Soldagem
<button class="star-btn" onclick="toggleFavorite(event, 'preaquecimento')"></button>
</div>
</div>
</div>
<div class="sidebar-content" id="sidebar-3">
<div class="sidebar-section">
<div class="sidebar-item" onclick="showSection('dureza')" data-section="dureza">
🔨 Conversor de Dureza
<button class="star-btn" onclick="toggleFavorite(event, 'dureza')"></button>
</div>
<div class="sidebar-item" onclick="showSection('charpy')" data-section="charpy">
📉 Análise de Charpy
<button class="star-btn" onclick="toggleFavorite(event, 'charpy')"></button>
</div>
<div class="sidebar-item" onclick="showSection('certificado')" data-section="certificado">
📋 Checklist Certificado
<button class="star-btn" onclick="toggleFavorite(event, 'certificado')"></button>
</div>
<div class="sidebar-item" onclick="showSection('ultrassom')" data-section="ultrassom">
🏥 Interpretação Ultrassom
<button class="star-btn" onclick="toggleFavorite(event, 'ultrassom')"></button>
</div>
</div>
</div>
<div class="sidebar-content" id="sidebar-4">
<div class="sidebar-section">
<div class="sidebar-item" onclick="showSection('area-pintura')" data-section="area-pintura">
📐 Cálculo de Área
<button class="star-btn" onclick="toggleFavorite(event, 'area-pintura')"></button>
</div>
<div class="sidebar-item" onclick="showSection('consumo-tinta')" data-section="consumo-tinta">
🎯 Consumo de Tinta
<button class="star-btn" onclick="toggleFavorite(event, 'consumo-tinta')"></button>
</div>
<div class="sidebar-item" onclick="showSection('galvanizacao')" data-section="galvanizacao">
🛡️ Galvanização
<button class="star-btn" onclick="toggleFavorite(event, 'galvanizacao')"></button>
</div>
<div class="sidebar-item" onclick="showSection('custo-pintura')" data-section="custo-pintura">
💰 Custo Total
<button class="star-btn" onclick="toggleFavorite(event, 'custo-pintura')"></button>
</div>
<div class="sidebar-item" onclick="showSection('secagem')" data-section="secagem">
⏱️ Tempo de Secagem
<button class="star-btn" onclick="toggleFavorite(event, 'secagem')"></button>
</div>
<div class="sidebar-item" onclick="showSection('inspecao-pintura')" data-section="inspecao-pintura">
✔️ Inspeção de Qualidade
<button class="star-btn" onclick="toggleFavorite(event, 'inspecao-pintura')"></button>
</div>
</div>
</div>
<div class="sidebar-content" id="sidebar-5">
<div class="sidebar-section">
<div class="sidebar-item" onclick="showSection('orcamento')" data-section="orcamento">
💵 Orçamento Detalhado
<button class="star-btn" onclick="toggleFavorite(event, 'orcamento')"></button>
</div>
<div class="sidebar-item" onclick="showSection('peso-rigging')" data-section="peso-rigging">
⚖️ Peso e Rigging
<button class="star-btn" onclick="toggleFavorite(event, 'peso-rigging')"></button>
</div>
<div class="sidebar-item" onclick="showSection('referencia')" data-section="referencia">
📖 Referência Técnica
<button class="star-btn" onclick="toggleFavorite(event, 'referencia')"></button>
</div>
</div>
</div>
</aside>
<main class="main-content" id="main-content">
<!-- Content will be dynamically loaded here -->
</main>
</div>
<!-- History Modal -->
<div class="modal" id="history-modal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">📋 Histórico de Cálculos</div>
<button class="close-btn" onclick="closeHistoryModal()">×</button>
</div>
<div id="history-list"></div>
</div>
</div>
<!-- Favorites Modal -->
<div class="modal" id="favorites-modal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">⭐ Favoritos</div>
<button class="close-btn" onclick="closeFavoritesModal()">×</button>
</div>
<div id="favorites-list"></div>
</div>
</div>
<!-- Admin Modal -->
<div class="modal-admin" id="adminModal">
<div class="modal-admin-content">
<div class="modal-admin-header">
<h2>⚙️ Painel Administrativo</h2>
<button class="close-btn" onclick="closeAdminModal()">×</button>
</div>
<div class="modal-admin-body">
<div class="admin-section">
<h3>🎨 Customização de Branding</h3>
<div class="form-group">
<label class="form-label">Nome do Aplicativo</label>
<input type="text" class="form-control" id="adminAppName" value="AÇO CALC PRO" maxlength="30">
</div>
<div class="form-group">
<label class="form-label">Subtítulo</label>
<input type="text" class="form-control" id="adminAppSubtitle" value="Plataforma Técnica com Base de Dados de Materiais Brasileiros" maxlength="100">
</div>
<div class="form-group">
<label class="form-label">Texto do Rodapé</label>
<textarea class="form-control" id="adminFooterText" rows="2" maxlength="200">© 2025 AÇO CALC PRO v6.5 PROFESSIONAL EDITION - Plataforma Técnica com Base de Dados de Materiais Brasileiros</textarea>
</div>
</div>
<div class="admin-section">
<h3>🔧 Ferramentas Visíveis (Modo Simples)</h3>
<p class="admin-note">No Modo Expert, TODAS as ferramentas aparecem sempre.</p>
<div class="admin-tools-grid">
<label class="checkbox-item"><input type="checkbox" value="cev" checked> CEV Avançado</label>
<label class="checkbox-item"><input type="checkbox" value="seletor" checked> Seletor de Aço</label>
<label class="checkbox-item"><input type="checkbox" value="equivalencias"> Equivalências</label>
<label class="checkbox-item"><input type="checkbox" value="comparativo"> Comparativo</label>
<label class="checkbox-item"><input type="checkbox" value="parafusos" checked> Parafusados</label>
<label class="checkbox-item"><input type="checkbox" value="layout" checked> Layout Furação</label>
<label class="checkbox-item"><input type="checkbox" value="parafuso-vs-solda"> Parafuso vs Solda</label>
<label class="checkbox-item"><input type="checkbox" value="preaquecimento" checked> Pré-Aquecimento</label>
<label class="checkbox-item"><input type="checkbox" value="dureza" checked> Conversor Dureza</label>
<label class="checkbox-item"><input type="checkbox" value="charpy" checked> Análise Charpy</label>
<label class="checkbox-item"><input type="checkbox" value="certificado"> Checklist Certificado</label>
<label class="checkbox-item"><input type="checkbox" value="ultrassom"> Ultrassom</label>
<label class="checkbox-item"><input type="checkbox" value="area-pintura" checked> Área Pintura</label>
<label class="checkbox-item"><input type="checkbox" value="consumo-tinta" checked> Consumo Tinta</label>
<label class="checkbox-item"><input type="checkbox" value="galvanizacao"> Galvanização</label>
<label class="checkbox-item"><input type="checkbox" value="custo-pintura" checked> Custo Pintura</label>
<label class="checkbox-item"><input type="checkbox" value="orcamento" checked> Orçamento</label>
<label class="checkbox-item"><input type="checkbox" value="peso-rigging"> Peso & Rigging</label>
</div>
</div>
<div class="admin-section">
<h3>⚙️ Preferências</h3>
<div class="form-group">
<label class="form-label">Tema Padrão</label>
<select class="form-control" id="adminThemeDefault">
<option value="escuro">Escuro</option>
<option value="claro">Claro</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Modo Padrão</label>
<select class="form-control" id="adminModeDefault">
<option value="simples">Simples</option>
<option value="expert">Expert</option>
</select>
</div>
</div>
</div>
<div class="modal-admin-footer">
<button class="btn btn-secondary" onclick="resetAdminDefaults()">🔄 Restaurar Padrões</button>
<button class="btn btn-primary" onclick="saveAdminConfig()">💾 Salvar Alterações</button>
</div>
</div>
</div>
<!-- Help Modal -->
<div class="modal-help" id="modalAjuda">
<div class="modal-help-content">
<div class="modal-help-header">
<h2 id="ajudaTitulo">📚 Ajuda</h2>
<button class="close-btn" onclick="closeAjudaModal()">×</button>
</div>
<div class="modal-help-tabs">
<button class="help-tab-btn active" onclick="switchHelpTab(0)">📖 Explicação</button>
<button class="help-tab-btn" onclick="switchHelpTab(1)">🔧 Campos</button>
<button class="help-tab-btn" onclick="switchHelpTab(2)">📊 Resultados</button>
<button class="help-tab-btn" onclick="switchHelpTab(3)">📚 Referências</button>
</div>
<div class="modal-help-body">
<div class="help-tab-content active" id="help-tab-0"></div>
<div class="help-tab-content" id="help-tab-1"></div>
<div class="help-tab-content" id="help-tab-2"></div>
<div class="help-tab-content" id="help-tab-3"></div>
</div>
<div class="modal-help-footer">
<button class="btn btn-primary" onclick="abrirManualRelacionado()">📖 Ver no Manual do Usuário</button>
</div>
</div>
</div>
<!-- Manual Usuario Modal -->
<div class="modal-manual" id="modalManual">
<div class="modal-manual-container">
<div class="manual-sidebar">
<div class="manual-search">
<input type="text" id="manualSearch" placeholder="🔍 Buscar..." oninput="buscarNoManual()">
</div>
<nav class="manual-nav" id="manualNav"></nav>
</div>
<div class="manual-content">
<button class="close-btn" onclick="closeManualModal()" style="position: absolute; top: 20px; right: 20px;">×</button>
<div id="manualConteudo"></div>
</div>
</div>
</div>
<div class="footer" id="appFooter">
<p>&copy; 2025 AÇO CALC PRO v6.5 PROFESSIONAL EDITION - Plataforma Técnica com Base de Dados de Materiais Brasileiros</p>
</div>
<script src="app.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

Binary file not shown.

41
fix_catalog.js Normal file
View File

@@ -0,0 +1,41 @@
const fs = require('fs');
const path = 'm:/OFICIAIS E FUNCIONANDO/STEELBASE/js/sections/perfis-catalog.js';
let content = fs.readFileSync(path, 'utf8');
// The main problem is that internal backticks are NOT escaped correctly,
// or the outer ones ARE escaped.
// We need:
// return ` ... inner content ... `;
// If inner content has backticks, they must be \`
// But the file currently has lots of \` and ` mixed up.
// Strategy:
// 1. Unescape ALL backticks first.
content = content.replace(/\\`/g, '`');
// 2. Find functions that start with return ` and ends with `; }
// And escape all backticks INSIDE that range.
const functions = [
'getCantoneirasContent',
'getTubosRHSContent',
'getChapasContent',
'getPerfisHPContent',
'getBarrasRoscadasContent',
'getFerramentasSoldaContent',
'getPerfisWContent'
];
functions.forEach(fn => {
const regex = new RegExp('(function\\s+' + fn + '\\s*\\(\\)\\s*\\{\\s*(?:console\\.log\\(.*?\\);\\s*)?return\\s+`)(.*?)(`;\\s*\\})', 'gs');
content = content.replace(regex, (match, header, body, footer) => {
// Escape backticks in body, but be careful about already escaped ones?
// No, we unescaped them all in step 1.
const escapedBody = body.replace(/`/g, '\\`');
return header + escapedBody + footer;
});
});
fs.writeFileSync(path, content);
console.log('Fixed perfis-catalog.js');

View File

@@ -507,7 +507,10 @@ function getCantoneirasContent() {
</div>
</div>
<script>
`;
// Auto-carregar dados das cantoneiras quando o conteúdo for renderizado
(function() {
console.log('🚀 Script inline de auto-carregamento executado');
@@ -564,7 +567,7 @@ function getCantoneirasContent() {
console.log('✅ Dados carregados:', dados.length, 'cantoneiras');
// Exibir na tabela
tbody.innerHTML = dados.map(item => \`
tbody.innerHTML = dados.map(item => `
<tr>
<td><strong>\${item.nome}</strong></td>
<td>\${item.lado_mm}</td>
@@ -576,13 +579,13 @@ function getCantoneirasContent() {
<td><span class="badge">\${item.tipo}</span></td>
<td><button class="btn btn-sm btn-primary">👁️ Ver</button></td>
</tr>
\`).join('');
`).join('');
console.log('✅ Tabela preenchida com sucesso!');
} catch (error) {
console.error('❌ Erro no fallback:', error);
tbody.innerHTML = \`
tbody.innerHTML = `
<tr>
<td colspan="9" style="text-align: center; padding: 20px; color: #ef4444;">
❌ Erro ao carregar dados: \${error.message}
@@ -590,13 +593,14 @@ function getCantoneirasContent() {
<button class="btn btn-primary" onclick="location.reload()">🔄 Recarregar Página</button>
</td>
</tr>
\`;
`;
}
}
}, 500); // Aguardar 500ms
})();
</script>
`;
}
// Função para trocar tabs de perfis
@@ -2483,7 +2487,10 @@ function getPerfisWContent() {
</div>
</div>
<script>
`;
// Dados dos modelos (extraídos do guia técnico)
const W_MODELOS = [
// W150
@@ -2511,7 +2518,7 @@ function getPerfisWContent() {
function _renderWTabela(dados) {
const tbody = document.getElementById('w-tbody');
if (!tbody) return;
tbody.innerHTML = dados.map(item => \`
tbody.innerHTML = dados.map(item => `
<tr>
<td><strong>\${item.nome}</strong></td>
<td>\${item.altura_mm}</td>
@@ -2523,7 +2530,7 @@ function getPerfisWContent() {
<td>R$ \${item.preco_12m.toFixed(0)}</td>
<td><button class="btn btn-sm btn-primary" onclick="alert('Modelo: \${item.nome}\\nSérie: \${item.serie}\\nIx: \${item.ix_cm4} cm⁴\\nWx: \${item.wx_cm3} cm³')">👁️ Ver</button></td>
</tr>
\`).join('');
`).join('');
const totalEl = document.getElementById('w-total');
if (totalEl) totalEl.textContent = String(dados.length);
}
@@ -2556,7 +2563,7 @@ function getPerfisWContent() {
const precoKg = parseFloat(document.getElementById('w-calc-preco-kg').value);
const pesoTotal = peso * comprimento * quantidade;
const precoTotal = pesoTotal * precoKg;
document.getElementById('w-calc-resultado').innerHTML = \`
document.getElementById('w-calc-resultado').innerHTML = `
<div class="card" style="background: var(--color-success); color: white;">
<h3 style="margin: 0 0 12px 0;">💰 Resultado do Cálculo</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">
@@ -2574,13 +2581,14 @@ function getPerfisWContent() {
</div>
</div>
</div>
\`;
`;
};
// Inicializa tabela ao renderizar conteúdo
setTimeout(() => _renderWTabela(W_MODELOS), 0);
</script>
`;
}
function getTubosRHSContent() {
@@ -2727,7 +2735,10 @@ function getTubosRHSContent() {
</div>
</div>
<script>
`;
// Atualiza diretamente da fonte CSV usando DataManager e repovoa todas as abas
window.atualizarFonteTubos_rhs = async function() {
try {
@@ -2837,8 +2848,9 @@ function getTubosRHSContent() {
alert('❌ Erro ao atualizar: ' + err.message);
}
};
</script>
`;
}
// --- Perfis W: disponibilizar no escopo global para funcionamento dos filtros ---

View File

@@ -1,28 +0,0 @@
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[build]
publish = "."
command = "echo 'No build needed - static site'"
[[headers]]
for = "/*"
[headers.values]
Cache-Control = "public, max-age=0, must-revalidate"
[[headers]]
for = "/BD/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.css"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.js"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"

1102
package-lock.json generated

File diff suppressed because it is too large Load Diff

13
package.json Normal file
View File

@@ -0,0 +1,13 @@
{
"name": "steelbase",
"version": "7.5.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^6.0.0"
}
}

18
scripts/push_gitea.py Normal file
View File

@@ -0,0 +1,18 @@
import subprocess
repo_dir = r"m:\OFICIAIS E FUNCIONANDO\STEELBASE"
# @@ -> %40%40 (Codificação URL padrão para caracteres especiais)
remote_url = "https://admtracksteel:%40%40Gi05Br;;@git.reifonas.cloud/admtracksteel/SteelBase.git"
def run_git(args):
result = subprocess.run(["git"] + args, cwd=repo_dir, capture_output=True, text=True)
if result.returncode != 0:
print(f"Erro em git {' '.join(args)}: {result.stderr}")
else:
print(f"Sucesso em git {' '.join(args)}")
return result.returncode
run_git(["remote", "set-url", "origin", remote_url])
run_git(["branch", "-M", "main"])
print("Iniciando Push de Elite Codificado...")
run_git(["push", "-u", "origin", "main", "--force"])

22
unwrap_scripts.js Normal file
View File

@@ -0,0 +1,22 @@
const fs = require('fs');
const path = 'm:/OFICIAIS E FUNCIONANDO/STEELBASE/js/sections/perfis-catalog.js';
let c = fs.readFileSync(path, 'utf8');
// Unescape some mangled ones
c = c.replace(/\\`/g, '`');
// Match <script> tags inside functions and Extract them out of the return ` ... `
// but after the function.
// 1. Find 'return ` ... <script> ... </script> ... `;'
// 2. Replace with 'return ` ... ... `; <script logic> ...'
// But a simpler way: just REMOVE the <script> and </script> tags ONLY if they are inside return ` ... `;
// and keep the content.
c = c.replace(/return\s+`([\s\S]*?)<script>([\s\S]*?)<\/script>([\s\S]*?)`;/gs, (match, before, script, after) => {
return 'return `' + before + after + '`; \n\n' + script + '\n\n';
});
fs.writeFileSync(path, c);
console.log('Unwrapped scripts in perfis-catalog.js');