const express = require('express'); const cors = require('cors'); const path = require('path'); const multer = require('multer'); const sqlite3 = require('sqlite3').verbose(); const { v4: uuidv4 } = require('uuid'); require('dotenv').config(); const app = express(); const PORT = process.env.PORT || 5000; // Middleware app.use(cors()); app.use(express.json()); app.use('/uploads', express.static('uploads')); app.use(express.static(path.join(__dirname, 'client/build'))); // Configuração do multer para upload de imagens const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, 'uploads/'); }, filename: (req, file, cb) => { const uniqueName = `${Date.now()}-${uuidv4()}${path.extname(file.originalname)}`; cb(null, uniqueName); } }); const upload = multer({ storage: storage, fileFilter: (req, file, cb) => { if (file.mimetype.startsWith('image/')) { cb(null, true); } else { cb(new Error('Apenas imagens são permitidas!'), false); } }, limits: { fileSize: 5 * 1024 * 1024 } // 5MB }); // Inicializar banco de dados const db = new sqlite3.Database('./liberi_kids.db', (err) => { if (err) { console.error('Erro ao conectar com o banco de dados:', err.message); } else { console.log('Conectado ao banco de dados SQLite.'); initializeDatabase(); } }); // Função para inicializar as tabelas function initializeDatabase() { // Tabela de produtos db.run(`CREATE TABLE IF NOT EXISTS produtos ( id TEXT PRIMARY KEY, id_produto TEXT, marca TEXT NOT NULL, nome TEXT NOT NULL, estacao TEXT NOT NULL, genero TEXT DEFAULT 'Unissex', fornecedor_id TEXT, valor_compra REAL NOT NULL, valor_revenda REAL NOT NULL, foto_principal_url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (fornecedor_id) REFERENCES fornecedores (id) )`); // Tabela de variações de produtos (tamanho, cor, quantidade) db.run(`CREATE TABLE IF NOT EXISTS produto_variacoes ( id TEXT PRIMARY KEY, produto_id TEXT NOT NULL, tamanho TEXT NOT NULL, cor TEXT NOT NULL, quantidade INTEGER NOT NULL DEFAULT 0, foto_url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (produto_id) REFERENCES produtos (id) ON DELETE CASCADE )`); // Tabela de clientes db.run(`CREATE TABLE IF NOT EXISTS clientes ( id TEXT PRIMARY KEY, nome_completo TEXT NOT NULL, email TEXT, telefone TEXT, endereco TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP )`); // Tabela de fornecedores db.run(`CREATE TABLE IF NOT EXISTS fornecedores ( id TEXT PRIMARY KEY, razao_social TEXT NOT NULL, telefone TEXT, whatsapp TEXT, endereco TEXT, email TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP )`); // Tabela de tipos de despesas db.run(`CREATE TABLE IF NOT EXISTS tipos_despesas ( id TEXT PRIMARY KEY, nome TEXT NOT NULL UNIQUE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )`); // Tabela de despesas db.run(`CREATE TABLE IF NOT EXISTS despesas ( id TEXT PRIMARY KEY, tipo_despesa_id TEXT NOT NULL, fornecedor_id TEXT, data DATE NOT NULL, valor REAL NOT NULL, descricao TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tipo_despesa_id) REFERENCES tipos_despesas (id), FOREIGN KEY (fornecedor_id) REFERENCES fornecedores (id) )`); // Tabela de vendas db.run(`CREATE TABLE IF NOT EXISTS vendas ( id TEXT PRIMARY KEY, cliente_id TEXT, tipo_pagamento TEXT NOT NULL, -- 'vista' ou 'parcelado' valor_total REAL NOT NULL, desconto REAL DEFAULT 0, parcelas INTEGER DEFAULT 1, valor_parcela REAL, data_venda DATE NOT NULL, status TEXT DEFAULT 'concluida', -- 'concluida', 'cancelada' observacoes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (cliente_id) REFERENCES clientes (id) )`); // Tabela de itens da venda db.run(`CREATE TABLE IF NOT EXISTS venda_itens ( id TEXT PRIMARY KEY, venda_id TEXT NOT NULL, produto_variacao_id TEXT NOT NULL, quantidade INTEGER NOT NULL, valor_unitario REAL NOT NULL, valor_total REAL NOT NULL, FOREIGN KEY (venda_id) REFERENCES vendas (id) ON DELETE CASCADE, FOREIGN KEY (produto_variacao_id) REFERENCES produto_variacoes (id) )`); console.log('Tabelas do banco de dados inicializadas.'); } // Rotas da API // === PRODUTOS === app.get('/api/produtos', (req, res) => { const query = ` SELECT p.*, f.razao_social as fornecedor_nome, COUNT(pv.id) as total_variacoes, SUM(pv.quantidade) as estoque_total FROM produtos p LEFT JOIN fornecedores f ON p.fornecedor_id = f.id LEFT JOIN produto_variacoes pv ON p.id = pv.produto_id GROUP BY p.id ORDER BY p.created_at DESC `; db.all(query, [], (err, rows) => { if (err) { res.status(500).json({ error: err.message }); return; } res.json(rows); }); }); app.post('/api/produtos', upload.any(), (req, res) => { console.log('Recebendo requisição para criar produto:', req.body); console.log('Arquivos recebidos:', req.files ? req.files.length : 0); const { id_produto, marca, nome, estacao, genero, fornecedor_id, valor_compra, valor_revenda, variacoes_data } = req.body; const produtoId = uuidv4(); // Validações básicas if (!marca || !nome || !estacao || !valor_compra || !valor_revenda) { return res.status(400).json({ error: 'Campos obrigatórios não preenchidos' }); } // Parse das variações let variacoes = []; try { if (variacoes_data) { variacoes = JSON.parse(variacoes_data); } } catch (error) { console.error('Erro ao fazer parse das variações:', error); return res.status(400).json({ error: 'Dados de variações inválidos' }); } if (variacoes.length === 0) { return res.status(400).json({ error: 'Pelo menos uma variação é obrigatória' }); } // Iniciar transação db.serialize(() => { db.run('BEGIN TRANSACTION'); // Inserir produto const produtoQuery = `INSERT INTO produtos (id, id_produto, marca, nome, estacao, genero, fornecedor_id, valor_compra, valor_revenda) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`; const fornecedorIdFinal = fornecedor_id && fornecedor_id !== '' ? fornecedor_id : null; db.run(produtoQuery, [produtoId, id_produto, marca, nome, estacao, genero || 'Unissex', fornecedorIdFinal, valor_compra, valor_revenda], function(err) { if (err) { console.error('Erro ao inserir produto:', err); db.run('ROLLBACK'); return res.status(500).json({ error: err.message }); } console.log('Produto inserido com sucesso, processando variações...'); // Processar variações e fotos let variacoesProcessadas = 0; const totalVariacoes = variacoes.length; if (totalVariacoes === 0) { db.run('COMMIT'); return res.json({ id: produtoId, message: 'Produto criado com sucesso!' }); } variacoes.forEach((variacao, varIndex) => { const variacaoId = uuidv4(); // Inserir variação const variacaoQuery = `INSERT INTO produto_variacoes (id, produto_id, tamanho, cor, quantidade) VALUES (?, ?, ?, ?, ?)`; db.run(variacaoQuery, [variacaoId, produtoId, variacao.tamanho, variacao.cor, variacao.quantidade], function(err) { if (err) { console.error('Erro ao inserir variação:', err); db.run('ROLLBACK'); return res.status(500).json({ error: err.message }); } // Processar fotos desta variação const fotosVariacao = req.files ? req.files.filter(file => file.fieldname.startsWith(`variacao_${varIndex}_foto_`) ) : []; if (fotosVariacao.length > 0) { // Usar a primeira foto como foto_url da variação const primeiraFoto = fotosVariacao[0]; const fotoUrl = `/uploads/${primeiraFoto.filename}`; // Atualizar variação com a primeira foto db.run('UPDATE produto_variacoes SET foto_url = ? WHERE id = ?', [fotoUrl, variacaoId], function(err) { if (err) { console.error('Erro ao atualizar foto da variação:', err); } }); } variacoesProcessadas++; console.log(`Variação processada: ${variacoesProcessadas}/${totalVariacoes}`); if (variacoesProcessadas === totalVariacoes) { db.run('COMMIT'); console.log('Produto criado com sucesso!'); res.json({ id: produtoId, message: 'Produto criado com sucesso!' }); } }); }); }); }); }); app.put('/api/produtos/:id', upload.single('foto_principal'), (req, res) => { const { id } = req.params; const { id_produto, marca, nome, estacao, genero, fornecedor_id, valor_compra, valor_revenda } = req.body; let query, params; if (req.file) { const foto_principal_url = `/uploads/${req.file.filename}`; query = `UPDATE produtos SET id_produto = ?, marca = ?, nome = ?, estacao = ?, genero = ?, fornecedor_id = ?, valor_compra = ?, valor_revenda = ?, foto_principal_url = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; params = [id_produto, marca, nome, estacao, genero || 'Unissex', fornecedor_id, valor_compra, valor_revenda, foto_principal_url, id]; } else { query = `UPDATE produtos SET id_produto = ?, marca = ?, nome = ?, estacao = ?, genero = ?, fornecedor_id = ?, valor_compra = ?, valor_revenda = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; params = [id_produto, marca, nome, estacao, genero || 'Unissex', fornecedor_id, valor_compra, valor_revenda, id]; } db.run(query, params, function(err) { if (err) { res.status(500).json({ error: err.message }); return; } res.json({ message: 'Produto atualizado com sucesso!' }); }); }); // === VARIAÇÕES DE PRODUTOS === app.get('/api/produtos/:id/variacoes', (req, res) => { const { id } = req.params; db.all('SELECT * FROM produto_variacoes WHERE produto_id = ? ORDER BY tamanho, cor', [id], (err, rows) => { if (err) { res.status(500).json({ error: err.message }); return; } res.json(rows); }); }); app.post('/api/produtos/:id/variacoes', upload.single('foto'), (req, res) => { const { id } = req.params; const { tamanho, cor, quantidade } = req.body; const foto_url = req.file ? `/uploads/${req.file.filename}` : null; const variacao_id = uuidv4(); const query = `INSERT INTO produto_variacoes (id, produto_id, tamanho, cor, quantidade, foto_url) VALUES (?, ?, ?, ?, ?, ?)`; db.run(query, [variacao_id, id, tamanho, cor, quantidade, foto_url], function(err) { if (err) { res.status(500).json({ error: err.message }); return; } res.json({ id: variacao_id, message: 'Variação adicionada com sucesso!' }); }); }); // === CLIENTES === app.get('/api/clientes', (req, res) => { db.all('SELECT * FROM clientes ORDER BY nome_completo', [], (err, rows) => { if (err) { res.status(500).json({ error: err.message }); return; } res.json(rows); }); }); app.post('/api/clientes', (req, res) => { const { nome_completo, email, telefone, endereco } = req.body; const id = uuidv4(); const query = `INSERT INTO clientes (id, nome_completo, email, telefone, endereco) VALUES (?, ?, ?, ?, ?)`; db.run(query, [id, nome_completo, email, telefone, endereco], function(err) { if (err) { res.status(500).json({ error: err.message }); return; } res.json({ id, message: 'Cliente cadastrado com sucesso!' }); }); }); // === FORNECEDORES === app.get('/api/fornecedores', (req, res) => { db.all('SELECT * FROM fornecedores ORDER BY razao_social', [], (err, rows) => { if (err) { res.status(500).json({ error: err.message }); return; } res.json(rows); }); }); app.post('/api/fornecedores', (req, res) => { const { razao_social, telefone, whatsapp, endereco, email } = req.body; const id = uuidv4(); const query = `INSERT INTO fornecedores (id, razao_social, telefone, whatsapp, endereco, email) VALUES (?, ?, ?, ?, ?, ?)`; db.run(query, [id, razao_social, telefone, whatsapp, endereco, email], function(err) { if (err) { res.status(500).json({ error: err.message }); return; } res.json({ id, message: 'Fornecedor cadastrado com sucesso!' }); }); }); // === TIPOS DE DESPESAS === app.get('/api/tipos-despesas', (req, res) => { db.all('SELECT * FROM tipos_despesas ORDER BY nome', [], (err, rows) => { if (err) { res.status(500).json({ error: err.message }); return; } res.json(rows); }); }); app.post('/api/tipos-despesas', (req, res) => { const { nome } = req.body; const id = uuidv4(); const query = `INSERT INTO tipos_despesas (id, nome) VALUES (?, ?)`; db.run(query, [id, nome], function(err) { if (err) { res.status(500).json({ error: err.message }); return; } res.json({ id, message: 'Tipo de despesa criado com sucesso!' }); }); }); // === DESPESAS === app.get('/api/despesas', (req, res) => { const query = ` SELECT d.*, td.nome as tipo_nome, f.razao_social as fornecedor_nome FROM despesas d LEFT JOIN tipos_despesas td ON d.tipo_despesa_id = td.id LEFT JOIN fornecedores f ON d.fornecedor_id = f.id ORDER BY d.data DESC `; db.all(query, [], (err, rows) => { if (err) { res.status(500).json({ error: err.message }); return; } res.json(rows); }); }); app.post('/api/despesas', (req, res) => { const { tipo_despesa_id, fornecedor_id, data, valor, descricao } = req.body; const id = uuidv4(); const query = `INSERT INTO despesas (id, tipo_despesa_id, fornecedor_id, data, valor, descricao) VALUES (?, ?, ?, ?, ?, ?)`; db.run(query, [id, tipo_despesa_id, fornecedor_id || null, data, valor, descricao], function(err) { if (err) { res.status(500).json({ error: err.message }); return; } res.json({ id, message: 'Despesa cadastrada com sucesso!' }); }); }); app.put('/api/despesas/:id', (req, res) => { const { id } = req.params; const { tipo_despesa_id, fornecedor_id, data, valor, descricao } = req.body; const query = `UPDATE despesas SET tipo_despesa_id = ?, fornecedor_id = ?, data = ?, valor = ?, descricao = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; db.run(query, [tipo_despesa_id, fornecedor_id || null, data, valor, descricao, id], function(err) { if (err) { res.status(500).json({ error: err.message }); return; } res.json({ message: 'Despesa atualizada com sucesso!' }); }); }); app.delete('/api/despesas/:id', (req, res) => { const { id } = req.params; db.run('DELETE FROM despesas WHERE id = ?', [id], function(err) { if (err) { res.status(500).json({ error: err.message }); return; } res.json({ message: 'Despesa excluída com sucesso!' }); }); }); // === VENDAS === app.get('/api/vendas', (req, res) => { const query = ` SELECT v.*, c.nome_completo as cliente_nome FROM vendas v LEFT JOIN clientes c ON v.cliente_id = c.id ORDER BY v.data_venda DESC `; db.all(query, [], (err, rows) => { if (err) { res.status(500).json({ error: err.message }); return; } res.json(rows); }); }); app.post('/api/vendas', (req, res) => { const { cliente_id, tipo_pagamento, valor_total, desconto, parcelas, data_venda, observacoes, itens } = req.body; const vendaId = uuidv4(); const valor_parcela = tipo_pagamento === 'parcelado' ? (valor_total - desconto) / parcelas : 0; // Iniciar transação db.serialize(() => { db.run('BEGIN TRANSACTION'); // Inserir venda const vendaQuery = `INSERT INTO vendas (id, cliente_id, tipo_pagamento, valor_total, desconto, parcelas, valor_parcela, data_venda, observacoes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`; db.run(vendaQuery, [vendaId, cliente_id || null, tipo_pagamento, valor_total, desconto, parcelas, valor_parcela, data_venda, observacoes], function(err) { if (err) { db.run('ROLLBACK'); res.status(500).json({ error: err.message }); return; } // Inserir itens da venda let itemsProcessed = 0; const totalItems = itens.length; if (totalItems === 0) { db.run('COMMIT'); res.json({ id: vendaId, message: 'Venda registrada com sucesso!' }); return; } itens.forEach((item) => { const itemId = uuidv4(); const itemQuery = `INSERT INTO venda_itens (id, venda_id, produto_variacao_id, quantidade, valor_unitario, valor_total) VALUES (?, ?, ?, ?, ?, ?)`; db.run(itemQuery, [itemId, vendaId, item.produto_variacao_id, item.quantidade, item.valor_unitario, item.valor_total], function(err) { if (err) { db.run('ROLLBACK'); res.status(500).json({ error: err.message }); return; } // Atualizar estoque const updateEstoque = `UPDATE produto_variacoes SET quantidade = quantidade - ? WHERE id = ?`; db.run(updateEstoque, [item.quantidade, item.produto_variacao_id], function(err) { if (err) { db.run('ROLLBACK'); res.status(500).json({ error: err.message }); return; } itemsProcessed++; if (itemsProcessed === totalItems) { db.run('COMMIT'); res.json({ id: vendaId, message: 'Venda registrada com sucesso!' }); } }); }); }); }); }); }); app.delete('/api/vendas/:id', (req, res) => { const { id } = req.params; db.run('DELETE FROM vendas WHERE id = ?', [id], function(err) { if (err) { res.status(500).json({ error: err.message }); return; } res.json({ message: 'Venda excluída com sucesso!' }); }); }); // === TESTE === app.get('/api/test', (req, res) => { res.json({ message: 'API funcionando corretamente!', timestamp: new Date() }); }); // === DASHBOARD === app.get('/api/dashboard', (req, res) => { const queries = { totalProdutos: 'SELECT COUNT(*) as count FROM produtos', totalClientes: 'SELECT COUNT(*) as count FROM clientes', totalFornecedores: 'SELECT COUNT(*) as count FROM fornecedores', vendasMes: `SELECT COUNT(*) as count, SUM(valor_total) as total FROM vendas WHERE strftime('%Y-%m', data_venda) = strftime('%Y-%m', 'now')`, estoqueTotal: 'SELECT SUM(quantidade) as total FROM produto_variacoes', despesasMes: `SELECT SUM(valor) as total FROM despesas WHERE strftime('%Y-%m', data) = strftime('%Y-%m', 'now')` }; const results = {}; let completed = 0; const total = Object.keys(queries).length; Object.entries(queries).forEach(([key, query]) => { db.get(query, [], (err, row) => { if (err) { results[key] = { error: err.message }; } else { results[key] = row; } completed++; if (completed === total) { res.json(results); } }); }); }); // Servir arquivos estáticos do React app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'client/build', 'index.html')); }); // Criar diretório de uploads se não existir const fs = require('fs'); if (!fs.existsSync('uploads')) { fs.mkdirSync('uploads'); } app.listen(PORT, () => { console.log(`Servidor rodando na porta ${PORT}`); }); module.exports = app;