From e77912785fc15031f76849dd583e857e0af15620 Mon Sep 17 00:00:00 2001 From: Marcos Reifonas Date: Fri, 11 Sep 2026 11:33:26 +0000 Subject: [PATCH] fix: calibracao das quantidades e dimensoes do conversor tecnometal --- public/conversor_tecnometal.html | 214 +++++++++++----- .../conversores/TecnometalConverter.tsx | 229 ++++++++++++------ 2 files changed, 313 insertions(+), 130 deletions(-) diff --git a/public/conversor_tecnometal.html b/public/conversor_tecnometal.html index 829dd85..e9f4987 100644 --- a/public/conversor_tecnometal.html +++ b/public/conversor_tecnometal.html @@ -475,90 +475,181 @@ return bestCol; }; + const KNOWN_MATERIALS = [ + 'A572GR50', 'A572-GR 50', 'A572-GR50', 'A572GR5', 'A572', + 'A36', 'ASTM A36', '10.9', '8.8', 'SAE 1020' + ]; + const assemblies = []; let currentAssembly = null; for (let i = 0; i < lines.length; i++) { const line = lines[i]; - const lineText = line.map(it => it.text).join(' '); + let rawLineText = line.map(it => it.text).join(' ').trim(); + if (!rawLineText) continue; + + let text = rawLineText.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)\s*-\s*(\d+)/g, '$1-$2-$3'); + text = text.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)/g, '$1-$2'); if ( - lineText.includes('LISTA DE CONJUNTOS') || - lineText.includes('Marca Pos. Descrição') || - lineText.includes('P.Un. P.Tot. S.Tot.') || - lineText.includes('B121-FASE') + text.includes('LISTA DE CONJUNTOS') || + text.includes('Marca Pos. Descrição') || + text.includes('P.Un. P.Tot.') || + text.includes('FASE4-LISTA') || + text === '21806' ) { continue; } - const rowByCol = {}; - for (const it of line) { - const col = getNearestCol(it.x); - if (col === 'superficieTotal') continue; // Ignora S.Tot. + // 1. Monopeça: Marca repete na coluna Pos + const monoMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+\1\s+(.+)$/); + if (monoMatch) { + const fullMark = monoMatch[1]; + const pieceNum = monoMatch[2]; + const rest = monoMatch[3].trim(); + const parts = fullMark.split('-'); - if (!rowByCol[col]) { - rowByCol[col] = it.text.trim(); - } else { - rowByCol[col] += ' ' + it.text.trim(); + let mat = ''; + let matIdx = -1; + let matLen = 0; + for (const m of KNOWN_MATERIALS) { + const idx = rest.indexOf(m); + if (idx !== -1 && idx > matIdx) { + mat = m; + matIdx = idx; + matLen = m.length; + } + } + + if (matIdx !== -1) { + const beforeMat = rest.substring(0, matIdx).trim(); + const afterMat = rest.substring(matIdx + matLen).trim(); + + const mDescQtd = beforeMat.match(/^(Lam\.\s*[0-9.]+|W[0-9]+X[0-9.]+|L[0-9./X]+|TB[0-9.X]+|[A-Za-z0-9./-]+)\s+(\d+)\s*(.*)$/i); + let desc = 'ESTRUTURA'; + let quant = 1; + let comp = 0; + + if (mDescQtd) { + desc = mDescQtd[1]; + quant = parseInt(mDescQtd[2], 10) || 1; + const dimTokens = mDescQtd[3].trim().split(/\s+/).filter(t => /^[0-9.,]+$/.test(t)); + if (dimTokens.length > 0) { + comp = parseNumeric(dimTokens[dimTokens.length - 1]); + } + } + + const postNums = afterMat.split(/\s+/).map(parseNumeric); + const pUn = postNums[0] || 0; + const pTot = postNums[1] || 0; + + assemblies.push({ + rawMark: fullMark, + of: parts[0] || defaultOf, + fase: parts[1] || defaultFase, + marca: pieceNum, + descricao: desc, + isComposed: 'NÃO', + quantidade: quant, + material: normalizeMaterial(mat), + perfilPrincipal: desc, + comprimento: comp, + pesoUnitario: pUn, + pesoTotal: pTot, + components: [] + }); + currentAssembly = null; + continue; } } - const rawMarca = (rowByCol.marca || '').trim(); - const rawPos = (rowByCol.pos || '').trim(); + // 2. Linha Mestre de Conjunto (Peça Composta) + const masterMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+([A-Za-z0-9./-]+)\s+(\d+)\s+([0-9.,]+)\s+([0-9.,]+)$/); + if (masterMatch) { + const fullMark = masterMatch[1]; + const pieceNum = masterMatch[2]; + const pieceNumInt = parseInt(pieceNum, 10); - const markMatch = rawMarca.match(/^([A-Za-z0-9]+)-(\d+)-(\d+)$/) || rawMarca.match(/^([A-Za-z0-9]+)-(\d+)$/); + if (pieceNumInt < 1000) { + const desc = masterMatch[3].trim(); + const quant = parseInt(masterMatch[4], 10) || 1; + const pUn = parseNumeric(masterMatch[5]); + const pTot = parseNumeric(masterMatch[6]); + const parts = fullMark.split('-'); - if (markMatch) { - const parts = rawMarca.split('-'); - const ofCode = parts.length === 3 ? parts[0] : defaultOf; - const phaseCode = parts.length === 3 ? parts[1] : defaultFase; - const pieceNumber = parts.length === 3 ? parts[2] : parts[1]; + currentAssembly = { + rawMark: fullMark, + of: parts[0] || defaultOf, + fase: parts[1] || defaultFase, + marca: pieceNum, + descricao: desc, + isComposed: 'SIM', + quantidade: quant, + material: '', + perfilPrincipal: desc, + comprimento: 0, + pesoUnitario: pUn, + pesoTotal: pTot, + components: [] + }; + assemblies.push(currentAssembly); + continue; + } + } - const qVal = parseInt(rowByCol.qtde || '1', 10); - const mainQuant = isNaN(qVal) || qVal <= 0 ? 1 : qVal; + // 3. Componente Subordinado + const compMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+(.+)$/); + if (compMatch) { + const pos = compMatch[1]; + const pieceNumInt = parseInt(compMatch[2], 10); + const rest = compMatch[3].trim(); - const pUnVal = parseNumeric(rowByCol.pesoUnit || 0); - const pTotVal = parseNumeric(rowByCol.pesoTotal || 0); - const compVal = parseNumeric(rowByCol.comprimento || 0); - const matVal = rowByCol.material ? normalizeMaterial(rowByCol.material) : ''; + if (pieceNumInt >= 1000 && currentAssembly) { + let mat = ''; + let matIdx = -1; + let matLen = 0; + for (const m of KNOWN_MATERIALS) { + const idx = rest.indexOf(m); + if (idx !== -1 && idx > matIdx) { + mat = m; + matIdx = idx; + matLen = m.length; + } + } - const isSimple = rawPos === rawMarca || (compVal > 0 && !!matVal); + if (matIdx !== -1) { + const beforeMat = rest.substring(0, matIdx).trim(); + const afterMat = rest.substring(matIdx + matLen).trim(); - currentAssembly = { - rawMark: rawMarca, - of: ofCode || defaultOf, - fase: phaseCode || defaultFase, - marca: pieceNumber, - descricao: rowByCol.descricao || 'ESTRUTURA', - quantidade: mainQuant, - material: matVal, - perfilPrincipal: rowByCol.descricao || 'ESTRUTURA', - comprimento: compVal, - pesoUnitario: pUnVal, - pesoTotal: pTotVal, - isComposed: isSimple ? 'NÃO' : 'NÃO', - components: [] - }; + const mDescQtd = beforeMat.match(/^(Lam\.\s*[0-9.]+|W[0-9]+X[0-9.]+|L[0-9./X]+|TB[0-9.X]+|[A-Za-z0-9./-]+)\s+(\d+)\s*(.*)$/i); + let desc = 'COMPONENTE'; + let quant = 1; + let comp = 0; - assemblies.push(currentAssembly); - } else if (rawPos && currentAssembly) { - currentAssembly.isComposed = 'SIM'; + if (mDescQtd) { + desc = mDescQtd[1]; + quant = parseInt(mDescQtd[2], 10) || 1; + const dimTokens = mDescQtd[3].trim().split(/\s+/).filter(t => /^[0-9.,]+$/.test(t)); + if (dimTokens.length > 0) { + comp = parseNumeric(dimTokens[dimTokens.length - 1]); + } + } - const compQuant = parseInt(rowByCol.qtde || '1', 10) || 1; - const compLength = parseNumeric(rowByCol.comprimento || 0); - const compMaterial = rowByCol.material ? normalizeMaterial(rowByCol.material) : 'A36'; - const compPUn = parseNumeric(rowByCol.pesoUnit || 0); - const compPTot = parseNumeric(rowByCol.pesoTotal || 0); + const postNums = afterMat.split(/\s+/).map(parseNumeric); + const pUn = postNums[0] || 0; + const pTot = postNums[1] || 0; - currentAssembly.components.push({ - pos: rawPos, - descricao: rowByCol.descricao || '', - quantidade: compQuant, - comprimento: compLength, - material: compMaterial, - pesoUnitario: compPUn, - pesoTotal: compPTot - }); + currentAssembly.components.push({ + pos: pos, + descricao: desc, + quantidade: quant, + comprimento: comp, + material: normalizeMaterial(mat), + pesoUnitario: pUn, + pesoTotal: pTot + }); + } + } } } @@ -574,7 +665,7 @@ if (isComposed) { let bestComp = asm.components[0]; - maxComp = bestComp.comprimento; + maxComp = 0; for (const comp of asm.components) { if (comp.comprimento > maxComp) { @@ -585,6 +676,7 @@ if (bestComp) { materialPrincipal = bestComp.material || 'A36'; + maxComp = bestComp.comprimento; perfilPrincipal = asm.descricao; } diff --git a/src/components/conversores/TecnometalConverter.tsx b/src/components/conversores/TecnometalConverter.tsx index 5ad8aaa..5df785d 100644 --- a/src/components/conversores/TecnometalConverter.tsx +++ b/src/components/conversores/TecnometalConverter.tsx @@ -287,94 +287,186 @@ const TecnometalConverterContent: React.FC = () => { return bestCol; }; + const KNOWN_MATERIALS = [ + 'A572GR50', 'A572-GR 50', 'A572-GR50', 'A572GR5', 'A572', + 'A36', 'ASTM A36', '10.9', '8.8', 'SAE 1020' + ]; + const assemblies: AssemblyItem[] = []; let currentAssembly: AssemblyItem | null = null; for (let i = 0; i < lines.length; i++) { const line = lines[i]; - const lineText = line.map((it) => it.text).join(' '); + let rawLineText = line.map((it) => it.text).join(' ').trim(); + if (!rawLineText) continue; + // Normalizar hífens e materiais + let text = rawLineText.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)\s*-\s*(\d+)/g, '$1-$2-$3'); + text = text.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)/g, '$1-$2'); + + // Descartar cabeçalhos conhecidos if ( - lineText.includes('LISTA DE CONJUNTOS') || - lineText.includes('Marca Pos. Descrição') || - lineText.includes('P.Un. P.Tot. S.Tot.') || - lineText.includes('B121-FASE') + text.includes('LISTA DE CONJUNTOS') || + text.includes('Marca Pos. Descrição') || + text.includes('P.Un. P.Tot.') || + text.includes('FASE4-LISTA') || + text === '21806' ) { continue; } - // Mapear elementos da linha nas colunas correspondentes (descartando S.Tot) - const rowByCol: Record = {}; - for (const it of line) { - const col = getNearestCol(it.x); - if (col === 'superficieTotal') continue; // S.Tot riscado / ignorado + // 1. Monopeça: Marca repete na coluna Pos (ex: "B121-4-1 B121-4-1 Lam. 9.5 22 160 10 270 A36 3,2 71") + const monoMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+\1\s+(.+)$/); + if (monoMatch) { + const fullMark = monoMatch[1]; + const pieceNum = monoMatch[2]; + const rest = monoMatch[3].trim(); + const parts = fullMark.split('-'); - if (!rowByCol[col]) { - rowByCol[col] = it.text.trim(); - } else { - rowByCol[col] += ' ' + it.text.trim(); + let mat = ''; + let matIdx = -1; + let matLen = 0; + for (const m of KNOWN_MATERIALS) { + const idx = rest.indexOf(m); + if (idx !== -1 && idx > matIdx) { + mat = m; + matIdx = idx; + matLen = m.length; + } + } + + if (matIdx !== -1) { + const beforeMat = rest.substring(0, matIdx).trim(); + const afterMat = rest.substring(matIdx + matLen).trim(); + + const mDescQtd = beforeMat.match(/^(Lam\.\s*[0-9.]+|W[0-9]+X[0-9.]+|L[0-9./X]+|TB[0-9.X]+|[A-Za-z0-9./-]+)\s+(\d+)\s*(.*)$/i); + let desc = 'ESTRUTURA'; + let quant = 1; + let comp = 0; + + if (mDescQtd) { + desc = mDescQtd[1]; + quant = parseInt(mDescQtd[2], 10) || 1; + const dimTokens = mDescQtd[3].trim().split(/\s+/).filter((t) => /^[0-9.,]+$/.test(t)); + if (dimTokens.length > 0) { + comp = parseNumeric(dimTokens[dimTokens.length - 1]); + } + } + + const postNums = afterMat.split(/\s+/).map(parseNumeric); + const pUn = postNums[0] || 0; + const pTot = postNums[1] || 0; + + assemblies.push({ + rawMark: fullMark, + of: parts[0] || defaultOf, + fase: parts[1] || defaultFase, + marca: pieceNum, + descricao: desc, + isComposed: 'NÃO', + quantidade: quant, + material: normalizeMaterial(mat), + perfilPrincipal: desc, + comprimento: comp, + pesoUnitario: pUn, + pesoTotal: pTot, + components: [] + }); + currentAssembly = null; + continue; } } - const rawMarca = (rowByCol.marca || '').trim(); - const rawPos = (rowByCol.pos || '').trim(); + // 2. Linha Mestre de Conjunto (Peça Composta) + // Ex: "B121-4-11 W410X46.1 2 60,8 122" ou "B121-4-18 W410X38.8 2 393,7 787" + const masterMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+([A-Za-z0-9./-]+)\s+(\d+)\s+([0-9.,]+)\s+([0-9.,]+)$/); + if (masterMatch) { + const fullMark = masterMatch[1]; + const pieceNum = masterMatch[2]; + const pieceNumInt = parseInt(pieceNum, 10); - // Testa se rawMarca contém uma marca principal (ex: B121-4-1, B121-4-10, B121-4-11) - const markMatch = rawMarca.match(/^([A-Za-z0-9]+)-(\d+)-(\d+)$/) || rawMarca.match(/^([A-Za-z0-9]+)-(\d+)$/); + if (pieceNumInt < 1000) { + const desc = masterMatch[3].trim(); + const quant = parseInt(masterMatch[4], 10) || 1; + const pUn = parseNumeric(masterMatch[5]); + const pTot = parseNumeric(masterMatch[6]); + const parts = fullMark.split('-'); - if (markMatch) { - const parts = rawMarca.split('-'); - const ofCode = parts.length === 3 ? parts[0] : defaultOf; - const phaseCode = parts.length === 3 ? parts[1] : defaultFase; - const pieceNumber = parts.length === 3 ? parts[2] : parts[1]; + currentAssembly = { + rawMark: fullMark, + of: parts[0] || defaultOf, + fase: parts[1] || defaultFase, + marca: pieceNum, + descricao: desc, + isComposed: 'SIM', + quantidade: quant, + material: '', + perfilPrincipal: desc, + comprimento: 0, + pesoUnitario: pUn, + pesoTotal: pTot, + components: [] + }; + assemblies.push(currentAssembly); + continue; + } + } - const qVal = parseInt(rowByCol.qtde || '1', 10); - const mainQuant = isNaN(qVal) || qVal <= 0 ? 1 : qVal; + // 3. Componente Subordinado + // Ex: "B121-4-1047 W410X46.1 1 1101 A572GR5 51,2 51" + // Ex: "B121-4-1038 Lam. 8 4 66 8 120 A36 0,5 2" + const compMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+(.+)$/); + if (compMatch) { + const pos = compMatch[1]; + const pieceNumInt = parseInt(compMatch[2], 10); + const rest = compMatch[3].trim(); - const pUnVal = parseNumeric(rowByCol.pesoUnit || 0); - const pTotVal = parseNumeric(rowByCol.pesoTotal || 0); - const compVal = parseNumeric(rowByCol.comprimento || 0); - const matVal = rowByCol.material ? normalizeMaterial(rowByCol.material) : ''; + if (pieceNumInt >= 1000 && currentAssembly) { + let mat = ''; + let matIdx = -1; + let matLen = 0; + for (const m of KNOWN_MATERIALS) { + const idx = rest.indexOf(m); + if (idx !== -1 && idx > matIdx) { + mat = m; + matIdx = idx; + matLen = m.length; + } + } - // Se a coluna Pos é igual à Marca ou se tem Comprimento e Material preenchidos na linha: - const isSimple = rawPos === rawMarca || (compVal > 0 && !!matVal); + if (matIdx !== -1) { + const beforeMat = rest.substring(0, matIdx).trim(); + const afterMat = rest.substring(matIdx + matLen).trim(); - currentAssembly = { - rawMark: rawMarca, - of: ofCode || defaultOf, - fase: phaseCode || defaultFase, - marca: pieceNumber, - descricao: rowByCol.descricao || 'ESTRUTURA', - quantidade: mainQuant, - material: matVal, - perfilPrincipal: rowByCol.descricao || 'ESTRUTURA', - comprimento: compVal, - pesoUnitario: pUnVal, - pesoTotal: pTotVal, - isComposed: isSimple ? 'NÃO' : 'NÃO', - components: [] - }; + const mDescQtd = beforeMat.match(/^(Lam\.\s*[0-9.]+|W[0-9]+X[0-9.]+|L[0-9./X]+|TB[0-9.X]+|[A-Za-z0-9./-]+)\s+(\d+)\s*(.*)$/i); + let desc = 'COMPONENTE'; + let quant = 1; + let comp = 0; - assemblies.push(currentAssembly); - } else if (rawPos && currentAssembly) { - // Linha de componente subordinado (Marca vazia, Pos preenchida com marca filha ex: B121-4-1037) - currentAssembly.isComposed = 'SIM'; + if (mDescQtd) { + desc = mDescQtd[1]; + quant = parseInt(mDescQtd[2], 10) || 1; + const dimTokens = mDescQtd[3].trim().split(/\s+/).filter((t) => /^[0-9.,]+$/.test(t)); + if (dimTokens.length > 0) { + comp = parseNumeric(dimTokens[dimTokens.length - 1]); + } + } - const compQuant = parseInt(rowByCol.qtde || '1', 10) || 1; - const compLength = parseNumeric(rowByCol.comprimento || 0); - const compMaterial = rowByCol.material ? normalizeMaterial(rowByCol.material) : 'A36'; - const compPUn = parseNumeric(rowByCol.pesoUnit || 0); - const compPTot = parseNumeric(rowByCol.pesoTotal || 0); + const postNums = afterMat.split(/\s+/).map(parseNumeric); + const pUn = postNums[0] || 0; + const pTot = postNums[1] || 0; - currentAssembly.components.push({ - pos: rawPos, - descricao: rowByCol.descricao || '', - quantidade: compQuant, - comprimento: compLength, - material: compMaterial, - pesoUnitario: compPUn, - pesoTotal: compPTot - }); + currentAssembly.components.push({ + pos: pos, + descricao: desc, + quantidade: quant, + comprimento: comp, + material: normalizeMaterial(mat), + pesoUnitario: pUn, + pesoTotal: pTot + }); + } + } } } @@ -391,11 +483,9 @@ const TecnometalConverterContent: React.FC = () => { if (isComposed) { // REGRA DE OURO TECNOMETAL: - // "para peças que tem varios componentes, quantidade, descricao (mesmo que perfil principal), - // peso unitario e peso total estarao na mesma linha. Para o comprimento e o material é necessario - // ver na lista de componentes aquele com maior comprimento e respectivamente na mesma linha obter tambem o material." + // Selecionar o componente subordinado com maior comprimento e extrair comprimento e material correspondente let bestComp = asm.components[0]; - maxComp = bestComp.comprimento; + maxComp = 0; for (const comp of asm.components) { if (comp.comprimento > maxComp) { @@ -406,7 +496,8 @@ const TecnometalConverterContent: React.FC = () => { if (bestComp) { materialPrincipal = bestComp.material || 'A36'; - perfilPrincipal = asm.descricao; // perfil principal da linha do conjunto + maxComp = bestComp.comprimento; + perfilPrincipal = asm.descricao; // perfil principal da linha mestre do conjunto } if (pesoTot === 0 && asm.components.length > 0) {