feat: sistema de deteccao de conflitos, conciliacao e ordem de intervencao fabril na importacao de pecas

This commit is contained in:
2026-10-16 15:27:30 -03:00
parent ac8bacc9e1
commit ec980cba05
10 changed files with 1975 additions and 11 deletions
+531
View File
@@ -0,0 +1,531 @@
import { supabase } from '@/integrations/supabase/client';
import {
DiffCampo,
DiffComponente,
ItemAnaliseImportacao,
NivelRiscoImportacao,
ProcessoApontado,
ResultadoAnaliseImportacao,
} from '@/types/importDiff';
interface CampoConfig {
key: string;
label: string;
isCritico: boolean;
isNumeric?: boolean;
}
const CAMPOS_MONITORADOS: CampoConfig[] = [
{ key: 'quantidade', label: 'Quantidade (un)', isCritico: true, isNumeric: true },
{ key: 'comprimento', label: 'Comprimento (mm)', isCritico: true, isNumeric: true },
{ key: 'perfil_principal', label: 'Perfil / Bitola', isCritico: true },
{ key: 'peso_unitario', label: 'Peso Unitário (kg)', isCritico: true, isNumeric: true },
{ key: 'peso_total', label: 'Peso Total (kg)', isCritico: true, isNumeric: true },
{ key: 'material', label: 'Material', isCritico: false },
{ key: 'tratamento_superficial', label: 'Tratamento', isCritico: false },
{ key: 'descricao', label: 'Descrição', isCritico: false },
{ key: 'tem_componentes', label: 'Tem Componentes', isCritico: true },
];
/**
* Normaliza e agrupa linhas de peças e subcomponentes
*/
export function consolidarPecasEComponentes(rawItems: any[]): {
pecasMap: Map<string, any>;
componentesMap: Map<string, any[]>;
} {
const pecasMap = new Map<string, any>();
const componentesMap = new Map<string, any[]>();
rawItems.forEach((item) => {
const ofNumber = String(item.of_number || '').trim();
const etapaFase = String(item.etapa_fase || '1').trim();
const marca = String(item.marca || '').trim();
if (!marca) return;
const pecaKey = `${ofNumber}-${etapaFase}-${marca}`;
const pecaData = {
of_number: ofNumber,
etapa_fase: etapaFase,
marca: marca,
descricao: item.descricao || item.perfil_principal || '',
quantidade: Number(item.quantidade) || 0,
peso_unitario: Number(item.peso_unitario) || 0,
peso_total: Number(item.peso_total) || 0,
comprimento:
item.comprimento !== undefined && item.comprimento !== null
? Number(item.comprimento)
: item.comprimento_ref
? Number(item.comprimento_ref)
: null,
tratamento_superficial: item.tratamento_superficial || 'pintura',
material: item.material || 'Aço A36',
perfil_principal: item.perfil_principal || item.descricao || '',
tem_componentes: Boolean(item.tem_componentes),
};
if (!pecasMap.has(pecaKey)) {
pecasMap.set(pecaKey, pecaData);
}
// Se o item contém um subcomponente
if (item.marca_componente && String(item.marca_componente).trim()) {
const rawMarca = String(item.marca_componente).trim();
let cleanMarca = rawMarca;
const matchSuffix = rawMarca.match(/-?(\d{4,})$/) || rawMarca.match(/(\d{4,})/);
if (matchSuffix) {
cleanMarca = matchSuffix[1];
} else {
const digits = rawMarca.replace(/\D/g, '');
if (digits.length >= 4) {
cleanMarca = digits.slice(-4);
} else {
cleanMarca = String(1000 + (componentesMap.get(pecaKey)?.length || 0));
}
}
let numVal = parseInt(cleanMarca, 10);
if (isNaN(numVal) || numVal < 1000 || numVal > 9999) {
numVal = 1000 + (componentesMap.get(pecaKey)?.length || 0);
}
cleanMarca = String(numVal);
const compData = {
marca_componente: cleanMarca,
descricao: item.descricao_componente || item.descricao || '',
perfil: item.perfil_componente || item.perfil_principal || '',
peso_unitario: Number(item.peso_unitario_componente) || 0,
comprimento: item.comprimento_componente ? Number(item.comprimento_componente) : null,
quantidade_por_peca: Math.max(1, Math.round(Number(item.quantidade_por_peca) || 1)),
};
if (!componentesMap.has(pecaKey)) {
componentesMap.set(pecaKey, []);
}
// Evita duplicatas idênticas no mesmo lote
const comps = componentesMap.get(pecaKey)!;
const exists = comps.some((c) => c.marca_componente === cleanMarca);
if (!exists) {
comps.push(compData);
}
}
});
return { pecasMap, componentesMap };
}
/**
* Analisa a lista de importação e cruza com a base de dados
*/
export async function analisarConflitosImportacao(
rawItems: any[]
): Promise<ResultadoAnaliseImportacao> {
const { pecasMap, componentesMap } = consolidarPecasEComponentes(rawItems);
const pecasImportadas = Array.from(pecasMap.values());
if (pecasImportadas.length === 0) {
return {
totalImportadas: 0,
totalNovas: 0,
totalIdenticas: 0,
totalComDivergencia: 0,
totalComApontamento: 0,
totalCriticas: 0,
itens: [],
};
}
// 1. Extrair OFs únicas
const ofsUnicas = Array.from(
new Set(pecasImportadas.map((p) => p.of_number).filter(Boolean))
);
// 2. Buscar peças existentes no Supabase
let pecasExistentes: any[] = [];
if (ofsUnicas.length > 0) {
const { data, error } = await supabase
.from('pecas')
.select('*')
.in('of_number', ofsUnicas);
if (error) {
console.error('❌ Erro ao consultar peças existentes para diff:', error);
} else {
pecasExistentes = data || [];
}
}
// Mapear peças existentes por chave
const existingMap = new Map<string, any>();
const existingIds: string[] = [];
pecasExistentes.forEach((p) => {
const key = `${p.of_number || ''}-${p.etapa_fase || ''}-${p.marca || ''}`;
existingMap.set(key, p);
existingIds.push(p.id);
});
// 3. Buscar componentes existentes
const existingComponentsByPecaId = new Map<string, any[]>();
if (existingIds.length > 0) {
const { data: compData, error: compErr } = await supabase
.from('componentes_peca')
.select('*')
.in('peca_id', existingIds);
if (!compErr && compData) {
compData.forEach((c) => {
if (!existingComponentsByPecaId.has(c.peca_id)) {
existingComponentsByPecaId.set(c.peca_id, []);
}
existingComponentsByPecaId.get(c.peca_id)!.push(c);
});
}
}
// 4. Buscar apontamentos de produção existentes
const apontamentosByPecaId = new Map<string, any[]>();
if (existingIds.length > 0) {
const { data: aptData, error: aptErr } = await supabase
.from('apontamentos_producao')
.select(`
id,
peca_id,
processo_id,
quantidade_produzida,
data_apontamento,
processo:processos_fabricacao(id, nome, ordem)
`)
.in('peca_id', existingIds);
if (!aptErr && aptData) {
aptData.forEach((a) => {
if (!apontamentosByPecaId.has(a.peca_id)) {
apontamentosByPecaId.set(a.peca_id, []);
}
apontamentosByPecaId.get(a.peca_id)!.push(a);
});
}
}
// 5. Buscar romaneios de expedição
const pecaIdsEmRomaneio = new Set<string>();
if (existingIds.length > 0) {
const { data: romData, error: romErr } = await supabase
.from('itens_romaneio_pecas')
.select('peca_id')
.in('peca_id', existingIds);
if (!romErr && romData) {
romData.forEach((r) => pecaIdsEmRomaneio.add(r.peca_id));
}
}
// 6. Construir a análise detalhada item a item
const itensResultado: ItemAnaliseImportacao[] = [];
for (const pecaImportada of pecasImportadas) {
const key = `${pecaImportada.of_number || ''}-${pecaImportada.etapa_fase || ''}-${pecaImportada.marca || ''}`;
const pecaExistente = existingMap.get(key);
const incomingComps = componentesMap.get(key) || [];
if (!pecaExistente) {
// Peça NOVA
itensResultado.push({
key,
of_number: pecaImportada.of_number,
etapa_fase: pecaImportada.etapa_fase,
marca: pecaImportada.marca,
descricao: pecaImportada.descricao || pecaImportada.perfil_principal || '',
isNova: true,
isIdentica: false,
temDivergencias: false,
camposDivergentes: [],
componentesDivergentes: incomingComps.map((c) => ({
marca_componente: c.marca_componente,
descricao: c.descricao,
perfil: c.perfil,
tipo: 'adicionado',
quantidadeNova: c.quantidade_por_peca,
comprimentoNovo: c.comprimento,
detalhe: 'Novo componente na peça',
})),
dadosImportados: pecaImportada,
apontamentoResumo: {
totalProduzido: 0,
processos: [],
temApontamento: false,
emRomaneio: false,
},
nivelRisco: 'nenhum',
sobrepor: true,
atencaoEspecial: false,
});
continue;
}
// Peça EXISTENTE: Comparar campos
const pecaId = pecaExistente.id;
const camposDivergentes: DiffCampo[] = [];
CAMPOS_MONITORADOS.forEach(({ key: campoKey, label, isCritico, isNumeric }) => {
let valAntigo = pecaExistente[campoKey];
let valNovo = pecaImportada[campoKey];
if (isNumeric) {
valAntigo = valAntigo !== null && valAntigo !== undefined ? Number(valAntigo) : null;
valNovo = valNovo !== null && valNovo !== undefined ? Number(valNovo) : null;
if (valAntigo !== null && valNovo !== null) {
if (Math.abs(valAntigo - valNovo) > 0.001) {
camposDivergentes.push({
campo: campoKey,
label,
valorAntigo: valAntigo,
valorNovo: valNovo,
isCritico,
});
}
} else if (valAntigo !== valNovo) {
camposDivergentes.push({
campo: campoKey,
label,
valorAntigo: valAntigo,
valorNovo: valNovo,
isCritico,
});
}
} else {
const strAntigo = String(valAntigo || '').trim().toLowerCase();
const strNovo = String(valNovo || '').trim().toLowerCase();
if (strAntigo !== strNovo) {
camposDivergentes.push({
campo: campoKey,
label,
valorAntigo: pecaExistente[campoKey] || '-',
valorNovo: pecaImportada[campoKey] || '-',
isCritico,
});
}
}
});
// Comparar componentes existentes vs importados
const existingComps = existingComponentsByPecaId.get(pecaId) || [];
const componentesDivergentes: DiffComponente[] = [];
const existingCompsMap = new Map<string, any>();
existingComps.forEach((c) => existingCompsMap.set(String(c.marca_componente).trim(), c));
const incomingCompsMap = new Map<string, any>();
incomingComps.forEach((c) => incomingCompsMap.set(String(c.marca_componente).trim(), c));
// Verificar adicionados ou modificados
incomingComps.forEach((inc) => {
const matchMarca = String(inc.marca_componente).trim();
const ext = existingCompsMap.get(matchMarca);
if (!ext) {
componentesDivergentes.push({
marca_componente: matchMarca,
descricao: inc.descricao,
perfil: inc.perfil,
tipo: 'adicionado',
quantidadeNova: inc.quantidade_por_peca,
comprimentoNovo: inc.comprimento,
detalhe: `Novo componente Pos. ${matchMarca} adicionado`,
});
} else {
const mudouQtd = Number(ext.quantidade_por_peca || 1) !== Number(inc.quantidade_por_peca || 1);
const mudouCompr =
ext.comprimento !== null && inc.comprimento !== null
? Math.abs(Number(ext.comprimento) - Number(inc.comprimento)) > 0.1
: ext.comprimento !== inc.comprimento;
const mudouPerfil =
String(ext.perfil || '').trim().toLowerCase() !== String(inc.perfil || '').trim().toLowerCase();
if (mudouQtd || mudouCompr || mudouPerfil) {
const detalhes: string[] = [];
if (mudouQtd) detalhes.push(`Qtd: ${ext.quantidade_por_peca} ➔ ${inc.quantidade_por_peca}`);
if (mudouCompr) detalhes.push(`Compr: ${ext.comprimento || 0} ➔ ${inc.comprimento || 0} mm`);
if (mudouPerfil) detalhes.push(`Perfil: ${ext.perfil} ➔ ${inc.perfil}`);
componentesDivergentes.push({
marca_componente: matchMarca,
descricao: inc.descricao || ext.descricao,
perfil: inc.perfil || ext.perfil,
tipo: 'modificado',
quantidadeAntiga: ext.quantidade_por_peca,
quantidadeNova: inc.quantidade_por_peca,
comprimentoAntigo: ext.comprimento,
comprimentoNovo: inc.comprimento,
detalhe: detalhes.join(' | '),
});
}
}
});
// Verificar componentes removidos
existingComps.forEach((ext) => {
const matchMarca = String(ext.marca_componente).trim();
if (!incomingCompsMap.has(matchMarca)) {
componentesDivergentes.push({
marca_componente: matchMarca,
descricao: ext.descricao,
perfil: ext.perfil,
tipo: 'removido',
quantidadeAntiga: ext.quantidade_por_peca,
comprimentoAntigo: ext.comprimento,
detalhe: `Componente Pos. ${matchMarca} removido na nova lista`,
});
}
});
// Analisar Apontamentos Fabris
const apontamentosPeca = apontamentosByPecaId.get(pecaId) || [];
const processosMap = new Map<string, ProcessoApontado>();
apontamentosPeca.forEach((apt) => {
const procNome = apt.processo?.nome || 'Geral';
const procOrdem = apt.processo?.ordem || 99;
const procId = apt.processo_id;
if (!processosMap.has(procId)) {
processosMap.set(procId, {
processoId: procId,
processoNome: procNome,
ordem: procOrdem,
quantidadeProduzida: 0,
});
}
processosMap.get(procId)!.quantidadeProduzida += Number(apt.quantidade_produzida || 0);
});
const processosApontados = Array.from(processosMap.values()).sort(
(a, b) => a.ordem - b.ordem
);
const totalProduzidoMax = processosApontados.reduce(
(max, p) => Math.max(max, p.quantidadeProduzida),
0
);
const ultimoProc =
processosApontados.length > 0
? processosApontados[processosApontados.length - 1].processoNome
: undefined;
const temApontamento = apontamentosPeca.length > 0;
const emRomaneio = pecaIdsEmRomaneio.has(pecaId);
const temDivergencias = camposDivergentes.length > 0 || componentesDivergentes.length > 0;
const isIdentica = !temDivergencias;
// Calcular nível de risco
let nivelRisco: NivelRiscoImportacao = 'nenhum';
let motivoAlerta = '';
let acaoRecomendadaFabril = '';
if (temDivergencias) {
const temAlteracaoCritica =
camposDivergentes.some((c) => c.isCritico) || componentesDivergentes.length > 0;
if (!temApontamento && !emRomaneio) {
nivelRisco = 'baixo';
motivoAlerta = 'Peça ainda não iniciada na fábrica. Atualização cadastral limpa.';
acaoRecomendadaFabril = 'Nenhuma ação necessária no galpão.';
} else {
// Já tem apontamento ou romaneio
if (emRomaneio) {
nivelRisco = 'critico';
motivoAlerta = `ALERTA MÁXIMO: Peça já foi expedida ou carregada em romaneio!`;
acaoRecomendadaFabril =
'Acionar PCP e Engenharia de Obra imediatamente para conferência do lote expedido.';
} else if (temAlteracaoCritica) {
nivelRisco = 'critico';
const etapaStr = ultimoProc ? `no setor de ${ultimoProc.toUpperCase()}` : 'na produção';
motivoAlerta = `Peça em andamento ${etapaStr} (${totalProduzidoMax} un produzidas) com alterações físicas/dimensionais.`;
const compReduzido = camposDivergentes.find(
(c) => c.campo === 'comprimento' && Number(c.valorNovo) < Number(c.valorAntigo)
);
const compAumentado = camposDivergentes.find(
(c) => c.campo === 'comprimento' && Number(c.valorNovo) > Number(c.valorAntigo)
);
const qtdReduzida = camposDivergentes.find(
(c) => c.campo === 'quantidade' && Number(c.valorNovo) < Number(c.valorAntigo)
);
if (compReduzido) {
acaoRecomendadaFabril = `Retornar peça para corte/serra: comprimento deve ser reduzido em ${Math.abs(
Number(compReduzido.valorAntigo) - Number(compReduzido.valorNovo)
)} mm.`;
} else if (compAumentado) {
acaoRecomendadaFabril = `ATENÇÃO: Peça cresceu ${
Number(compAumentado.valorNovo) - Number(compAumentado.valorAntigo)
} mm. Descartar/reaproveitar corte antigo e cortar nova barra.`;
} else if (qtdReduzida) {
acaoRecomendadaFabril = `Redução de lote: verificar se excedente já fabricado deve ser estornado ou enviado para estoque de peças prontas.`;
} else if (componentesDivergentes.length > 0) {
acaoRecomendadaFabril = `Interromper solda/pintura: verificar adição/remoção de componentes na montagem antes de avançar.`;
} else {
acaoRecomendadaFabril = `Inspecionar peça na bancada de trabalho perante os novos dados técnicos.`;
}
} else {
nivelRisco = 'medio';
motivoAlerta = 'Alteração apenas cadastral/superficial em peça com apontamento.';
acaoRecomendadaFabril = 'Verificar etiquetas e conferir especificação na pintura.';
}
}
}
// Configura ações padrão
itensResultado.push({
key,
of_number: pecaImportada.of_number,
etapa_fase: pecaImportada.etapa_fase,
marca: pecaImportada.marca,
descricao: pecaImportada.descricao || pecaExistente.descricao || '',
isNova: false,
isIdentica,
temDivergencias,
camposDivergentes,
componentesDivergentes,
dadosImportados: pecaImportada,
dadosExistentes: pecaExistente,
pecaIdExistente: pecaId,
apontamentoResumo: {
totalProduzido: totalProduzidoMax,
processos: processosApontados,
ultimoProcessoNome: ultimoProc,
temApontamento,
emRomaneio,
},
nivelRisco,
sobrepor: temDivergencias, // Sobrepor marcado por padrão se tem divergência
atencaoEspecial: nivelRisco === 'critico', // Atenção especial marcada automaticamente se crítico
motivoAlerta,
acaoRecomendadaFabril,
});
}
// Ordenar: críticas primeiro, depois com divergência, depois novas, depois idênticas
itensResultado.sort((a, b) => {
const pesoRisco = { critico: 4, medio: 3, baixo: 2, nenhum: 1 };
const diffRisco = pesoRisco[b.nivelRisco] - pesoRisco[a.nivelRisco];
if (diffRisco !== 0) return diffRisco;
if (b.temDivergencias !== a.temDivergencias) return b.temDivergencias ? 1 : -1;
return a.marca.localeCompare(b.marca, undefined, { numeric: true });
});
return {
totalImportadas: itensResultado.length,
totalNovas: itensResultado.filter((i) => i.isNova).length,
totalIdenticas: itensResultado.filter((i) => i.isIdentica).length,
totalComDivergencia: itensResultado.filter((i) => i.temDivergencias).length,
totalComApontamento: itensResultado.filter((i) => i.apontamentoResumo.temApontamento).length,
totalCriticas: itensResultado.filter((i) => i.nivelRisco === 'critico').length,
itens: itensResultado,
};
}
+251
View File
@@ -0,0 +1,251 @@
import { supabase } from '@/integrations/supabase/client';
import { ItemAnaliseImportacao } from '@/types/importDiff';
export interface ExecucaoImportacaoParams {
itens: ItemAnaliseImportacao[];
componentesMap: Map<string, any[]>;
userId: string;
userNome?: string;
userEmail?: string;
motivoRevisao?: string;
}
export interface ResultadoExecucaoImportacao {
sucesso: boolean;
totalInseridas: number;
totalAtualizadas: number;
totalIgnoradas: number;
totalComAtencaoEspecial: number;
itensAtencaoEspecial: ItemAnaliseImportacao[];
mensagem: string;
}
/**
* Executa a importação conciliada respeitando as decisões tomadas pelo usuário no modal.
* Preserva 100% dos IDs existentes, garantindo que nenhum apontamento de produção ou romaneio seja afetado.
*/
export async function executarImportacaoConciliada(
params: ExecucaoImportacaoParams
): Promise<ResultadoExecucaoImportacao> {
const {
itens,
componentesMap,
userId,
userNome = 'Operador',
userEmail = '',
motivoRevisao = 'Atualização de projeto via importação de lista de peças',
} = params;
console.log('🚀 Executando importação conciliada para', itens.length, 'itens analisados');
let totalInseridas = 0;
let totalAtualizadas = 0;
let totalIgnoradas = 0;
const itensAtencaoEspecial: ItemAnaliseImportacao[] = [];
try {
// 1. Separar itens por ação
const pecasNovas = itens.filter((i) => i.isNova && i.sobrepor);
const pecasParaAtualizar = itens.filter(
(i) => !i.isNova && i.sobrepor && (i.temDivergencias || i.isIdentica)
);
const pecasIgnoradas = itens.filter((i) => !i.sobrepor);
totalIgnoradas = pecasIgnoradas.length;
// 2. Atualizar peças existentes selecionadas mantendo o UUID
for (const item of pecasParaAtualizar) {
if (!item.pecaIdExistente) continue;
const pecaId = item.pecaIdExistente;
const importado = item.dadosImportados;
const existente = item.dadosExistentes || {};
// Se é idêntica e estava marcada, apenas pula a escrita desnecessária
if (item.isIdentica) {
continue;
}
// Se marcada para atenção especial, memorizar para a lista impressa
if (item.atencaoEspecial) {
itensAtencaoEspecial.push(item);
}
const qtdFinal = Number(importado.quantidade || existente.quantidade || 0);
const pesoUnitFinal = Number(importado.peso_unitario || existente.peso_unitario || 0);
const pesoTotalCalculado = Number((qtdFinal * pesoUnitFinal).toFixed(3)) || Number(importado.peso_total || 0);
const payloadUpdate: any = {
descricao: importado.descricao || existente.descricao || '',
quantidade: qtdFinal,
peso_unitario: pesoUnitFinal,
peso_total: pesoTotalCalculado,
comprimento: importado.comprimento !== undefined ? importado.comprimento : existente.comprimento,
tratamento_superficial: importado.tratamento_superficial || existente.tratamento_superficial || 'pintura',
material: importado.material || existente.material || 'Aço A36',
perfil_principal: importado.perfil_principal || existente.perfil_principal || '',
tem_componentes: importado.tem_componentes !== undefined ? importado.tem_componentes : existente.tem_componentes,
updated_at: new Date().toISOString(),
};
const { error: updateErr } = await supabase
.from('pecas')
.update(payloadUpdate)
.eq('id', pecaId);
if (updateErr) {
console.error(`❌ Erro ao atualizar peça ${item.marca}:`, updateErr);
continue;
}
totalAtualizadas++;
// Atualizar componentes da peça existente
const novosComponentes = componentesMap.get(item.key) || [];
if (novosComponentes.length > 0) {
// Limpar componentes antigos e inserir os novos
await supabase.from('componentes_peca').delete().eq('peca_id', pecaId);
const componentesFormatados = novosComponentes.map((comp) => ({
...comp,
peca_id: pecaId,
user_id: userId,
}));
await supabase.from('componentes_peca').insert(componentesFormatados);
} else if (item.camposDivergentes.some((c) => c.campo === 'tem_componentes' && !c.valorNovo)) {
// Peça virou simples sem componentes: limpar antigos
await supabase.from('componentes_peca').delete().eq('peca_id', pecaId);
}
// Registrar Histórico de Revisão na tabela 'revisoes' se houve divergência
if (item.temDivergencias) {
const camposAlterados = item.camposDivergentes.map((c) => c.campo);
if (item.componentesDivergentes.length > 0) {
camposAlterados.push('componentes');
}
const proxNumeroRevisao = existente.revisao_atual
? `R${(Number(existente.revisao_atual.replace(/\D/g, '')) || 0) + 1}`
: 'R1';
try {
await supabase.from('revisoes' as any).insert({
of_number: item.of_number,
peca_id: pecaId,
numero_revisao: proxNumeroRevisao,
tipo_revisao: item.nivelRisco === 'critico' ? 'critica' : 'simples',
descricao_motivo: motivoRevisao,
observacoes: item.motivoAlerta || item.acaoRecomendadaFabril || null,
usuario_id: userId || null,
usuario_nome: userNome,
usuario_email: userEmail,
dados_anteriores: existente,
dados_novos: importado,
campos_alterados: camposAlterados,
afeta_apontamentos: item.apontamentoResumo.temApontamento,
impacto_processos: {
divergencias: item.camposDivergentes.map((c) => ({
campo: c.label,
antes: c.valorAntigo,
depois: c.valorNovo,
})),
componentes_alterados: item.componentesDivergentes,
ultimo_processo_afetado: item.apontamentoResumo.ultimoProcessoNome || null,
total_produzido: item.apontamentoResumo.totalProduzido,
atencao_especial_marcada: item.atencaoEspecial,
},
data_revisao: new Date().toISOString(),
});
} catch (revError) {
console.warn('Aviso: Não foi possível gravar histórico em revisoes:', revError);
}
}
}
// 3. Inserir peças novas
if (pecasNovas.length > 0) {
const pecasParaInsert = pecasNovas.map((p) => {
const imp = p.dadosImportados;
const qtd = Number(imp.quantidade) || 0;
const pesoU = Number(imp.peso_unitario) || 0;
const pesoT = Number(imp.peso_total) || Number((qtd * pesoU).toFixed(3));
return {
of_number: imp.of_number,
etapa_fase: imp.etapa_fase,
marca: imp.marca,
descricao: imp.descricao || imp.perfil_principal || '',
quantidade: qtd,
peso_unitario: pesoU,
peso_total: pesoT,
comprimento: imp.comprimento || null,
tratamento_superficial: imp.tratamento_superficial || 'pintura',
material: imp.material || 'Aço A36',
perfil_principal: imp.perfil_principal || '',
tem_componentes: Boolean(imp.tem_componentes),
user_id: userId,
prioridade: 'P4',
};
});
const { data: pecasInseridas, error: insertError } = await supabase
.from('pecas')
.insert(pecasParaInsert)
.select('id, of_number, etapa_fase, marca');
if (insertError) {
console.error('❌ Erro ao inserir peças novas:', insertError);
throw insertError;
}
if (pecasInseridas) {
totalInseridas = pecasInseridas.length;
// Inserir componentes das peças novas
const componentesParaInsert: any[] = [];
pecasInseridas.forEach((p) => {
const key = `${p.of_number || ''}-${p.etapa_fase || ''}-${p.marca || ''}`;
const comps = componentesMap.get(key) || [];
comps.forEach((c) => {
componentesParaInsert.push({
...c,
peca_id: p.id,
user_id: userId,
});
});
});
if (componentesParaInsert.length > 0) {
await supabase.from('componentes_peca').insert(componentesParaInsert);
}
}
}
let mensagem = `✅ Importação concluída!`;
if (totalInseridas > 0) mensagem += ` ${totalInseridas} nova(s) peça(s) inserida(s).`;
if (totalAtualizadas > 0) mensagem += ` ${totalAtualizadas} peça(s) sobreposta(s)/atualizada(s).`;
if (totalIgnoradas > 0) mensagem += ` ${totalIgnoradas} peça(s) mantida(s) no cadastro original.`;
return {
sucesso: true,
totalInseridas,
totalAtualizadas,
totalIgnoradas,
totalComAtencaoEspecial: itensAtencaoEspecial.length,
itensAtencaoEspecial,
mensagem,
};
} catch (error: any) {
console.error('❌ Falha na execução da importação conciliada:', error);
return {
sucesso: false,
totalInseridas: 0,
totalAtualizadas: 0,
totalIgnoradas: 0,
totalComAtencaoEspecial: 0,
itensAtencaoEspecial: [],
mensagem: `Erro na importação: ${error?.message || 'Falha de comunicação com o banco'}`,
};
}
}