fix: calibracao advance steel quantidade marca 20, comprimentos e subtotal somatoria de peso

This commit is contained in:
2026-09-11 11:23:27 +00:00
parent f850f374fa
commit 6a7d761690
2 changed files with 331 additions and 477 deletions
+159 -236
View File
@@ -424,271 +424,191 @@
}
}
// Calibração inicial das colunas X
const colX = {
marca: 30,
qtde: 100,
nome: 180,
qualidade: 290,
comprimento: 370,
largura: 430,
pesoPeca: 490,
totalPeso: 550,
superficie: 610,
totalSuperficie: 670
};
const KNOWN_MATERIALS = [
'A572-GR 50', 'A572-GR50', 'A572-GR.50', 'A572 GR50', 'A572GR50', 'A572GR5', 'A572',
'A36', 'ASTM A36', 'ASTM A572', '10.9', '8.8', 'SAE 1020', 'SAE 1045', 'INOX', '304', '316'
];
for (const line of lines) {
const lineText = line.map(i => i.text).join(' ').toLowerCase();
if (lineText.includes('marca') && (lineText.includes('qtde') || lineText.includes('quant'))) {
for (const item of line) {
const t = item.text.toLowerCase().trim();
if (t === 'marca') colX.marca = item.x;
else if (t.startsWith('qtd') || t.startsWith('quant')) colX.qtde = item.x;
else if (t === 'nome') colX.nome = item.x;
else if (t.startsWith('qualid')) colX.qualidade = item.x;
else if (t.startsWith('comprim')) colX.comprimento = item.x;
else if (t.startsWith('largura')) colX.largura = item.x;
else if (t.includes('peso da') || t.includes('da peça') || t.includes('piece')) colX.pesoPeca = item.x;
else if (t.includes('total peso') || (t === 'total' && item.x < 600)) colX.totalPeso = item.x;
else if (t.includes('total superf') || (t === 'total' && item.x >= 600)) colX.totalSuperficie = item.x;
else if (t.startsWith('superf')) colX.superficie = item.x;
}
}
function normalizeLineText(lineStr) {
let s = lineStr.trim();
s = s.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)\s*-\s*(\d+)/g, '$1-$2-$3');
s = s.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)/g, '$1-$2');
s = s.replace(/A572\s*-\s*GR\s*50/gi, 'A572-GR 50');
s = s.replace(/A572\s*-\s*GR/gi, 'A572-GR');
return s;
}
const getNearestCol = (x) => {
let bestCol = '';
let minDiff = Infinity;
for (const [colName, targetX] of Object.entries(colX)) {
const diff = Math.abs(x - targetX);
if (diff < minDiff) {
minDiff = diff;
bestCol = colName;
}
}
return bestCol;
};
const assemblies = [];
let currentAssembly = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lineText = line.map(i => i.text).join(' ');
const rawLineText = line.map(it => it.text).join(' ').trim();
const lineText = normalizeLineText(rawLineText);
if (
lineText.includes('Lista de Peças') ||
lineText.includes('LISTA DE PEÇAS') ||
lineText.includes('LISTA ESTRUTURADA') ||
lineText.includes('DetailDwgExtract') ||
lineText.includes('Cliente:') ||
lineText.includes('Marca Quant Nome') ||
lineText.includes('Desenhado') ||
lineText.includes('Superfície') ||
lineText.includes('Trabalho:')
lineText.includes('Trabalho:') ||
lineText.includes('Kg/piece') ||
lineText.includes('( mm)') ||
(lineText.toLowerCase().includes('marca') && lineText.toLowerCase().includes('nome'))
) {
continue;
}
let markStr = '';
let markTokensConsumed = 0;
// 1. Linha Mestre de Peça Principal (ex: "B133-1-20 2 Pl 6x160x154" ou "B133-1-11 1 C 100x50x20x2.8")
const masterMatch = lineText.match(/^([A-Za-z0-9]+(?:-\d+)+)\s+(\d+)\s+([A-Za-z].*)$/);
if (masterMatch) {
const fullMark = masterMatch[1];
const quant = parseInt(masterMatch[2], 10);
const descCandidate = masterMatch[3].trim();
const parts = fullMark.split('-');
const pieceNumber = parts[parts.length - 1];
const pieceNumInt = parseInt(pieceNumber, 10);
const firstTok = line[0].text.trim();
if (firstTok.match(/^([A-Za-z0-9]+)-(\d+)-(\d+)$/) || firstTok.match(/^([A-Za-z0-9]+)-(\d+)$/)) {
markStr = firstTok;
markTokensConsumed = 1;
} else {
let candidate = '';
let found3 = null;
let found2 = null;
if (pieceNumInt < 1000) {
const hasMaterial = KNOWN_MATERIALS.some(m => descCandidate.includes(m));
const hasManyNumbers = (descCandidate.match(/[0-9.,]+/g) || []).length >= 3;
for (let k = 0; k < Math.min(line.length, 6); k++) {
candidate += line[k].text.trim();
if (candidate.match(/^([A-Za-z0-9]+)-(\d+)-(\d+)$/)) {
found3 = { mark: candidate, count: k + 1 };
break;
if (!hasMaterial && !hasManyNumbers) {
const ofCode = parts.length >= 2 ? parts[0] : defaultOf;
const phaseCode = parts.length >= 3 ? parts[1] : '0';
currentAssembly = {
rawMark: fullMark,
of: ofCode || defaultOf,
fase: phaseCode,
marca: pieceNumber,
numeroPeca: pieceNumber,
descricao: descCandidate || 'ESTRUTURA',
quantidade: quant > 0 ? quant : 1,
material: '',
comprimento: 0,
pesoUnitario: 0,
pesoTotal: 0,
subtotalPeso: 0,
isComposed: 'NÃO',
components: []
};
assemblies.push(currentAssembly);
continue;
}
if (candidate.match(/^([A-Za-z0-9]+)-(\d+)$/) && !found2) {
found2 = { mark: candidate, count: k + 1 };
}
}
if (found3) {
markStr = found3.mark;
markTokensConsumed = found3.count;
} else if (found2) {
markStr = found2.mark;
markTokensConsumed = found2.count;
}
}
if (markStr) {
const parts = markStr.split('-');
const ofCode = parts.length === 3 ? parts[0] : (parts.length === 2 ? parts[0] : defaultOf);
const phaseCode = parts.length === 3 ? parts[1] : '0';
const pieceNumber = parts.length === 3 ? parts[2] : parts[1];
const pieceNumInt = parseInt(pieceNumber, 10);
const isComponent = !isNaN(pieceNumInt) && pieceNumInt >= 1000;
const remainingItems = line.slice(markTokensConsumed);
if (!isComponent) {
// Se a marca for idêntica à da peça principal atual, é a linha de detalhe da peça simples!
if (currentAssembly && currentAssembly.rawMark === markStr) {
for (const it of remainingItems) {
const col = getNearestCol(it.x);
const valNum = parseNumeric(it.text);
if (col === 'qualidade' && !currentAssembly.material) {
currentAssembly.material = it.text;
} else if (col === 'comprimento' && valNum > 0) {
currentAssembly.comprimento = valNum;
} else if (col === 'pesoPeca' && valNum > 0) {
currentAssembly.pesoUnitario = valNum;
} else if (col === 'totalPeso' && valNum > 0) {
currentAssembly.pesoTotal = valNum;
}
}
continue;
}
// Nova Peça Principal
let mainQuant = 1;
const descTokens = [];
for (const it of remainingItems) {
const col = getNearestCol(it.x);
const qVal = parseInt(it.text, 10);
if (col === 'qtde' && !isNaN(qVal) && qVal > 0) {
mainQuant = qVal;
} else if (col === 'nome' || (!it.text.match(/^[0-9.,]+$/) && descTokens.length === 0)) {
descTokens.push(it.text);
}
}
if (mainQuant === 1 && remainingItems.length > 0) {
const firstNum = remainingItems.find((it) => /^[0-9]+$/.test(it.text.trim()));
if (firstNum) {
const parsedQ = parseInt(firstNum.text.trim(), 10);
if (parsedQ > 0 && parsedQ < 1000) mainQuant = parsedQ;
}
}
currentAssembly = {
rawMark: markStr,
of: ofCode || defaultOf,
fase: phaseCode,
marca: pieceNumber,
numeroPeca: pieceNumber,
descricao: descTokens.join(' ') || 'ESTRUTURA',
quantidade: mainQuant,
material: '',
comprimento: 0,
pesoUnitario: 0,
pesoTotal: 0,
subtotalPeso: 0,
isComposed: 'NÃO',
components: []
};
assemblies.push(currentAssembly);
} else {
// Componente com marca >= 1000 (Peça Composta)
if (currentAssembly) {
currentAssembly.isComposed = 'SIM';
let compQuant = 1;
const compDescTokens = [];
let compMaterial = 'A36';
let compLength = 0;
let compWeightTotal = 0;
for (const it of remainingItems) {
const col = getNearestCol(it.x);
const valNum = parseNumeric(it.text);
const t = it.text.trim();
if (col === 'qtde') {
const qVal = parseInt(t, 10);
if (!isNaN(qVal)) compQuant = qVal;
} else if (col === 'nome') {
compDescTokens.push(t);
} else if (col === 'qualidade') {
compMaterial = t;
} else if (col === 'comprimento' && valNum > 0) {
compLength = valNum;
} else if (col === 'totalPeso' && valNum > 0) {
compWeightTotal = valNum;
}
}
if (compWeightTotal === 0) {
const nums = remainingItems.map((it) => parseNumeric(it.text)).filter((n) => n > 0);
if (nums.length >= 2) {
compWeightTotal = nums[nums.length - 1];
}
}
currentAssembly.components.push({
quantidade: compQuant,
perfil: compDescTokens.join(' ') || currentAssembly.descricao,
material: compMaterial,
comprimento: compLength,
pesoTotal: compWeightTotal
});
// 2. Linha com Subtotal da Marca (ex: "81 3,07" ou "3,5 0,155" ou "2,6 0,25")
const tokens = lineText.split(/\s+/);
const allTokensNumeric = tokens.length > 0 && tokens.every(tok => /^[0-9.,]+$/.test(tok));
if (allTokensNumeric && tokens.length <= 3) {
if (currentAssembly && (!currentAssembly.subtotalPeso || currentAssembly.subtotalPeso === 0)) {
const subP = parseNumeric(tokens[0]);
if (subP > 0) {
currentAssembly.subtotalPeso = subP;
}
}
continue;
}
// 3. Linha de Detalhe ou Componente
let itemMark = '';
let itemQuant = 1;
let remaining = lineText;
const mMark = remaining.match(/^([A-Za-z0-9]+(?:-\d+)+|-)\s+(\d+)\s+/);
if (mMark) {
itemMark = mMark[1];
itemQuant = parseInt(mMark[2], 10);
remaining = remaining.substring(mMark[0].length).trim();
} else if (remaining.startsWith('-')) {
itemMark = '-';
remaining = remaining.substring(1).trim();
const mQ = remaining.match(/^(\d+)\s+/);
if (mQ) {
itemQuant = parseInt(mQ[1], 10);
remaining = remaining.substring(mQ[0].length).trim();
}
}
let detectedMaterial = '';
let matIdx = -1;
let matLen = 0;
for (const mat of KNOWN_MATERIALS) {
const idx = remaining.indexOf(mat);
if (idx !== -1 && idx > matIdx) {
detectedMaterial = mat;
matIdx = idx;
matLen = mat.length;
}
}
let itemDesc = '';
let afterMat = '';
if (matIdx !== -1) {
itemDesc = remaining.substring(0, matIdx).trim();
afterMat = remaining.substring(matIdx + matLen).trim();
} else {
// Linhas sem marca explícita
if (currentAssembly) {
if (lineText.includes('M13') || lineText.startsWith('-') || lineText.includes('ASTM') || lineText.includes('10.9')) {
currentAssembly.isComposed = 'SIM';
let compLength = 0;
let compWeightTotal = 0;
let compQuant = 1;
const parts = remaining.split(/\s+/);
const firstNumIdx = parts.findIndex(p => /^[0-9.,]+$/.test(p));
if (firstNumIdx > 0) {
itemDesc = parts.slice(0, firstNumIdx).join(' ');
afterMat = parts.slice(firstNumIdx).join(' ');
} else {
itemDesc = remaining;
}
}
for (const it of line) {
const col = getNearestCol(it.x);
const valNum = parseNumeric(it.text);
if (col === 'qtde') {
const q = parseInt(it.text, 10);
if (!isNaN(q)) compQuant = q;
} else if (col === 'comprimento' && valNum > 0) {
compLength = valNum;
} else if (col === 'totalPeso' && valNum > 0) {
compWeightTotal = valNum;
}
}
const numTokens = afterMat.split(/\s+/).filter(tok => /^[0-9.,]+$/.test(tok));
const nums = numTokens.map(parseNumeric);
currentAssembly.components.push({
quantidade: compQuant,
perfil: lineText.split(' ')[0] || 'ACESSÓRIO',
material: '10.9',
comprimento: compLength,
pesoTotal: compWeightTotal
});
} else if (lineText.includes('W ') || lineText.includes('C ') || lineText.includes('Pl ')) {
let compLength = 0;
let compWeightTotal = 0;
let compMaterial = 'A36';
let itemComp = 0;
let itemPesoTot = 0;
for (const it of line) {
const col = getNearestCol(it.x);
const valNum = parseNumeric(it.text);
if (col === 'qualidade') compMaterial = it.text;
else if (col === 'comprimento' && valNum > 0) compLength = valNum;
else if (col === 'totalPeso' && valNum > 0) compWeightTotal = valNum;
}
if (nums.length >= 1) {
itemComp = nums[0];
}
currentAssembly.components.push({
quantidade: 1,
perfil: lineText.split(' ')[0] + ' ' + (lineText.split(' ')[1] || ''),
material: compMaterial,
comprimento: compLength,
pesoTotal: compWeightTotal
});
} else {
for (const it of line) {
const col = getNearestCol(it.x);
const valNum = parseNumeric(it.text);
if (col === 'totalPeso' && valNum > 0) {
currentAssembly.subtotalPeso = valNum;
}
}
if (nums.length === 3) {
itemPesoTot = nums[2];
} else if (nums.length === 4) {
itemPesoTot = nums[2];
} else if (nums.length === 5) {
itemPesoTot = nums[2];
} else if (nums.length >= 6) {
itemPesoTot = nums[3];
}
if (currentAssembly) {
const isSubComp =
itemMark === '-' ||
itemMark.includes('-100') ||
itemMark.includes('-10') ||
(itemMark.match(/-\d+$/) && parseInt(itemMark.match(/-(\d+)$/)[1], 10) >= 1000);
if (isSubComp) {
currentAssembly.isComposed = 'SIM';
currentAssembly.components.push({
quantidade: itemQuant,
perfil: itemDesc || currentAssembly.descricao,
material: detectedMaterial || 'A36',
comprimento: itemComp,
pesoTotal: itemPesoTot
});
} else {
if (!currentAssembly.material && detectedMaterial) {
currentAssembly.material = detectedMaterial;
}
if (itemComp > 0 && (!currentAssembly.comprimento || currentAssembly.comprimento === 0)) {
currentAssembly.comprimento = itemComp;
}
if (itemPesoTot > 0 && (!currentAssembly.pesoTotal || currentAssembly.pesoTotal === 0)) {
currentAssembly.pesoTotal = itemPesoTot;
}
}
}
@@ -706,6 +626,8 @@
if (isComposed) {
let sumComponentsWeight = 0;
let bestComp = asm.components[0];
maxComp = 0;
asm.components.forEach(c => {
if (c.comprimento > maxComp) {
maxComp = c.comprimento;
@@ -717,6 +639,7 @@
if (bestComp) {
perfilPrincipal = bestComp.perfil || asm.descricao;
materialPrincipal = bestComp.material || 'A36';
maxComp = bestComp.comprimento;
}
if (!totalPeso || totalPeso === 0) {
@@ -215,51 +215,18 @@ const AdvanceSteelConverterContent: React.FC = () => {
}
}
// Calibração inicial das colunas X para o layout padrão do Advance Steel
const colX = {
marca: 30,
qtde: 100,
nome: 180,
qualidade: 290,
comprimento: 370,
largura: 430,
pesoPeca: 490,
totalPeso: 550,
superficie: 610,
totalSuperficie: 670
};
const KNOWN_MATERIALS = [
'A572-GR 50', 'A572-GR50', 'A572-GR.50', 'A572 GR50', 'A572GR50', 'A572GR5', 'A572',
'A36', 'ASTM A36', 'ASTM A572', '10.9', '8.8', 'SAE 1020', 'SAE 1045', 'INOX', '304', '316'
];
// Tentar recalibrar X dinamicamente a partir dos títulos das colunas
for (const line of lines) {
const lineText = line.map((i) => i.text).join(' ').toLowerCase();
if (lineText.includes('marca') && (lineText.includes('qtde') || lineText.includes('quant'))) {
for (const item of line) {
const t = item.text.toLowerCase().trim();
if (t === 'marca') colX.marca = item.x;
else if (t.startsWith('qtd') || t.startsWith('quant')) colX.qtde = item.x;
else if (t === 'nome') colX.nome = item.x;
else if (t.startsWith('qualid')) colX.qualidade = item.x;
else if (t.startsWith('comprim')) colX.comprimento = item.x;
else if (t.startsWith('largura')) colX.largura = item.x;
else if (t.includes('peso da') || t.includes('da peça') || t.includes('piece')) colX.pesoPeca = item.x;
else if (t.includes('total peso') || (t === 'total' && item.x < 600)) colX.totalPeso = item.x;
else if (t.includes('total superf') || (t === 'total' && item.x >= 600)) colX.totalSuperficie = item.x;
else if (t.startsWith('superf')) colX.superficie = item.x;
}
}
}
const getNearestCol = (x: number): string => {
let bestCol = '';
let minDiff = Infinity;
for (const [colName, targetX] of Object.entries(colX)) {
const diff = Math.abs(x - targetX);
if (diff < minDiff) {
minDiff = diff;
bestCol = colName;
}
}
return bestCol;
const normalizeLineText = (lineStr: string): string => {
let s = lineStr.trim();
s = s.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)\s*-\s*(\d+)/g, '$1-$2-$3');
s = s.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)/g, '$1-$2');
s = s.replace(/A572\s*-\s*GR\s*50/gi, 'A572-GR 50');
s = s.replace(/A572\s*-\s*GR/gi, 'A572-GR');
return s;
};
const assemblies: AssemblyItem[] = [];
@@ -267,224 +234,184 @@ const AdvanceSteelConverterContent: React.FC = () => {
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();
const lineText = normalizeLineText(rawLineText);
// Ignorar cabeçalhos e metadados conhecidos
if (
lineText.includes('Lista de Peças') ||
lineText.includes('LISTA DE PEÇAS') ||
lineText.includes('LISTA ESTRUTURADA') ||
lineText.includes('DetailDwgExtract') ||
lineText.includes('Cliente:') ||
lineText.includes('Marca Quant Nome') ||
lineText.includes('Desenhado') ||
lineText.includes('Superfície') ||
lineText.includes('Trabalho:')
lineText.includes('Trabalho:') ||
lineText.includes('Kg/piece') ||
lineText.includes('( mm)') ||
(lineText.toLowerCase().includes('marca') && lineText.toLowerCase().includes('nome'))
) {
continue;
}
// 1. Identificar se a linha inicia com marca ou aglutinação de tokens (ex: B133-1-20)
let markStr = '';
let markTokensConsumed = 0;
// 1. Verificar se é Linha Mestre de Peça Principal (ex: "B133-1-20 2 Pl 6x160x154" ou "B133-1-11 1 C 100x50x20x2.8")
const masterMatch = lineText.match(/^([A-Za-z0-9]+(?:-\d+)+)\s+(\d+)\s+([A-Za-z].*)$/);
let isMasterLine = false;
const firstTok = line[0].text.trim();
if (firstTok.match(/^([A-Za-z0-9]+)-(\d+)-(\d+)$/) || firstTok.match(/^([A-Za-z0-9]+)-(\d+)$/)) {
markStr = firstTok;
markTokensConsumed = 1;
} else {
let candidate = '';
let found3: { mark: string; count: number } | null = null;
let found2: { mark: string; count: number } | null = null;
if (masterMatch) {
const fullMark = masterMatch[1];
const quant = parseInt(masterMatch[2], 10);
const descCandidate = masterMatch[3].trim();
const parts = fullMark.split('-');
const pieceNumber = parts[parts.length - 1];
const pieceNumInt = parseInt(pieceNumber, 10);
for (let k = 0; k < Math.min(line.length, 6); k++) {
candidate += line[k].text.trim();
if (candidate.match(/^([A-Za-z0-9]+)-(\d+)-(\d+)$/)) {
found3 = { mark: candidate, count: k + 1 };
break;
// Se o número da peça for menor que 1000, é uma peça principal mestre
if (pieceNumInt < 1000) {
// Checar se não é uma linha completa de detalhe com material e medidas
const hasMaterial = KNOWN_MATERIALS.some((m) => descCandidate.includes(m));
const hasManyNumbers = (descCandidate.match(/[0-9.,]+/g) || []).length >= 3;
if (!hasMaterial && !hasManyNumbers) {
isMasterLine = true;
const ofCode = parts.length >= 2 ? parts[0] : defaultOf;
const phaseCode = parts.length >= 3 ? parts[1] : '0';
currentAssembly = {
rawMark: fullMark,
of: ofCode || defaultOf,
fase: phaseCode,
marca: pieceNumber,
numeroPeca: pieceNumber,
descricao: descCandidate || 'ESTRUTURA',
quantidade: quant > 0 ? quant : 1,
material: '',
comprimento: 0,
pesoUnitario: 0,
pesoTotal: 0,
subtotalPeso: 0,
isComposed: 'NÃO',
components: []
};
assemblies.push(currentAssembly);
continue;
}
if (candidate.match(/^([A-Za-z0-9]+)-(\d+)$/) && !found2) {
found2 = { mark: candidate, count: k + 1 };
}
}
if (found3) {
markStr = found3.mark;
markTokensConsumed = found3.count;
} else if (found2) {
markStr = found2.mark;
markTokensConsumed = found2.count;
}
}
if (markStr) {
const parts = markStr.split('-');
const ofCode = parts.length === 3 ? parts[0] : (parts.length === 2 ? parts[0] : defaultOf);
const phaseCode = parts.length === 3 ? parts[1] : '0';
const pieceNumber = parts.length === 3 ? parts[2] : parts[1];
const pieceNumInt = parseInt(pieceNumber, 10);
const isComponent = !isNaN(pieceNumInt) && pieceNumInt >= 1000;
const remainingItems = line.slice(markTokensConsumed);
if (!isComponent) {
// Se a marca for idêntica à da peça principal atual, é a linha de detalhe da peça simples!
if (currentAssembly && currentAssembly.rawMark === markStr) {
for (const it of remainingItems) {
const col = getNearestCol(it.x);
const valNum = parseNumeric(it.text);
if (col === 'qualidade' && !currentAssembly.material) {
currentAssembly.material = it.text;
} else if (col === 'comprimento' && valNum > 0) {
currentAssembly.comprimento = valNum;
} else if (col === 'pesoPeca' && valNum > 0) {
currentAssembly.pesoUnitario = valNum;
} else if (col === 'totalPeso' && valNum > 0) {
currentAssembly.pesoTotal = valNum;
}
}
continue;
}
// Nova Peça Principal
let mainQuant = 1;
const descTokens: string[] = [];
for (const it of remainingItems) {
const col = getNearestCol(it.x);
const qVal = parseInt(it.text, 10);
if (col === 'qtde' && !isNaN(qVal) && qVal > 0) {
mainQuant = qVal;
} else if (col === 'nome' || (!it.text.match(/^[0-9.,]+$/) && descTokens.length === 0)) {
descTokens.push(it.text);
}
}
if (mainQuant === 1 && remainingItems.length > 0) {
const firstNum = remainingItems.find((it) => /^[0-9]+$/.test(it.text.trim()));
if (firstNum) {
const parsedQ = parseInt(firstNum.text.trim(), 10);
if (parsedQ > 0 && parsedQ < 1000) mainQuant = parsedQ;
}
}
currentAssembly = {
rawMark: markStr,
of: ofCode || defaultOf,
fase: phaseCode,
marca: pieceNumber,
numeroPeca: pieceNumber,
descricao: descTokens.join(' ') || 'ESTRUTURA',
quantidade: mainQuant,
material: '',
comprimento: 0,
pesoUnitario: 0,
pesoTotal: 0,
subtotalPeso: 0,
isComposed: 'NÃO',
components: []
};
assemblies.push(currentAssembly);
} else {
// Componente com marca >= 1000 (Peça Composta)
if (currentAssembly) {
currentAssembly.isComposed = 'SIM';
let compQuant = 1;
const compDescTokens: string[] = [];
let compMaterial = 'A36';
let compLength = 0;
let compWeightTotal = 0;
for (const it of remainingItems) {
const col = getNearestCol(it.x);
const valNum = parseNumeric(it.text);
const t = it.text.trim();
if (col === 'qtde') {
const qVal = parseInt(t, 10);
if (!isNaN(qVal)) compQuant = qVal;
} else if (col === 'nome') {
compDescTokens.push(t);
} else if (col === 'qualidade') {
compMaterial = t;
} else if (col === 'comprimento' && valNum > 0) {
compLength = valNum;
} else if (col === 'totalPeso' && valNum > 0) {
compWeightTotal = valNum;
}
}
// Fallback caso coordenadas não tenham capturado peso total do componente
if (compWeightTotal === 0) {
const nums = remainingItems.map((it) => parseNumeric(it.text)).filter((n) => n > 0);
if (nums.length >= 2) {
compWeightTotal = nums[nums.length - 1];
}
}
currentAssembly.components.push({
quantidade: compQuant,
perfil: compDescTokens.join(' ') || currentAssembly.descricao,
material: compMaterial,
comprimento: compLength,
pesoTotal: compWeightTotal
});
// 2. Linha com Subtotal da Marca (ex: "81 3,07" ou "3,5 0,155" ou "2,6 0,25")
const tokens = lineText.split(/\s+/);
const allTokensNumeric = tokens.length > 0 && tokens.every((tok) => /^[0-9.,]+$/.test(tok));
if (allTokensNumeric && tokens.length <= 3) {
if (currentAssembly && (!currentAssembly.subtotalPeso || currentAssembly.subtotalPeso === 0)) {
const subP = parseNumeric(tokens[0]);
if (subP > 0) {
currentAssembly.subtotalPeso = subP;
}
}
continue;
}
// 3. Linha de Detalhe de Monopeça ou Componente Subordinado
// Ex: "B133-1-11 1 C 100x50x20x2.8 A36 540 2,6 2,6 0,25 0,25"
// Ex: "B133-1-1000 1 Pl 6x120x91 A36 120 91 0,5 1,1 0,024 0,049"
// Ex: "B133-1-1002 1 W 150x18.0 A572-GR 50 4.000 72 72 2,76 2,76"
// Ex: "- 12 M13 NaW 32 ASTM A325 10.9 10.9 32 0,1 1,7"
let itemMark = '';
let itemQuant = 1;
let remaining = lineText;
const mMark = remaining.match(/^([A-Za-z0-9]+(?:-\d+)+|-)\s+(\d+)\s+/);
if (mMark) {
itemMark = mMark[1];
itemQuant = parseInt(mMark[2], 10);
remaining = remaining.substring(mMark[0].length).trim();
} else if (remaining.startsWith('-')) {
itemMark = '-';
remaining = remaining.substring(1).trim();
const mQ = remaining.match(/^(\d+)\s+/);
if (mQ) {
itemQuant = parseInt(mQ[1], 10);
remaining = remaining.substring(mQ[0].length).trim();
}
}
// Localizar Material dentro de remaining
let detectedMaterial = '';
let matIdx = -1;
let matLen = 0;
for (const mat of KNOWN_MATERIALS) {
const idx = remaining.indexOf(mat);
if (idx !== -1 && idx > matIdx) {
detectedMaterial = mat;
matIdx = idx;
matLen = mat.length;
}
}
let itemDesc = '';
let afterMat = '';
if (matIdx !== -1) {
itemDesc = remaining.substring(0, matIdx).trim();
afterMat = remaining.substring(matIdx + matLen).trim();
} else {
// Linhas sem marca explícita (subitens, parafusos '-' ou linha de subtotal)
if (currentAssembly) {
if (lineText.includes('M13') || lineText.startsWith('-') || lineText.includes('ASTM') || lineText.includes('10.9')) {
currentAssembly.isComposed = 'SIM';
let compLength = 0;
let compWeightTotal = 0;
let compQuant = 1;
const parts = remaining.split(/\s+/);
const firstNumIdx = parts.findIndex((p) => /^[0-9.,]+$/.test(p));
if (firstNumIdx > 0) {
itemDesc = parts.slice(0, firstNumIdx).join(' ');
afterMat = parts.slice(firstNumIdx).join(' ');
} else {
itemDesc = remaining;
}
}
for (const it of line) {
const col = getNearestCol(it.x);
const valNum = parseNumeric(it.text);
if (col === 'qtde') {
const q = parseInt(it.text, 10);
if (!isNaN(q)) compQuant = q;
} else if (col === 'comprimento' && valNum > 0) {
compLength = valNum;
} else if (col === 'totalPeso' && valNum > 0) {
compWeightTotal = valNum;
}
}
const numTokens = afterMat.split(/\s+/).filter((tok) => /^[0-9.,]+$/.test(tok));
const nums = numTokens.map(parseNumeric);
currentAssembly.components.push({
quantidade: compQuant,
perfil: lineText.split(' ')[0] || 'ACESSÓRIO',
material: '10.9',
comprimento: compLength,
pesoTotal: compWeightTotal
});
} else if (lineText.includes('W ') || lineText.includes('C ') || lineText.includes('Pl ')) {
// Perfil principal de conjunto composto
let compLength = 0;
let compWeightTotal = 0;
let compMaterial = 'A36';
let itemComp = 0;
let itemPesoTot = 0;
for (const it of line) {
const col = getNearestCol(it.x);
const valNum = parseNumeric(it.text);
if (col === 'qualidade') compMaterial = it.text;
else if (col === 'comprimento' && valNum > 0) compLength = valNum;
else if (col === 'totalPeso' && valNum > 0) compWeightTotal = valNum;
}
if (nums.length >= 1) {
itemComp = nums[0]; // O comprimento é sempre o primeiro valor numérico após o material
}
currentAssembly.components.push({
quantidade: 1,
perfil: lineText.split(' ')[0] + ' ' + (lineText.split(' ')[1] || ''),
material: compMaterial,
comprimento: compLength,
pesoTotal: compWeightTotal
});
} else {
// Linha com totais do conjunto (ex: 3,5 na coluna Total Peso)
for (const it of line) {
const col = getNearestCol(it.x);
const valNum = parseNumeric(it.text);
if (col === 'totalPeso' && valNum > 0) {
currentAssembly.subtotalPeso = valNum;
}
}
if (nums.length === 3) {
itemPesoTot = nums[2];
} else if (nums.length === 4) {
itemPesoTot = nums[2];
} else if (nums.length === 5) {
itemPesoTot = nums[2];
} else if (nums.length >= 6) {
itemPesoTot = nums[3];
}
if (currentAssembly) {
const isSubComp =
itemMark === '-' ||
itemMark.includes('-100') ||
itemMark.includes('-10') ||
(itemMark.match(/-\d+$/) && parseInt(itemMark.match(/-(\d+)$/)![1], 10) >= 1000);
if (isSubComp) {
currentAssembly.isComposed = 'SIM';
currentAssembly.components.push({
quantidade: itemQuant,
perfil: itemDesc || currentAssembly.descricao,
material: detectedMaterial || 'A36',
comprimento: itemComp,
pesoTotal: itemPesoTot
});
} else {
// Monopeça (peça simples sem componentes subordinados)
if (!currentAssembly.material && detectedMaterial) {
currentAssembly.material = detectedMaterial;
}
if (itemComp > 0 && (!currentAssembly.comprimento || currentAssembly.comprimento === 0)) {
currentAssembly.comprimento = itemComp;
}
if (itemPesoTot > 0 && (!currentAssembly.pesoTotal || currentAssembly.pesoTotal === 0)) {
currentAssembly.pesoTotal = itemPesoTot;
}
}
}
@@ -502,6 +429,8 @@ const AdvanceSteelConverterContent: React.FC = () => {
if (isComposed) {
let sumComponentsWeight = 0;
let bestComp = asm.components[0];
maxComp = 0;
asm.components.forEach((c) => {
if (c.comprimento > maxComp) {
maxComp = c.comprimento;
@@ -513,8 +442,10 @@ const AdvanceSteelConverterContent: React.FC = () => {
if (bestComp) {
perfilPrincipal = bestComp.perfil || asm.descricao;
materialPrincipal = bestComp.material || 'A36';
maxComp = bestComp.comprimento;
}
// Se não obteve subtotal na linha isolada, usar a somatória dos componentes
if (!totalPeso || totalPeso === 0) {
totalPeso = sumComponentsWeight;
}