import express from "express"; import cors from "cors"; import axios from "axios"; import "dotenv/config"; import { prisma } from './prismaClient'; // Corrigir o import do prisma import { AuthService } from "../services/AuthService"; import { UsuarioRepository } from "../repositories/UsuarioRepository"; import { ClienteRepository } from "../repositories/ClienteRepository"; import { ProcessoRepository } from "../repositories/ProcessoRepository"; import { OrgaoJulgadorRepository } from "../repositories/OrgaoJulgadorRepository"; import { CustaProcessualRepository } from "../repositories/CustaProcessualRepository"; import { HonorarioRepository } from "../repositories/HonorarioRepository"; import jwt from "jsonwebtoken"; import { authMiddleware } from '../middleware/auth'; import bcrypt from "bcryptjs"; import { TipoAtendimento } from "@prisma/client"; // Adicionar esta importação import tarefasRoutes from './routes/tarefas'; import dashboardRoutes from './routes/dashboard'; import atendimentosRoutes from './routes/atendimentos'; import anexosRoutes from './routes/anexos'; import documentosRouter from './routes/documentos'; import documentosModelosRouter from './routes/documentos-modelos'; import processosAdministrativosRoutes from './routes/processosAdministrativos'; import provasRouter from './routes/provas'; import calculosJudiciaisRoutes from './routes/calculosJudiciais'; import indicesMonetariosRoutes from './routes/indicesMonetarios'; import pensaoAlimenticiaRouter from './routes/pensaoAlimenticia'; // Importar router de pensão alimentícia import financeiroPainelRoutes from './routes/financeiroPainel'; // Importar rotas do financeiro painel import audienciasRoutes from './routes/audiencias'; // Importar rotas de audiências import pdfRoutes from './routes/pdf'; // Importar rotas de PDF import { PDFService } from '../services/pdfService'; // Importar serviço de PDF para warmup OCR const app = express(); const port = process.env.PORT || 5000; const corsOrigin = process.env.CORS_ORIGIN || "http://localhost:3000"; app.use( cors({ origin: corsOrigin, methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"], allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"], exposedHeaders: ["Content-Range", "X-Content-Range"], credentials: true, }), ); // Adicionar headers específicos para CORS app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", corsOrigin); res.header("Access-Control-Allow-Credentials", "true"); next(); }); app.use(express.json({ limit: '20mb' })); app.use(express.urlencoded({ extended: true, limit: '20mb' })); const clienteRepository = new ClienteRepository(); const processoRepository = new ProcessoRepository(); const orgaoJulgadorRepository = new OrgaoJulgadorRepository(); const custaProcessualRepository = new CustaProcessualRepository(); const honorarioRepository = new HonorarioRepository(); // Atualizar a função isValidUUID para ser mais robusta const isValidUUID = (uuid: string | null | undefined): boolean => { if (!uuid) return false; const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return uuidRegex.test(uuid); }; // Middleware para extrair usuário do token // Remover este middleware pois já temos o auth.ts // const extractUser = (req: any, res: any, next: any) => { ... } // app.use(extractUser); // Rota de teste app.get("/api/health", (req, res) => { res.json({ status: "ok", timestamp: new Date() }); }); app.post("/api/auth/login", async (req, res) => { try { console.log("Server - Request de login:", req.body); // Debug const { email, senha } = req.body; const authService = new AuthService(new UsuarioRepository()); const result = await authService.login(email, senha); console.log("Server - Login bem-sucedido:", { userId: result.user.id }); // Debug res.json(result); } catch (error) { console.error("Server - Erro no login:", error); // Debug const message = error instanceof Error ? error.message : "Erro ao fazer login"; res.status(401).json({ error: message }); } }); app.get("/auth/verify", async (req, res) => { try { const token = req.headers.authorization?.replace("Bearer ", ""); if (!token) { return res.status(401).json({ error: "Token não fornecido" }); } const decoded = jwt.verify( token, process.env.JWT_SECRET || "default-secret", ); if (!decoded) { return res.status(401).json({ error: "Token inválido" }); } res.json({ valid: true }); } catch (error) { res.status(401).json({ error: "Token inválido" }); } }); // Redirecionar para página pública de verificação (atualizar esta rota) app.get('/verificar/:hash', (req, res) => { // Redirecionar para a verificação pública (sem login) res.redirect(`${process.env.FRONTEND_URL || 'http://localhost:3000'}/public/verificar/${req.params.hash}`); }); // Rota para buscar perfil do usuário logado (ajuste do caminho) app.get("/api/auth/me", async (req, res) => { // Adicionado prefixo /api try { const token = req.headers.authorization?.replace("Bearer ", ""); if (!token) { console.log("Token não fornecido"); return res.status(401).json({ error: "Token não fornecido" }); } const decoded = jwt.verify( token, process.env.JWT_SECRET || "default-secret", ) as any; console.log("Token decodificado:", decoded); const user = await prisma.usuario.findUnique({ // usar o prisma importado where: { id: decoded.id }, select: { id: true, nome: true, email: true, perfil: true, ativo: true, }, }); if (!user) { console.log("Usuário não encontrado:", decoded.id); return res.status(404).json({ error: "Usuário não encontrado" }); } if (!user.ativo) { console.log("Usuário inativo:", decoded.id); return res.status(401).json({ error: "Usuário inativo" }); } console.log("Usuário autenticado:", user); res.json(user); } catch (error) { console.error("Erro na autenticação:", error); res.status(401).json({ error: "Token inválido" }); } }); // Rotas de Clientes app.get("/api/clientes", async (req, res) => { try { const clientes = await clienteRepository.findAll(); console.log("Clientes encontrados:", clientes.length); res.json(clientes); } catch (error) { console.error("Erro ao buscar clientes:", error); res.status(500).json({ error: "Erro ao buscar clientes", details: error instanceof Error ? error.message : "Erro desconhecido", }); } }); app.post("/api/clientes", async (req, res) => { try { const requiredFields = [ "nome", "email", "telefone", "cpfCnpj", "endereco", "cep", "numero", "bairro", "cidade", "estado", ]; const missingFields = requiredFields.filter((field) => !req.body[field]); if (missingFields.length > 0) { return res.status(400).json({ error: `Campos obrigatórios faltando: ${missingFields.join(", ")}`, missingFields, }); } const cleanData = { ...req.body, renda: req.body.renda ? Number(req.body.renda) : null, declaraIR: Boolean(req.body.declaraIR), contribuiPrevidencia: Boolean(req.body.contribuiPrevidencia), }; console.log("Dados recebidos:", cleanData); const cliente = await clienteRepository.create(cleanData); res.status(201).json(cliente); } catch (error) { console.error("Erro ao criar cliente:", error); res.status(400).json({ error: error instanceof Error ? error.message : "Erro ao criar cliente", details: error, }); } }); app.put("/api/clientes/:id", async (req, res) => { try { const { id } = req.params; console.log("Atualizando cliente:", id, req.body); const cleanData = { ...req.body, renda: req.body.renda ? Number(req.body.renda) : undefined, declaraIR: req.body.declaraIR === undefined ? undefined : Boolean(req.body.declaraIR), contribuiPrevidencia: req.body.contribuiPrevidencia === undefined ? undefined : Boolean(req.body.contribuiPrevidencia), }; const cliente = await clienteRepository.update(id, cleanData); res.json(cliente); } catch (error) { console.error("Erro ao atualizar cliente:", error); res.status(400).json({ error: error instanceof Error ? error.message : "Erro ao atualizar cliente", details: error, }); } }); app.delete("/api/clientes/:id", async (req, res) => { try { const { id } = req.params; await clienteRepository.delete(id); res.status(204).send(); } catch (error) { console.error("Erro ao deletar cliente:", error); res.status(400).json({ error: "Erro ao deletar cliente" }); } }); // IMPORTANTE: Mover esta rota ANTES das rotas com parâmetros app.get("/api/processos/orgaos", async (req, res) => { try { const orgaos = await prisma.processo.findMany({ select: { orgaoJulgador: true, }, where: { orgaoJulgador: { not: null, not: "", // Garante que não está vazio também }, }, distinct: ["orgaoJulgador"], }); const orgaosUnicos = orgaos .map((o) => o.orgaoJulgador) .filter(Boolean) // Remove valores null/undefined .filter((org): org is string => org !== "") // Remove strings vazias e satisfaz o TypeScript .sort(); res.json(orgaosUnicos); } catch (error) { console.error("Erro ao buscar órgãos:", error); res.status(500).json({ error: "Erro ao buscar órgãos" }); } }); // Adicionar este endpoint antes das outras rotas de processos app.patch('/api/processos/:id/resultado', async (req, res) => { try { const { id } = req.params; const { resultado } = req.body; const processo = await prisma.processo.update({ where: { id }, data: { resultado, dataAtualizacao: new Date() } }); res.json(processo); } catch (error) { console.error('Erro ao atualizar resultado:', error); res.status(500).json({ error: 'Erro ao atualizar resultado do processo' }); } }); // Rotas de Processos app.get("/api/processos", async (req, res) => { try { const processos = await processoRepository.findAll(); res.json(processos); } catch (error) { console.error("Erro ao buscar processos:", error); res.status(500).json({ error: "Erro ao buscar processos" }); } }); app.post("/api/processos", async (req, res) => { try { const processo = await processoRepository.create(req.body); res.status(201).json(processo); } catch (error) { console.error("Erro ao criar processo:", error); res.status(400).json({ error: "Erro ao criar processo" }); } }); app.get("/api/processos/:id", async (req, res) => { try { const processo = await prisma.processo.findUnique({ where: { id: req.params.id }, include: { partes: true, diligencias: true, audiencias: true, documentos: true, movimentacoes: true, honorarios: { include: { parcelas: true, }, }, custas: true, }, }); if (!processo) { return res.status(404).json({ error: "Processo não encontrado" }); } res.json(processo); } catch (error) { console.error("Erro ao buscar processo:", error); res.status(500).json({ error: "Erro ao buscar processo" }); } }); app.put("/api/processos/:id", async (req, res) => { try { const processo = await processoRepository.update(req.params.id, req.body); res.json(processo); } catch (error) { console.error("Erro ao atualizar processo:", error); res.status(400).json({ error: "Erro ao atualizar processo" }); } }); app.delete("/api/processos/:id", async (req, res) => { try { await processoRepository.delete(req.params.id); res.status(204).send(); } catch (error) { console.error("Erro ao deletar processo:", error); res.status(400).json({ error: "Erro ao deletar processo" }); } }); // Rotas para Partes do Processo app.post("/api/processos/:id/partes", async (req, res) => { try { const { id } = req.params; const { tipo, nome, cpfCnpj, observacoes, clienteId, principal } = req.body; const parte = await prisma.parteProcesso.create({ data: { tipo, nome, cpfCnpj, observacoes, principal: principal || false, processo: { connect: { id }, }, ...(clienteId && { cliente: { connect: { id: clienteId }, }, }), }, }); res.status(201).json(parte); } catch (error) { console.error("Erro detalhado ao criar parte:", error); res .status(400) .json({ error: "Erro ao criar parte do processo", details: error }); } }); app.put("/api/processos/:processoId/partes/:id", async (req, res) => { try { const { id } = req.params; const parte = await prisma.parteProcesso.update({ where: { id }, data: req.body, }); res.json(parte); } catch (error) { console.error("Erro ao atualizar parte:", error); res.status(400).json({ error: "Erro ao atualizar parte do processo" }); } }); app.delete("/api/processos/:processoId/partes/:id", async (req, res) => { try { const { id } = req.params; await prisma.parteProcesso.delete({ where: { id }, }); res.status(204).send(); } catch (error) { console.error("Erro ao deletar parte:", error); res.status(400).json({ error: "Erro ao deletar parte do processo" }); } }); // Rotas para Audiências do Processo app.post("/api/processos/:id/audiencias", async (req, res) => { try { const { id } = req.params; const audiencia = await prisma.audiencia.create({ data: { ...req.body, data: new Date(req.body.data), processo: { connect: { id }, }, }, }); res.status(201).json(audiencia); } catch (error) { console.error("Erro ao criar audiência:", error); res.status(400).json({ error: "Erro ao criar audiência" }); } }); app.put("/api/processos/:processoId/audiencias/:id", async (req, res) => { try { const { id } = req.params; const audiencia = await prisma.audiencia.update({ where: { id }, data: { ...req.body, data: new Date(req.body.data), }, }); res.json(audiencia); } catch (error) { console.error("Erro ao atualizar audiência:", error); res.status(400).json({ error: "Erro ao atualizar audiência" }); } }); app.delete("/api/processos/:processoId/audiencias/:id", async (req, res) => { try { const { id } = req.params; await prisma.audiencia.delete({ where: { id }, }); res.status(204).send(); } catch (error) { console.error("Erro ao deletar audiência:", error); res.status(400).json({ error: "Erro ao deletar audiência" }); } }); // Rota para buscar órgãos julgadores app.get("/api/orgaos-julgadores", async (req, res) => { try { const orgaos = await orgaoJulgadorRepository.findAll(); res.json(orgaos); } catch (error) { console.error("Erro ao buscar órgãos julgadores:", error); res.status(500).json({ error: "Erro ao buscar órgãos julgadores" }); } }); // Rota para criar órgão julgador app.post("/api/orgaos-julgadores", async (req, res) => { try { const { nome } = req.body; // Verificar se já existe const existente = await prisma.orgaoJulgador.findUnique({ where: { nome }, }); if (existente) { return res.json(existente); } // Criar novo const orgao = await prisma.orgaoJulgador.create({ data: { nome }, }); res.status(201).json(orgao); } catch (error) { console.error("Erro ao criar órgão julgador:", error); res.status(400).json({ error: "Erro ao criar órgão julgador" }); } }); // Custas Processuais app.get("/api/processos/:id/custas", async (req, res) => { try { const custas = await custaProcessualRepository.findByProcesso( req.params.id, ); res.json(custas); } catch (error) { res.status(500).json({ error: "Erro ao buscar custas processuais" }); } }); app.post("/api/processos/:id/custas", async (req, res) => { try { console.log("Dados recebidos para criar custa:", req.body); const custa = await custaProcessualRepository.create({ ...req.body, processoId: req.params.id, }); res.json(custa); } catch (error) { console.error("Erro ao criar custa processual:", error); res.status(500).json({ error: "Erro ao criar custa processual", details: error instanceof Error ? error.message : "Erro desconhecido", }); } }); app.put("/api/custas/:id", async (req, res) => { try { const custa = await custaProcessualRepository.update( req.params.id, req.body, ); res.json(custa); } catch (error) { res.status(500).json({ error: "Erro ao atualizar custa processual" }); } }); app.delete("/api/custas/:id", async (req, res) => { try { await custaProcessualRepository.delete(req.params.id); res.json({ message: "Custa processual excluída com sucesso" }); } catch (error) { res.status(500).json({ error: "Erro ao excluir custa processual" }); } }); // Honorários app.get("/api/processos/:id/honorarios", async (req, res) => { try { const honorarios = await honorarioRepository.findByProcesso(req.params.id); res.json(honorarios); } catch (error) { res.status(500).json({ error: "Erro ao buscar honorários" }); } }); app.post("/api/processos/:id/honorarios", async (req, res) => { try { console.log("Dados recebidos para criar honorário:", req.body); const honorario = await honorarioRepository.create({ ...req.body, processoId: req.params.id, }); res.json(honorario); } catch (error) { console.error("Erro ao criar honorário:", error); res.status(500).json({ error: "Erro ao criar honorário", details: error instanceof Error ? error.message : "Erro desconhecido", }); } }); app.put("/api/honorarios/:id", async (req, res) => { try { const honorario = await honorarioRepository.update(req.params.id, req.body); res.json(honorario); } catch (error) { res.status(500).json({ error: "Erro ao atualizar honorário" }); } }); app.delete("/api/honorarios/:id", async (req, res) => { try { await honorarioRepository.delete(req.params.id); res.json({ message: "Honorário excluído com sucesso" }); } catch (error) { res.status(500).json({ error: "Erro ao excluir honorário" }); } }); // Mover esta rota ANTES das rotas com parâmetros genéricos app.put("/api/honorarios/parcelas/:id", async (req, res) => { try { console.log("Atualizando parcela:", req.params.id, req.body); const result = await honorarioRepository.updateParcela(req.params.id, { status: req.body.status, valorPago: req.body.valorPago ? Number(req.body.valorPago) : null, dataPagamento: req.body.dataPagamento ? new Date(req.body.dataPagamento) : null, comprovante: req.body.comprovante, observacoes: req.body.observacoes, }); res.json(result); } catch (error) { console.error("Erro ao atualizar parcela:", error); res.status(500).json({ error: "Erro ao atualizar parcela" }); } }); // Adicionar rota para proxy do DataJud app.post("/api/datajud/consulta", async (req, res) => { try { const { numeroProcesso, tribunal } = req.body; const API_KEY = process.env.DATAJUD_API_KEY; const tribunalLowerCase = tribunal.toLowerCase().replace("_", ""); console.log("Consultando DataJud:", { numeroProcesso, tribunal: tribunalLowerCase, url: `https://api-publica.datajud.cnj.jus.br/api_publica_${tribunalLowerCase}/_search`, }); const response = await axios.post( `https://api-publica.datajud.cnj.jus.br/api_publica_${tribunalLowerCase}/_search`, { query: { match: { numeroProcesso, }, }, }, { headers: { Authorization: `APIKey ${API_KEY}`, "Content-Type": "application/json", }, }, ); res.json(response.data); } catch (error: any) { console.error( "Erro na consulta ao DataJud:", error.response?.data || error.message, ); res.status(500).json({ error: "Erro ao consultar DataJud", details: error.response?.data || error.message, }); } }); // Rotas de Usuários app.get("/api/usuarios", async (req, res) => { try { const usuarios = await prisma.usuario.findMany({ select: { id: true, nome: true, email: true, perfil: true, cpf: true, rg: true, oab: true, oabUF: true, telefone: true, endereco: true, numero: true, complemento: true, bairro: true, cidade: true, estado: true, cep: true, ativo: true, dataCriacao: true, }, }); res.json(usuarios); } catch (error) { res.status(500).json({ error: "Erro ao buscar usuários" }); } }); app.post("/api/usuarios", async (req, res) => { try { const dados = { ...req.body, senha: await bcrypt.hash(req.body.senha, 10), }; const usuario = await prisma.usuario.create({ data: dados, select: { id: true, nome: true, email: true, perfil: true, cpf: true, rg: true, oab: true, oabUF: true, telefone: true, endereco: true, numero: true, complemento: true, bairro: true, cidade: true, estado: true, cep: true, ativo: true, dataCriacao: true, }, }); res.status(201).json(usuario); } catch (error) { res.status(400).json({ error: "Erro ao criar usuário" }); } }); // Adicionar middleware para verificar campos protegidos const protectFields = (req, res, next) => { const protectedFields = ["perfil", "cpf"]; protectedFields.forEach((field) => { delete req.body[field]; }); next(); }; // Atualizar rota de update de usuário app.put("/api/usuarios/:id", protectFields, async (req, res) => { try { const { id } = req.params; const token = req.headers.authorization?.replace("Bearer ", ""); const decoded = jwt.verify(token, process.env.JWT_SECRET) as any; // Verificar se o usuário está atualizando seu próprio perfil if (id !== decoded.id) { return res.status(403).json({ error: "Não autorizado" }); } const dados = { ...req.body }; if (dados.senha) { dados.senha = await bcrypt.hash(dados.senha, 10); } else { delete dados.senha; } const usuario = await prisma.usuario.update({ where: { id }, data: dados, select: { id: true, nome: true, email: true, perfil: true, cpf: true, rg: true, oab: true, oabUF: true, telefone: true, endereco: true, numero: true, complemento: true, bairro: true, cidade: true, estado: true, cep: true, ativo: true, dataCriacao: true, }, }); res.json(usuario); } catch (error) { console.error("Erro ao atualizar usuário:", error); res.status(400).json({ error: "Erro ao atualizar usuário" }); } }); // Nova rota DELETE para remover usuário app.delete("/api/usuarios/:id", async (req, res) => { try { const { id } = req.params; await prisma.usuario.delete({ where: { id } }); res.status(204).send(); } catch (error) { console.error("Erro ao excluir usuário:", error); res.status(400).json({ error: "Erro ao excluir usuário" }); } }); // Rota para buscar usuário específico app.get("/api/usuarios/:id", async (req, res) => { try { const { id } = req.params; const token = req.headers.authorization?.replace("Bearer ", ""); const decoded = jwt.verify( token, process.env.JWT_SECRET || "default-secret", ) as any; // Verificar se o usuário está buscando seu próprio perfil ou é admin const requestingUser = await prisma.usuario.findUnique({ where: { id: decoded.id }, select: { perfil: true }, }); if (id !== decoded.id && requestingUser?.perfil !== "ADVOGADO_ADMIN") { return res.status(403).json({ error: "Não autorizado" }); } const usuario = await prisma.usuario.findUnique({ where: { id }, select: { id: true, nome: true, email: true, perfil: true, cpf: true, rg: true, oab: true, oabUF: true, telefone: true, endereco: true, numero: true, complemento: true, bairro: true, cidade: true, estado: true, cep: true, ativo: true, dataCriacao: true, }, }); if (!usuario) { return res.status(404).json({ error: "Usuário não encontrado" }); } res.json(usuario); } catch (error) { console.error("Erro ao buscar usuário:", error); res.status(500).json({ error: "Erro ao buscar usuário" }); } }); // Adicionar rota para alternar status do usuário app.patch("/api/usuarios/:id/status", async (req, res) => { try { const { id } = req.params; const { ativo } = req.body; // Verificar se o usuário que faz a requisição é admin const token = req.headers.authorization?.replace("Bearer ", ""); const decoded = jwt.verify(token, process.env.JWT_SECRET) as any; const requestingUser = await prisma.usuario.findUnique({ where: { id: decoded.id }, select: { perfil: true }, }); if (requestingUser?.perfil !== "ADVOGADO_ADMIN") { return res.status(403).json({ error: "Não autorizado" }); } const usuario = await prisma.usuario.update({ where: { id }, data: { ativo }, select: { id: true, nome: true, email: true, perfil: true, cpf: true, rg: true, oab: true, oabUF: true, telefone: true, endereco: true, numero: true, complemento: true, bairro: true, cidade: true, estado: true, cep: true, ativo: true, }, }); res.json(usuario); } catch (error) { console.error("Erro ao atualizar status do usuário:", error); res.status(400).json({ error: "Erro ao atualizar status do usuário" }); } }); // Rotas de Atendimentos app.get("/api/atendimentos", async (req, res) => { try { const atendimentos = await prisma.atendimento.findMany({ include: { cliente: true, processo: true, usuario: { select: { id: true, nome: true, }, }, anexos: true, }, orderBy: { dataAtendimento: 'desc', }, }); res.json(atendimentos); } catch (error) { console.error("Erro ao buscar atendimentos:", error); res.status(500).json({ error: "Erro ao buscar atendimentos" }); } }); app.post("/api/atendimentos", authMiddleware, async (req, res) => { try { const { clienteId, processoId, tipo, titulo, conteudo, agendamentoId } = req.body; if (!req.user?.id) { return res.status(401).json({ error: "Usuário não autenticado" }); } if (!clienteId) { return res.status(400).json({ error: "ClienteId é obrigatório" }); } // Verificar se o cliente existe const clienteExiste = await prisma.cliente.findUnique({ where: { id: clienteId } }); if (!clienteExiste) { return res.status(400).json({ error: "Cliente não encontrado" }); } // Gerar protocolo const ano = new Date().getFullYear(); const ultimoProtocolo = await prisma.atendimento.findFirst({ where: { protocolo: { startsWith: `${ano}/`, }, }, orderBy: { protocolo: 'desc', }, }); const numeroSequencial = ultimoProtocolo ? parseInt(ultimoProtocolo.protocolo.split('/')[1]) + 1 : 1; const protocolo = `${ano}/${numeroSequencial.toString().padStart(6, '0')}`; // Criar atendimento usando transaction const atendimento = await prisma.$transaction(async (tx) => { // Criar o atendimento primeiro const atendimento = await tx.atendimento.create({ data: { protocolo, titulo, tipo: tipo as TipoAtendimento, conteudo, cliente: { connect: { id: clienteId } }, usuario: { connect: { id: req.user!.id } }, ...(processoId && { processo: { connect: { id: processoId } } }), // Adicionar vírgula aqui status: 'ATIVO', dataAtualizacao: new Date() }, include: { cliente: true, processo: true, usuario: { select: { id: true, nome: true, }, }, }, }); // Se houver agendamentoId, atualizar o agendamento if (agendamentoId) { await tx.agendamentoAtendimento.update({ where: { id: agendamentoId }, data: { status: 'REALIZADO', atendimento: { connect: { id: atendimento.id } }, }, }); } return atendimento; }); res.status(201).json(atendimento); } catch (error) { console.error("Erro ao criar atendimento:", error); res.status(400).json({ error: "Erro ao criar atendimento", details: error instanceof Error ? error.message : "Erro desconhecido" }); } }); // Adicionar nova rota para buscar atendimentos iniciais de um cliente app.get("/api/atendimentos/iniciais/:clienteId", async (req, res) => { try { const { clienteId } = req.params; const atendimentosIniciais = await prisma.atendimento.findMany({ where: { clienteId, tipo: TipoAtendimento.INICIAL, }, orderBy: { dataAtendimento: 'desc', }, }); res.json(atendimentosIniciais); } catch (error) { console.error("Erro ao buscar atendimentos iniciais:", error); res.status(500).json({ error: "Erro ao buscar atendimentos iniciais" }); } }); // Adicionar rota de busca de clientes app.get("/api/clientes/search", async (req, res) => { try { const { q } = req.query; if (!q || typeof q !== 'string' || q.length < 3) { return res.status(400).json({ error: "Termo de busca inválido" }); } const clientes = await prisma.cliente.findMany({ where: { OR: [ { nome: { contains: q, mode: 'insensitive' } }, { cpfCnpj: { contains: q } }, { email: { contains: q, mode: 'insensitive' } } ] }, take: 10, }); res.json(clientes); } catch (error) { console.error("Erro na busca de clientes:", error); res.status(500).json({ error: "Erro ao buscar clientes" }); } }); // Modificar a rota de criação de atendimento para incluir atendimentoInicialId app.post("/api/atendimentos", authMiddleware, async (req, res) => { try { const { clienteId, processoId, tipo, titulo, conteudo, atendimentoInicialId } = req.body; if (!req.user?.id) { return res.status(401).json({ error: "Usuário não autenticado" }); } if (!clienteId) { return res.status(400).json({ error: "ClienteId é obrigatório" }); } // Validar se existe atendimentoInicialId quando não for INICIAL if (tipo !== TipoAtendimento.INICIAL && !atendimentoInicialId) { return res.status(400).json({ error: "Atendimento inicial é obrigatório para retornos e encaminhamentos" }); } const ano = new Date().getFullYear(); const ultimoProtocolo = await prisma.atendimento.findFirst({ where: { protocolo: { startsWith: `${ano}/` } }, orderBy: { protocolo: 'desc' }, }); const numeroSequencial = ultimoProtocolo ? parseInt(ultimoProtocolo.protocolo.split('/')[1]) + 1 : 1; const protocolo = `${ano}/${numeroSequencial.toString().padStart(6, '0')}`; const data = { protocolo, titulo, tipo, conteudo, clienteId, processoId, usuarioId: req.user.id, ...(atendimentoInicialId && { atendimentoInicialId }), }; const atendimento = await prisma.atendimento.create({ data, include: { cliente: true, processo: true, usuario: { select: { id: true, nome: true, }, }, }, }); res.status(201).json(atendimento); } catch (error) { console.error("Erro ao criar atendimento:", error); res.status(400).json({ error: "Erro ao criar atendimento", details: error instanceof Error ? error.message : "Erro desconhecido" }); } }); // Adicionar rota para buscar atendimento específico app.get("/api/atendimentos/:id", async (req, res) => { try { const { id } = req.params; const atendimento = await prisma.atendimento.findUnique({ where: { id }, include: { cliente: true, processo: true, usuario: { select: { id: true, nome: true, }, }, anexos: true, atendimentoInicial: true, atendimentosRelacionados: { include: { usuario: { select: { id: true, nome: true, } } } } }, }); if (!atendimento) { return res.status(404).json({ error: "Atendimento não encontrado" }); } res.json(atendimento); } catch (error) { console.error("Erro ao buscar atendimento:", error); res.status(500).json({ error: "Erro ao buscar atendimento" }); } }); // Rotas de Agendamento app.get("/api/agendamentos", async (req, res) => { try { const { data } = req.query; const where = data ? { data: { gte: new Date(data as string), lt: new Date(new Date(data as string).setDate(new Date(data as string).getDate() + 1)) } } : {}; const agendamentos = await prisma.agendamentoAtendimento.findMany({ where, include: { cliente: true, usuario: { select: { id: true, nome: true, }, }, atendimento: { select: { id: true, protocolo: true, titulo: true, }, }, }, orderBy: { data: 'asc', }, }); res.json(agendamentos); } catch (error) { console.error("Erro ao buscar agendamentos:", error); res.status(500).json({ error: "Erro ao buscar agendamentos" }); } }); app.post("/api/agendamentos", authMiddleware, async (req, res) => { try { const { clienteId, data, observacoes } = req.body; if (!req.user?.id) { return res.status(401).json({ error: "Usuário não autenticado" }); } const agendamento = await prisma.agendamentoAtendimento.create({ data: { clienteId, usuarioId: req.user.id, data: new Date(data), observacoes, }, include: { cliente: true, usuario: { select: { id: true, nome: true, }, }, }, }); res.status(201).json(agendamento); } catch (error) { console.error("Erro ao criar agendamento:", error); res.status(400).json({ error: "Erro ao criar agendamento" }); } }); app.patch("/api/agendamentos/:id/status", authMiddleware, async (req, res) => { try { const { id } = req.params; const { status } = req.body; const agendamento = await prisma.agendamentoAtendimento.update({ where: { id }, data: { status }, include: { cliente: true, usuario: { select: { id: true, nome: true, }, }, }, }); res.json(agendamento); } catch (error) { console.error("Erro ao atualizar status do agendamento:", error); res.status(400).json({ error: "Erro ao atualizar status do agendamento" }); } }); // Adicionar rotas de atualização e exclusão de agendamentos app.put("/api/agendamentos/:id", authMiddleware, async (req, res) => { try { const { id } = req.params; const { clienteId, data, observacoes, tipo } = req.body; const agendamento = await prisma.agendamentoAtendimento.update({ where: { id }, data: { clienteId, data: new Date(data), observacoes, tipo, }, include: { cliente: true, usuario: { select: { id: true, nome: true, }, }, }, }); res.json(agendamento); } catch (error) { console.error("Erro ao atualizar agendamento:", error); res.status(400).json({ error: "Erro ao atualizar agendamento" }); } }); app.delete("/api/agendamentos/:id", authMiddleware, async (req, res) => { try { const { id } = req.params; await prisma.agendamentoAtendimento.delete({ where: { id }, }); res.status(204).send(); } catch (error) { console.error("Erro ao excluir agendamento:", error); res.status(400).json({ error: "Erro ao excluir agendamento" }); } }); // Atualizar a rota de agendamentos app.get("/api/atendimentos/agendamentos", async (req, res) => { try { const { data } = req.query; if (!data || typeof data !== 'string') { return res.status(400).json({ error: 'Data inválida' }); } const dataConsulta = new Date(data); // Validar se a data é válida if (isNaN(dataConsulta.getTime())) { return res.status(400).json({ error: 'Formato de data inválido' }); } // Definir início e fim do dia const inicioDia = new Date(dataConsulta.setHours(0, 0, 0, 0)); const fimDia = new Date(dataConsulta.setHours(23, 59, 59, 999)); // Buscar na tabela correta com filtros apropriados const agendamentos = await prisma.agendamentoAtendimento.findMany({ where: { data: { gte: inicioDia, lte: fimDia } }, include: { cliente: { select: { id: true, nome: true, cpfCnpj: true, telefone: true } }, usuario: { select: { id: true, nome: true } } }, orderBy: { data: 'asc' } }); // Filtrar apenas agendamentos com UUIDs válidos const validAgendamentos = agendamentos.filter(agendamento => isValidUUID(agendamento.id) && isValidUUID(agendamento.clienteId) && isValidUUID(agendamento.usuarioId) ); console.log(`Encontrados ${validAgendamentos.length} agendamentos válidos para ${data}`); res.json(validAgendamentos); } catch (error) { console.error('Erro ao buscar agendamentos:', error); res.status(500).json({ error: 'Erro ao buscar agendamentos', details: error instanceof Error ? error.message : 'Erro desconhecido' }); } }); // Erro handling middleware app.use( ( err: Error, req: express.Request, res: express.Response, next: express.NextFunction, ) => { console.error(err.stack); res.status(500).json({ error: "Erro interno do servidor" }); }, ); // Aumentar timeout do servidor para operações longas (ex: PDF, OCR) const server = app.listen(port, () => { console.log(`🚀 Servidor rodando em http://localhost:${port}`); // Pré-aquece o worker Tesseract OCR em background (elimina delay na primeira operação) PDFService.warmupOCR('por').catch(() => {}); }); server.timeout = 600000; // 10 minutos (600 segundos) // Encerra workers Tesseract ao desligar o servidor process.on('SIGTERM', async () => { console.log('Encerrando workers OCR...'); await PDFService.terminateWorkers(); process.exit(0); }); process.on('SIGINT', async () => { console.log('Encerrando workers OCR...'); await PDFService.terminateWorkers(); process.exit(0); }); process.on("unhandledRejection", (err) => { console.error("Erro não tratado:", err); }); app.patch("/api/atendimentos/:id/status", authMiddleware, async (req, res) => { try { const { id } = req.params; const { status, justificativa } = req.body; const atendimento = await prisma.atendimento.update({ where: { id }, data: { status, justificativaCancelamento: status === 'CANCELADO' ? justificativa : null, dataAtualizacao: new Date() }, include: { cliente: true, usuario: { select: { id: true, nome: true, }, }, }, }); res.json(atendimento); } catch (error) { console.error("Erro ao atualizar status do atendimento:", error); res.status(400).json({ error: "Erro ao atualizar status do atendimento" }); } }); // Mover estas rotas para o início, antes das outras app.use('/api/anexos', anexosRoutes); app.use('/api/atendimentos', atendimentosRoutes); app.use('/api/provas', provasRouter); app.use('/api/tarefas', tarefasRoutes); app.use('/api/dashboard', authMiddleware, dashboardRoutes); app.use('/api/documentos', documentosRouter); app.use('/api/documentos-modelos', documentosModelosRouter); app.use('/api/processos-administrativos', processosAdministrativosRoutes); app.use('/api/calculos', calculosJudiciaisRoutes); app.use('/api/indices', indicesMonetariosRoutes); app.use('/api/pensao-alimenticia', pensaoAlimenticiaRouter); app.use('/api/financeiro-painel', financeiroPainelRoutes); // Adicionar uso das rotas do financeiro painel app.use('/api/audiencias', audienciasRoutes); // Adicionar uso das rotas de audiências app.use('/api/pdf', pdfRoutes); // Adicionar uso das rotas de PDF