Files
advx/src/server/index.ts
T
rodrigo ab73ea667e pdf: accelerate OCR
Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo.sousa.577@gmail.com>
2026-02-27 20:20:07 -03:00

1505 lines
41 KiB
TypeScript

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