fix: calibracao das quantidades e dimensoes do conversor tecnometal

This commit is contained in:
2026-09-11 11:33:26 +00:00
parent 6a7d761690
commit e77912785f
2 changed files with 313 additions and 130 deletions
+153 -61
View File
@@ -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;
}
@@ -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<string, string> = {};
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) {