Fix syntax errors in perfis-catalog.js for Vite compatibility
This commit is contained in:
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal 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/
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
{"projectId":"prj_w6vDwmiwgABCctzGbk5dUigI550V","orgId":"team_KY59uhtbyLyWDtIuz7Ew3uEk","projectName":"trae_jenzyix7","neverMindDeployCard":true}
|
||||
@@ -1,8 +0,0 @@
|
||||
node_modules
|
||||
.git
|
||||
.vscode
|
||||
.kiro
|
||||
ORIGINAL
|
||||
*.zip
|
||||
*.md
|
||||
!README.md
|
||||
17
Dockerfile
17
Dockerfile
@@ -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;"]
|
||||
|
||||
7268
ORIGINAL/app.js
7268
ORIGINAL/app.js
File diff suppressed because it is too large
Load Diff
@@ -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}m²`);
|
||||
}
|
||||
|
||||
// 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();
|
||||
});
|
||||
@@ -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>© 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>
|
||||
2090
ORIGINAL/style.css
2090
ORIGINAL/style.css
File diff suppressed because it is too large
Load Diff
Binary file not shown.
41
fix_catalog.js
Normal file
41
fix_catalog.js
Normal 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');
|
||||
@@ -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 ---
|
||||
|
||||
28
netlify.toml
28
netlify.toml
@@ -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
1102
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
Normal file
13
package.json
Normal 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
18
scripts/push_gitea.py
Normal 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
22
unwrap_scripts.js
Normal 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');
|
||||
Reference in New Issue
Block a user