feat: Complete all 10 legal flows, OpenRouter/Gemini integration, and tests
- Switch LLM provider to OpenRouter with Gemini 2.5 Flash as default - Add 4 trabalhista flows: rescisão indireta, dano moral, acúmulo de função, contestação trabalhista - Add 5 cível flows: cobrança, indenização, obrigação de fazer, contestação cível, contrato (revisão) - Register all 10 flows in the flow registry - Write 36 unit tests (sanitize, parse-flow-state, prompt-builder, url-builder, flow-engine, llm-client) - Write 24 integration tests (all routes, deep links, step flow) - All 60 tests passing, all 12 routes verified at runtime https://claude.ai/code/session_01CvrcMDqfCKWV2hC3xpRbx3
This commit is contained in:
parent
001bd7de0a
commit
e966049169
|
|
@ -1,7 +1,11 @@
|
|||
# LLM Provider (openai or anthropic)
|
||||
LLM_PROVIDER=openai
|
||||
LLM_API_KEY=sk-your-api-key
|
||||
LLM_MODEL=gpt-4o-mini
|
||||
# LLM Provider (openrouter, openai, or anthropic)
|
||||
LLM_PROVIDER=openrouter
|
||||
LLM_API_KEY=sk-or-v1-your-openrouter-key
|
||||
LLM_MODEL=google/gemini-2.5-flash
|
||||
LLM_BASE_URL=https://openrouter.ai/api/v1
|
||||
|
||||
# Alternative: use OPENROUTER_API_KEY directly
|
||||
# OPENROUTER_API_KEY=sk-or-v1-your-openrouter-key
|
||||
|
||||
# Server
|
||||
PORT=3000
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm run dev:server\" \"npm run dev:css\"",
|
||||
"dev:server": "tsx watch src/server.ts",
|
||||
"dev:server": "tsx watch --env-file=.env src/server.ts",
|
||||
"dev:css": "npx @tailwindcss/cli -i src/styles/input.css -o src/public/css/app.css --watch",
|
||||
"build": "npm run build:css && npm run build:server",
|
||||
"build:server": "tsc",
|
||||
"build:css": "npx @tailwindcss/cli -i src/styles/input.css -o src/public/css/app.css --minify",
|
||||
"start": "node dist/server.js",
|
||||
"start": "node --env-file=.env dist/server.js",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
||||
import Fastify from "fastify";
|
||||
import fastifyView from "@fastify/view";
|
||||
import fastifyFormbody from "@fastify/formbody";
|
||||
import nunjucks from "nunjucks";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
import { homeRoutes } from "../routes/home.js";
|
||||
import { flowRoutes } from "../routes/flow.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const templatesDir = join(__dirname, "..", "templates");
|
||||
|
||||
async function buildApp() {
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(fastifyFormbody);
|
||||
await app.register(fastifyView, {
|
||||
engine: { nunjucks },
|
||||
templates: templatesDir,
|
||||
});
|
||||
await app.register(homeRoutes);
|
||||
await app.register(flowRoutes);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
describe("Routes Integration", () => {
|
||||
let app: Awaited<ReturnType<typeof buildApp>>;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await buildApp();
|
||||
await app.ready();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe("GET /", () => {
|
||||
it("returns 200", async () => {
|
||||
const response = await app.inject({ method: "GET", url: "/" });
|
||||
expect(response.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
it("contains page content", async () => {
|
||||
const response = await app.inject({ method: "GET", url: "/" });
|
||||
expect(response.body).toContain("Jus IA");
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /health", () => {
|
||||
it("returns ok status", async () => {
|
||||
const response = await app.inject({ method: "GET", url: "/health" });
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json()).toEqual({ status: "ok" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /selecionar", () => {
|
||||
it("returns 200 for trabalhista", async () => {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/selecionar",
|
||||
payload: { tipo_tarefa: "peticao-inicial", area: "trabalhista" },
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toContain("Horas Extras");
|
||||
});
|
||||
|
||||
it("returns 200 for civel", async () => {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/selecionar",
|
||||
payload: { tipo_tarefa: "peticao-inicial", area: "civel" },
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toContain("Cobrança");
|
||||
});
|
||||
|
||||
it("returns not-available for unknown area", async () => {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/selecionar",
|
||||
payload: { tipo_tarefa: "peticao-inicial", area: "penal" },
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toContain("disponível");
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /:area/:subtipo (deep links)", () => {
|
||||
it("returns 200 for trabalhista/horas-extras", async () => {
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/trabalhista/horas-extras",
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toContain("Dados do Caso");
|
||||
});
|
||||
|
||||
it("returns 200 for civel/cobranca", async () => {
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/civel/cobranca",
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toContain("Dados da Dívida");
|
||||
});
|
||||
|
||||
it("returns not-available for unknown flow", async () => {
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/trabalhista/nao-existe",
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toContain("disponível");
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /:area/iniciar", () => {
|
||||
it("starts horas-extras flow", async () => {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/trabalhista/iniciar",
|
||||
payload: { subtipo: "horas-extras", _tipo_tarefa: "peticao-inicial" },
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toContain("Dados do Caso");
|
||||
});
|
||||
|
||||
it("starts cobranca flow", async () => {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/civel/iniciar",
|
||||
payload: { subtipo: "cobranca", _tipo_tarefa: "peticao-inicial" },
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
it("returns not-available for unknown subtipo", async () => {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/trabalhista/iniciar",
|
||||
payload: { subtipo: "nao-existe" },
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toContain("disponível");
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /:area/:subtipo/step/:stepNumber (step submission)", () => {
|
||||
it("advances from step 1 to step 2 in horas-extras", async () => {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/trabalhista/horas-extras/step/1",
|
||||
payload: {
|
||||
_area: "trabalhista",
|
||||
_subtipo: "horas-extras",
|
||||
_tipo_tarefa: "peticao-inicial",
|
||||
_step: "1",
|
||||
_total_steps: "5",
|
||||
_responses: "{}",
|
||||
empregador_tipo: "Pessoa Jurídica (empresa)",
|
||||
regime: "CLT",
|
||||
jornada_contratual: "44h semanais",
|
||||
data_inicio: "2020-01-01",
|
||||
data_fim: "2024-01-01",
|
||||
ainda_empregado: "Não",
|
||||
horas_extras_semana: "5 a 10 horas",
|
||||
banco_horas: "Não",
|
||||
},
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toContain("Detalhes do Caso");
|
||||
});
|
||||
|
||||
it("shows preview after final step in horas-extras", async () => {
|
||||
const responses = JSON.stringify({
|
||||
empregador_tipo: "Pessoa Jurídica (empresa)",
|
||||
regime: "CLT",
|
||||
jornada_contratual: "44h semanais",
|
||||
data_inicio: "2020-01-01",
|
||||
data_fim: "2024-01-01",
|
||||
ainda_empregado: "Não",
|
||||
horas_extras_semana: "5 a 10 horas",
|
||||
banco_horas: "Não",
|
||||
});
|
||||
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/trabalhista/horas-extras/step/2",
|
||||
payload: {
|
||||
_area: "trabalhista",
|
||||
_subtipo: "horas-extras",
|
||||
_tipo_tarefa: "peticao-inicial",
|
||||
_step: "2",
|
||||
_total_steps: "5",
|
||||
_responses: responses,
|
||||
registro_ponto: "Sim, eletrônico",
|
||||
testemunhas: "Sim",
|
||||
pagamento_parcial: "Não, nenhum pagamento",
|
||||
},
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
// Should contain the assembled prompt with CLT reference
|
||||
expect(response.body).toContain("CLT");
|
||||
});
|
||||
});
|
||||
|
||||
describe("All registered flows have valid deep links", () => {
|
||||
const flows = [
|
||||
"/trabalhista/horas-extras",
|
||||
"/trabalhista/rescisao-indireta",
|
||||
"/trabalhista/dano-moral",
|
||||
"/trabalhista/acumulo-funcao",
|
||||
"/trabalhista/contestacao",
|
||||
"/civel/cobranca",
|
||||
"/civel/indenizacao",
|
||||
"/civel/obrigacao-fazer",
|
||||
"/civel/contestacao",
|
||||
"/civel/contrato",
|
||||
];
|
||||
|
||||
for (const flowUrl of flows) {
|
||||
it(`GET ${flowUrl} returns 200`, async () => {
|
||||
const response = await app.inject({ method: "GET", url: flowUrl });
|
||||
expect(response.statusCode).toBe(200);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,16 @@
|
|||
import type { FlowConfig } from "../flows/types.js";
|
||||
// Trabalhista flows
|
||||
import { horasExtrasFlow } from "../flows/trabalhista/horas-extras.js";
|
||||
import { rescisaoIndiretaFlow } from "../flows/trabalhista/rescisao-indireta.js";
|
||||
import { danoMoralFlow } from "../flows/trabalhista/dano-moral.js";
|
||||
import { acumuloFuncaoFlow } from "../flows/trabalhista/acumulo-funcao.js";
|
||||
import { contestacaoTrabalhistaFlow } from "../flows/trabalhista/contestacao.js";
|
||||
// Cível flows
|
||||
import { cobrancaFlow } from "../flows/civel/cobranca.js";
|
||||
import { indenizacaoFlow } from "../flows/civel/indenizacao.js";
|
||||
import { obrigacaoFazerFlow } from "../flows/civel/obrigacao-fazer.js";
|
||||
import { contestacaoCivelFlow } from "../flows/civel/contestacao.js";
|
||||
import { contratoRevisaoFlow } from "../flows/civel/contrato.js";
|
||||
|
||||
/** Registry of all available flows */
|
||||
const flowRegistry: Map<string, FlowConfig> = new Map();
|
||||
|
|
@ -9,9 +20,19 @@ function registerFlow(flow: FlowConfig): void {
|
|||
flowRegistry.set(key, flow);
|
||||
}
|
||||
|
||||
// Register all flows
|
||||
// Register all trabalhista flows
|
||||
registerFlow(horasExtrasFlow);
|
||||
// TODO: Register remaining 9 flows as they are built
|
||||
registerFlow(rescisaoIndiretaFlow);
|
||||
registerFlow(danoMoralFlow);
|
||||
registerFlow(acumuloFuncaoFlow);
|
||||
registerFlow(contestacaoTrabalhistaFlow);
|
||||
|
||||
// Register all cível flows
|
||||
registerFlow(cobrancaFlow);
|
||||
registerFlow(indenizacaoFlow);
|
||||
registerFlow(obrigacaoFazerFlow);
|
||||
registerFlow(contestacaoCivelFlow);
|
||||
registerFlow(contratoRevisaoFlow);
|
||||
|
||||
/** Get a flow by area/subtipo */
|
||||
export function getFlow(area: string, subtipo: string): FlowConfig | undefined {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,13 @@ export const config = {
|
|||
host: process.env.HOST || "0.0.0.0",
|
||||
nodeEnv: process.env.NODE_ENV || "development",
|
||||
llm: {
|
||||
provider: process.env.LLM_PROVIDER || "openai",
|
||||
apiKey: process.env.LLM_API_KEY || "",
|
||||
model: process.env.LLM_MODEL || "gpt-4o-mini",
|
||||
provider: (process.env.LLM_PROVIDER || "openrouter") as
|
||||
| "openrouter"
|
||||
| "openai"
|
||||
| "anthropic",
|
||||
apiKey: process.env.LLM_API_KEY || process.env.OPENROUTER_API_KEY || "",
|
||||
model: process.env.LLM_MODEL || "google/gemini-2.5-flash",
|
||||
baseUrl: process.env.LLM_BASE_URL || "https://openrouter.ai/api/v1",
|
||||
},
|
||||
analyticsId: process.env.ANALYTICS_ID || "",
|
||||
sentryDsn: process.env.SENTRY_DSN || "",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,179 @@
|
|||
import type { FlowConfig } from "../types.js";
|
||||
|
||||
export const cobrancaFlow: FlowConfig = {
|
||||
area: "civel",
|
||||
areaLabel: "Cível",
|
||||
subtipo: "cobranca",
|
||||
subtipoLabel: "Cobrança",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
steps: [
|
||||
{
|
||||
stepNumber: 1,
|
||||
title: "Dados da Dívida",
|
||||
requiresLlm: false,
|
||||
groups: [
|
||||
{
|
||||
title: "Credor e Devedor",
|
||||
questions: [
|
||||
{
|
||||
id: "relacao_partes",
|
||||
text: "Qual a relação entre as partes?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Contrato de prestação de serviço",
|
||||
"Empréstimo pessoal",
|
||||
"Venda de produto",
|
||||
"Aluguel",
|
||||
"Cheque devolvido",
|
||||
"Nota promissória",
|
||||
"Outro",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "tipo_devedor",
|
||||
text: "O devedor é pessoa física ou jurídica?",
|
||||
type: "select",
|
||||
options: ["PF", "PJ"],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Valor e Prazo",
|
||||
questions: [
|
||||
{
|
||||
id: "faixa_valor",
|
||||
text: "Qual a faixa de valor da dívida?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Até R$ 5 mil",
|
||||
"R$ 5 a 20 mil",
|
||||
"R$ 20 a 50 mil",
|
||||
"Acima de R$ 50 mil",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "data_vencimento",
|
||||
text: "Data de vencimento da dívida",
|
||||
type: "date",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "tentou_cobranca",
|
||||
text: "Já tentou cobrar antes?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Sim, extrajudicialmente",
|
||||
"Sim, com protesto",
|
||||
"Não",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
title: "Documentação",
|
||||
requiresLlm: true,
|
||||
groups: [
|
||||
{
|
||||
title: "Provas da Dívida",
|
||||
questions: [
|
||||
{
|
||||
id: "documentos",
|
||||
text: "Quais documentos possui?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Contrato assinado",
|
||||
"Nota promissória",
|
||||
"Cheque",
|
||||
"Notas fiscais",
|
||||
"Comprovantes de transferência",
|
||||
"Mensagens",
|
||||
"E-mails",
|
||||
"Nenhum documento formal",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "divida_reconhecida",
|
||||
text: "A dívida é reconhecida pelo devedor?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Sim, devedor reconhece",
|
||||
"Parcialmente",
|
||||
"Não, devedor contesta",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Situação Atual",
|
||||
questions: [
|
||||
{
|
||||
id: "parcelas_pagas",
|
||||
text: "Houve pagamento parcial?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Nenhuma",
|
||||
"Algumas parcelas",
|
||||
"Maioria paga, faltam poucas",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "possui_garantia",
|
||||
text: "Há garantia vinculada à dívida?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Sim, com garantia real",
|
||||
"Sim, com fiador",
|
||||
"Não",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
promptTemplate: `Elabore uma petição inicial de ação de cobrança com os seguintes dados:
|
||||
|
||||
**Partes:**
|
||||
- Credor: [a ser preenchido pelo advogado]
|
||||
- Devedor: {{tipo_devedor}}
|
||||
|
||||
**Relação entre as partes:**
|
||||
- Origem: {{relacao_partes}}
|
||||
- Faixa de valor: {{faixa_valor}}
|
||||
- Data de vencimento: {{data_vencimento}}
|
||||
- Cobrança prévia: {{tentou_cobranca}}
|
||||
|
||||
**Documentação e provas:**
|
||||
- Documentos disponíveis: {{documentos}}
|
||||
- Reconhecimento da dívida: {{divida_reconhecida}}
|
||||
- Parcelas pagas: {{parcelas_pagas}}
|
||||
- Garantia: {{possui_garantia}}
|
||||
|
||||
{{#refinement_context}}
|
||||
|
||||
**Fundamente com base em:**
|
||||
- Art. 318 do CPC (procedimento comum)
|
||||
- Art. 319 do CPC (requisitos da petição inicial)
|
||||
- Art. 784 do CPC (títulos executivos extrajudiciais)
|
||||
- Art. 397 do CC (mora)
|
||||
|
||||
Inclua pedidos de: pagamento do valor principal, juros de mora, correção monetária, custas processuais e honorários advocatícios.`,
|
||||
|
||||
legalReferences: [
|
||||
"art. 318 CPC",
|
||||
"art. 319 CPC",
|
||||
"art. 784 CPC",
|
||||
"art. 397 CC",
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
import type { FlowConfig } from "../types.js";
|
||||
|
||||
export const contestacaoCivelFlow: FlowConfig = {
|
||||
area: "civel",
|
||||
areaLabel: "Cível",
|
||||
subtipo: "contestacao",
|
||||
subtipoLabel: "Contestação Cível",
|
||||
tipoTarefa: "contestacao",
|
||||
steps: [
|
||||
{
|
||||
stepNumber: 1,
|
||||
title: "Dados do Processo",
|
||||
requiresLlm: false,
|
||||
groups: [
|
||||
{
|
||||
title: "Processo",
|
||||
questions: [
|
||||
{
|
||||
id: "numero_processo",
|
||||
text: "Número do processo",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "vara_tribunal",
|
||||
text: "Vara e tribunal",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "tipo_acao_autor",
|
||||
text: "Qual o tipo de ação movida pelo autor?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Cobrança",
|
||||
"Indenização",
|
||||
"Obrigação de fazer",
|
||||
"Revisional",
|
||||
"Consignação",
|
||||
"Outra",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Partes",
|
||||
questions: [
|
||||
{
|
||||
id: "posicao_cliente",
|
||||
text: "Posição do cliente no processo",
|
||||
type: "select",
|
||||
options: [
|
||||
"Réu pessoa física",
|
||||
"Réu pessoa jurídica",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "valor_causa",
|
||||
text: "Valor da causa",
|
||||
type: "select",
|
||||
options: [
|
||||
"Até R$ 20 mil",
|
||||
"R$ 20 a 50 mil",
|
||||
"R$ 50 a 100 mil",
|
||||
"Acima de R$ 100 mil",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
title: "Estratégia de Defesa",
|
||||
requiresLlm: true,
|
||||
groups: [
|
||||
{
|
||||
title: "Preliminares",
|
||||
questions: [
|
||||
{
|
||||
id: "preliminares",
|
||||
text: "Quais preliminares deseja alegar?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Inépcia da inicial",
|
||||
"Ilegitimidade passiva",
|
||||
"Falta de interesse de agir",
|
||||
"Incompetência",
|
||||
"Litispendência",
|
||||
"Coisa julgada",
|
||||
"Prescrição",
|
||||
"Decadência",
|
||||
"Nenhuma",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "merito_defesa",
|
||||
text: "Qual a tese principal de defesa no mérito?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Fato não ocorreu",
|
||||
"Fato ocorreu diferente",
|
||||
"Inexistência de dano",
|
||||
"Culpa exclusiva do autor",
|
||||
"Caso fortuito ou força maior",
|
||||
"Pagamento já realizado",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Provas",
|
||||
questions: [
|
||||
{
|
||||
id: "documentos_defesa",
|
||||
text: "Quais documentos possui para defesa?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Contrato",
|
||||
"Comprovantes de pagamento",
|
||||
"E-mails",
|
||||
"Protocolo",
|
||||
"Fotos",
|
||||
"Testemunhas",
|
||||
"Perícia",
|
||||
"Nenhum",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "pedido_reconvencao",
|
||||
text: "Deseja fazer pedido reconvencional?",
|
||||
type: "select",
|
||||
options: ["Sim", "Não"],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
promptTemplate: `Elabore uma contestação cível com os seguintes dados:
|
||||
|
||||
**Processo:**
|
||||
- Número: {{numero_processo}}
|
||||
- Vara/Tribunal: {{vara_tribunal}}
|
||||
- Tipo de ação: {{tipo_acao_autor}}
|
||||
- Valor da causa: {{valor_causa}}
|
||||
|
||||
**Partes:**
|
||||
- Contestante: {{posicao_cliente}}
|
||||
|
||||
**Estratégia de defesa:**
|
||||
- Preliminares: {{preliminares}}
|
||||
- Tese de mérito: {{merito_defesa}}
|
||||
|
||||
**Provas e pedidos:**
|
||||
- Documentos: {{documentos_defesa}}
|
||||
- Reconvenção: {{pedido_reconvencao}}
|
||||
|
||||
{{#refinement_context}}
|
||||
|
||||
**Fundamente com base em:**
|
||||
- Art. 335 do CPC (prazo para contestação)
|
||||
- Art. 336 do CPC (ônus de impugnar especificamente)
|
||||
- Art. 337 do CPC (preliminares de contestação)
|
||||
- Art. 343 do CPC (reconvenção)
|
||||
|
||||
Inclua: preliminares aplicáveis, impugnação específica dos fatos, defesa de mérito, requerimento de provas e pedidos finais.`,
|
||||
|
||||
legalReferences: [
|
||||
"art. 335 CPC",
|
||||
"art. 336 CPC",
|
||||
"art. 337 CPC",
|
||||
"art. 343 CPC",
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
import type { FlowConfig } from "../types.js";
|
||||
|
||||
export const contratoRevisaoFlow: FlowConfig = {
|
||||
area: "civel",
|
||||
areaLabel: "Cível",
|
||||
subtipo: "contrato",
|
||||
subtipoLabel: "Contrato (Revisão)",
|
||||
tipoTarefa: "contrato",
|
||||
steps: [
|
||||
{
|
||||
stepNumber: 1,
|
||||
title: "Dados do Contrato",
|
||||
requiresLlm: false,
|
||||
groups: [
|
||||
{
|
||||
title: "Tipo de Contrato",
|
||||
questions: [
|
||||
{
|
||||
id: "tipo_contrato",
|
||||
text: "Qual o tipo de contrato?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Aluguel",
|
||||
"Prestação de serviço",
|
||||
"Financiamento",
|
||||
"Empréstimo",
|
||||
"Seguro",
|
||||
"Outro",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "partes_contrato",
|
||||
text: "Quais as partes do contrato?",
|
||||
type: "select",
|
||||
options: ["PF x PF", "PF x PJ", "PJ x PJ"],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "data_contrato",
|
||||
text: "Data de assinatura do contrato",
|
||||
type: "date",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "vigencia",
|
||||
text: "Situação do contrato",
|
||||
type: "select",
|
||||
options: ["Em vigor", "Encerrado", "Rescindido"],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Problema",
|
||||
questions: [
|
||||
{
|
||||
id: "problema_principal",
|
||||
text: "Qual o problema principal do contrato?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Cláusula abusiva",
|
||||
"Reajuste abusivo",
|
||||
"Descumprimento",
|
||||
"Vício de consentimento",
|
||||
"Onerosidade excessiva",
|
||||
"Outro",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "tentou_negociar",
|
||||
text: "Tentou negociar com a outra parte?",
|
||||
type: "select",
|
||||
options: ["Sim, sem sucesso", "Não"],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
title: "Detalhes da Revisão",
|
||||
requiresLlm: true,
|
||||
groups: [
|
||||
{
|
||||
title: "Cláusulas",
|
||||
questions: [
|
||||
{
|
||||
id: "clausulas_contestadas",
|
||||
text: "Cláusulas que deseja revisar",
|
||||
type: "text",
|
||||
placeholder: "Quais cláusulas deseja revisar?",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "valor_atual",
|
||||
text: "Valor atual do contrato",
|
||||
type: "text",
|
||||
placeholder: "Valor mensal ou total atual",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "valor_pretendido",
|
||||
text: "Valor pretendido",
|
||||
type: "text",
|
||||
placeholder: "Valor que considera justo",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "fundamentacao",
|
||||
text: "Fundamentação legal principal",
|
||||
type: "select",
|
||||
options: [
|
||||
"CDC - relação de consumo",
|
||||
"CC - onerosidade excessiva",
|
||||
"CC - lesão",
|
||||
"Lei do Inquilinato",
|
||||
"Outro",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Documentação",
|
||||
questions: [
|
||||
{
|
||||
id: "documentos",
|
||||
text: "Quais documentos possui?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Contrato original",
|
||||
"Aditivos",
|
||||
"Comprovantes de pagamento",
|
||||
"Notificações",
|
||||
"Extratos",
|
||||
"Nenhum",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "pedido_tutela",
|
||||
text: "Deseja pedir tutela de urgência?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Sim, com tutela de urgência",
|
||||
"Não",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
promptTemplate: `Elabore uma petição inicial de ação revisional de contrato com os seguintes dados:
|
||||
|
||||
**Contrato:**
|
||||
- Tipo: {{tipo_contrato}}
|
||||
- Partes: {{partes_contrato}}
|
||||
- Data: {{data_contrato}}
|
||||
- Vigência: {{vigencia}}
|
||||
|
||||
**Problema:**
|
||||
- Problema principal: {{problema_principal}}
|
||||
- Tentativa de negociação: {{tentou_negociar}}
|
||||
|
||||
**Revisão pretendida:**
|
||||
- Cláusulas contestadas: {{clausulas_contestadas}}
|
||||
- Valor atual: {{valor_atual}}
|
||||
- Valor pretendido: {{valor_pretendido}}
|
||||
- Fundamentação: {{fundamentacao}}
|
||||
|
||||
**Documentação e pedidos:**
|
||||
- Documentos: {{documentos}}
|
||||
- Tutela de urgência: {{pedido_tutela}}
|
||||
|
||||
{{#refinement_context}}
|
||||
|
||||
**Fundamente com base em:**
|
||||
- Art. 317 do CC (correção do valor da prestação)
|
||||
- Art. 478 do CC (resolução por onerosidade excessiva)
|
||||
- Art. 51 do CDC (nulidade de cláusulas abusivas)
|
||||
- Art. 6º, V do CDC (modificação de cláusulas desproporcionais)
|
||||
|
||||
Inclua pedidos de: revisão das cláusulas abusivas, adequação dos valores, tutela de urgência (se aplicável), custas processuais e honorários advocatícios.`,
|
||||
|
||||
legalReferences: [
|
||||
"art. 317 CC",
|
||||
"art. 478 CC",
|
||||
"art. 51 CDC",
|
||||
"art. 6º, V CDC",
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
import type { FlowConfig } from "../types.js";
|
||||
|
||||
export const indenizacaoFlow: FlowConfig = {
|
||||
area: "civel",
|
||||
areaLabel: "Cível",
|
||||
subtipo: "indenizacao",
|
||||
subtipoLabel: "Indenização",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
steps: [
|
||||
{
|
||||
stepNumber: 1,
|
||||
title: "Dados do Fato",
|
||||
requiresLlm: false,
|
||||
groups: [
|
||||
{
|
||||
title: "Tipo de Dano",
|
||||
questions: [
|
||||
{
|
||||
id: "tipo_indenizacao",
|
||||
text: "Qual o tipo de indenização pretendida?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Dano material",
|
||||
"Dano moral",
|
||||
"Danos estéticos",
|
||||
"Lucros cessantes",
|
||||
"Dano material e moral",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "origem",
|
||||
text: "Qual a origem do dano?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Acidente de trânsito",
|
||||
"Erro médico",
|
||||
"Relação de consumo",
|
||||
"Relação contratual",
|
||||
"Ato ilícito",
|
||||
"Outro",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "data_fato",
|
||||
text: "Data do fato",
|
||||
type: "date",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Partes",
|
||||
questions: [
|
||||
{
|
||||
id: "tipo_responsavel",
|
||||
text: "O responsável é pessoa física, jurídica ou poder público?",
|
||||
type: "select",
|
||||
options: ["PF", "PJ", "Poder Público"],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "relacao_com_responsavel",
|
||||
text: "Qual a relação com o responsável?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Consumidor",
|
||||
"Contratante",
|
||||
"Terceiro",
|
||||
"Paciente",
|
||||
"Outro",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
title: "Detalhes e Provas",
|
||||
requiresLlm: true,
|
||||
groups: [
|
||||
{
|
||||
title: "Extensão do Dano",
|
||||
questions: [
|
||||
{
|
||||
id: "descricao_dano",
|
||||
text: "Descrição do dano sofrido",
|
||||
type: "text",
|
||||
placeholder: "Descreva brevemente o dano sofrido",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "valor_estimado",
|
||||
text: "Valor estimado da indenização",
|
||||
type: "select",
|
||||
options: [
|
||||
"Até R$ 10 mil",
|
||||
"R$ 10 a 50 mil",
|
||||
"R$ 50 a 100 mil",
|
||||
"Acima de R$ 100 mil",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "consequencias",
|
||||
text: "Quais as consequências do dano?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Gastos médicos",
|
||||
"Perda de renda",
|
||||
"Dano psicológico",
|
||||
"Dano estético",
|
||||
"Perda de bem material",
|
||||
"Outro",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Provas",
|
||||
questions: [
|
||||
{
|
||||
id: "provas",
|
||||
text: "Quais provas possui?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Boletim de ocorrência",
|
||||
"Laudos médicos",
|
||||
"Fotos",
|
||||
"Vídeos",
|
||||
"Testemunhas",
|
||||
"Notas fiscais",
|
||||
"Orçamentos",
|
||||
"Contratos",
|
||||
"Nenhuma",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "tentou_acordo",
|
||||
text: "Tentou acordo extrajudicial?",
|
||||
type: "select",
|
||||
options: ["Sim, sem sucesso", "Não"],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
promptTemplate: `Elabore uma petição inicial de ação de indenização por responsabilidade civil com os seguintes dados:
|
||||
|
||||
**Partes:**
|
||||
- Autor: [a ser preenchido pelo advogado]
|
||||
- Réu: {{tipo_responsavel}}
|
||||
- Relação: {{relacao_com_responsavel}}
|
||||
|
||||
**Fato gerador:**
|
||||
- Origem: {{origem}}
|
||||
- Data do fato: {{data_fato}}
|
||||
- Tipo de indenização: {{tipo_indenizacao}}
|
||||
|
||||
**Extensão do dano:**
|
||||
- Descrição: {{descricao_dano}}
|
||||
- Valor estimado: {{valor_estimado}}
|
||||
- Consequências: {{consequencias}}
|
||||
|
||||
**Provas disponíveis:**
|
||||
- Provas: {{provas}}
|
||||
- Tentativa de acordo: {{tentou_acordo}}
|
||||
|
||||
{{#refinement_context}}
|
||||
|
||||
**Fundamente com base em:**
|
||||
- Art. 186 do CC (ato ilícito)
|
||||
- Art. 927 do CC (obrigação de reparar o dano)
|
||||
- Art. 944 do CC (indenização mede-se pela extensão do dano)
|
||||
- Art. 14 do CDC (responsabilidade do fornecedor)
|
||||
|
||||
Inclua pedidos de: indenização por danos materiais e/ou morais, juros de mora, correção monetária, custas processuais e honorários advocatícios.`,
|
||||
|
||||
legalReferences: [
|
||||
"art. 186 CC",
|
||||
"art. 927 CC",
|
||||
"art. 944 CC",
|
||||
"art. 14 CDC",
|
||||
],
|
||||
};
|
||||
|
|
@ -2,10 +2,10 @@ export const civelArea = {
|
|||
value: "civel",
|
||||
label: "Cível",
|
||||
subtipos: [
|
||||
{ value: "cobranca", label: "Cobrança", available: false },
|
||||
{ value: "indenizacao", label: "Indenização", available: false },
|
||||
{ value: "obrigacao-fazer", label: "Obrigação de Fazer", available: false },
|
||||
{ value: "contestacao", label: "Contestação Cível", available: false },
|
||||
{ value: "contrato", label: "Contrato (Revisão)", available: false },
|
||||
{ value: "cobranca", label: "Cobrança" },
|
||||
{ value: "indenizacao", label: "Indenização" },
|
||||
{ value: "obrigacao-fazer", label: "Obrigação de Fazer" },
|
||||
{ value: "contestacao", label: "Contestação Cível" },
|
||||
{ value: "contrato", label: "Contrato (Revisão)" },
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,182 @@
|
|||
import type { FlowConfig } from "../types.js";
|
||||
|
||||
export const obrigacaoFazerFlow: FlowConfig = {
|
||||
area: "civel",
|
||||
areaLabel: "Cível",
|
||||
subtipo: "obrigacao-fazer",
|
||||
subtipoLabel: "Obrigação de Fazer",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
steps: [
|
||||
{
|
||||
stepNumber: 1,
|
||||
title: "Dados da Obrigação",
|
||||
requiresLlm: false,
|
||||
groups: [
|
||||
{
|
||||
title: "Origem",
|
||||
questions: [
|
||||
{
|
||||
id: "origem_obrigacao",
|
||||
text: "Qual a origem da obrigação?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Contrato",
|
||||
"Decisão judicial anterior",
|
||||
"Lei ou regulamento",
|
||||
"Relação de consumo",
|
||||
"Outro",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "tipo_obrigante",
|
||||
text: "Quem deveria cumprir a obrigação?",
|
||||
type: "select",
|
||||
options: ["PF", "PJ", "Poder Público"],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Obrigação",
|
||||
questions: [
|
||||
{
|
||||
id: "descricao_obrigacao",
|
||||
text: "Descrição da obrigação",
|
||||
type: "text",
|
||||
placeholder: "O que deveria ser feito ou entregue?",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "prazo_original",
|
||||
text: "Prazo original para cumprimento",
|
||||
type: "date",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "urgencia",
|
||||
text: "Qual o grau de urgência?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Urgente - risco de dano irreparável",
|
||||
"Moderada",
|
||||
"Baixa - pode aguardar rito normal",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
title: "Detalhes",
|
||||
requiresLlm: true,
|
||||
groups: [
|
||||
{
|
||||
title: "Inadimplência",
|
||||
questions: [
|
||||
{
|
||||
id: "notificou",
|
||||
text: "Notificou o obrigado?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Sim, com AR",
|
||||
"Sim, por e-mail",
|
||||
"Sim, verbal",
|
||||
"Não",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "motivo_recusa",
|
||||
text: "Qual o motivo da recusa ou inadimplência?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Alega impossibilidade",
|
||||
"Ignora pedidos",
|
||||
"Contesta obrigação",
|
||||
"Desconhecido",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "prejuizo",
|
||||
text: "Qual o prejuízo causado?",
|
||||
type: "text",
|
||||
placeholder: "Qual o prejuízo pela não realização?",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Provas",
|
||||
questions: [
|
||||
{
|
||||
id: "documentos",
|
||||
text: "Quais documentos possui?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Contrato",
|
||||
"Notificação",
|
||||
"E-mails",
|
||||
"Mensagens",
|
||||
"Protocolo de atendimento",
|
||||
"Fotos",
|
||||
"Nenhum",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "pedido_tutela",
|
||||
text: "Deseja pedir tutela de urgência?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Sim, com tutela de urgência",
|
||||
"Não, apenas rito normal",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
promptTemplate: `Elabore uma petição inicial de ação de obrigação de fazer com os seguintes dados:
|
||||
|
||||
**Partes:**
|
||||
- Autor: [a ser preenchido pelo advogado]
|
||||
- Réu: {{tipo_obrigante}}
|
||||
|
||||
**Obrigação:**
|
||||
- Origem: {{origem_obrigacao}}
|
||||
- Descrição: {{descricao_obrigacao}}
|
||||
- Prazo original: {{prazo_original}}
|
||||
- Urgência: {{urgencia}}
|
||||
|
||||
**Inadimplência:**
|
||||
- Notificação: {{notificou}}
|
||||
- Motivo da recusa: {{motivo_recusa}}
|
||||
- Prejuízo: {{prejuizo}}
|
||||
|
||||
**Provas e pedidos:**
|
||||
- Documentos: {{documentos}}
|
||||
- Tutela de urgência: {{pedido_tutela}}
|
||||
|
||||
{{#refinement_context}}
|
||||
|
||||
**Fundamente com base em:**
|
||||
- Art. 497 do CPC (tutela específica das obrigações de fazer)
|
||||
- Art. 536 do CPC (cumprimento de sentença de obrigação de fazer)
|
||||
- Art. 537 do CPC (multa periódica - astreintes)
|
||||
- Art. 300 do CPC (tutela de urgência)
|
||||
|
||||
Inclua pedidos de: cumprimento da obrigação de fazer, fixação de astreintes em caso de descumprimento, tutela de urgência (se aplicável), custas processuais e honorários advocatícios.`,
|
||||
|
||||
legalReferences: [
|
||||
"art. 497 CPC",
|
||||
"art. 536 CPC",
|
||||
"art. 537 CPC",
|
||||
"art. 300 CPC",
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
import type { FlowConfig } from "../types.js";
|
||||
|
||||
export const acumuloFuncaoFlow: FlowConfig = {
|
||||
area: "trabalhista",
|
||||
areaLabel: "Trabalhista",
|
||||
subtipo: "acumulo-funcao",
|
||||
subtipoLabel: "Acúmulo de Função",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
steps: [
|
||||
{
|
||||
stepNumber: 1,
|
||||
title: "Dados do Vínculo",
|
||||
requiresLlm: false,
|
||||
groups: [
|
||||
{
|
||||
title: "Contrato",
|
||||
questions: [
|
||||
{
|
||||
id: "cargo_registrado",
|
||||
text: "Qual o cargo registrado em carteira?",
|
||||
type: "text",
|
||||
placeholder: "Ex: Auxiliar Administrativo",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "cargo_exercido",
|
||||
text: "Qual o cargo efetivamente exercido?",
|
||||
type: "text",
|
||||
placeholder: "Ex: Auxiliar Administrativo + Financeiro",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "regime",
|
||||
text: "Qual o regime de trabalho?",
|
||||
type: "select",
|
||||
options: ["CLT", "PJ", "Autônomo", "Temporário"],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "tempo_servico",
|
||||
text: "Quanto tempo de serviço?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Menos de 1 ano",
|
||||
"1 a 3 anos",
|
||||
"3 a 5 anos",
|
||||
"Mais de 5 anos",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Remuneração",
|
||||
questions: [
|
||||
{
|
||||
id: "faixa_salarial",
|
||||
text: "Qual a faixa salarial?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Até 2 SM",
|
||||
"2 a 5 SM",
|
||||
"5 a 10 SM",
|
||||
"Acima de 10 SM",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "recebeu_adicional",
|
||||
text: "Recebeu algum adicional pelo acúmulo?",
|
||||
type: "select",
|
||||
options: ["Sim", "Não"],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
title: "Detalhes do Acúmulo",
|
||||
requiresLlm: true,
|
||||
groups: [
|
||||
{
|
||||
title: "Funções Acumuladas",
|
||||
questions: [
|
||||
{
|
||||
id: "funcoes_extras",
|
||||
text: "Descreva as funções exercidas além do cargo contratado",
|
||||
type: "text",
|
||||
placeholder: "Descreva as funções exercidas além do cargo contratado",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "inicio_acumulo",
|
||||
text: "Quando começou o acúmulo de função?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Desde a contratação",
|
||||
"Após promoção",
|
||||
"Após saída de colega",
|
||||
"Após reestruturação",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "frequencia_acumulo",
|
||||
text: "Com que frequência exerce as funções extras?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Diariamente",
|
||||
"Várias vezes por semana",
|
||||
"Eventualmente",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Provas",
|
||||
questions: [
|
||||
{
|
||||
id: "provas_disponiveis",
|
||||
text: "Quais provas estão disponíveis?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"E-mails com atribuições",
|
||||
"Testemunhas",
|
||||
"Descrição de cargo",
|
||||
"Contracheques",
|
||||
"Prints de sistemas",
|
||||
"Nenhuma",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "outros_exercem",
|
||||
text: "Há outro funcionário específico para a função acumulada?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Sim, há funcionário específico",
|
||||
"Sim, havia antes",
|
||||
"Não sei",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
promptTemplate: `Elabore uma petição inicial trabalhista de adicional por acúmulo de função com os seguintes dados:
|
||||
|
||||
**Partes:**
|
||||
- Reclamante: [a ser preenchido pelo advogado]
|
||||
- Reclamada: [a ser preenchido pelo advogado]
|
||||
|
||||
**Vínculo empregatício:**
|
||||
- Cargo registrado: {{cargo_registrado}}
|
||||
- Cargo efetivamente exercido: {{cargo_exercido}}
|
||||
- Regime: {{regime}}
|
||||
- Tempo de serviço: {{tempo_servico}}
|
||||
- Faixa salarial: {{faixa_salarial}}
|
||||
- Recebeu adicional: {{recebeu_adicional}}
|
||||
|
||||
**Detalhes do acúmulo:**
|
||||
- Funções extras exercidas: {{funcoes_extras}}
|
||||
- Início do acúmulo: {{inicio_acumulo}}
|
||||
- Frequência: {{frequencia_acumulo}}
|
||||
|
||||
**Provas:**
|
||||
- Provas disponíveis: {{provas_disponiveis}}
|
||||
- Existência de funcionário específico para a função: {{outros_exercem}}
|
||||
|
||||
{{#refinement_context}}
|
||||
|
||||
**Fundamente com base em:**
|
||||
- Art. 456, parágrafo único da CLT (condição do contrato de trabalho)
|
||||
- Art. 468 da CLT (alteração contratual lesiva)
|
||||
|
||||
Inclua pedidos de: pagamento de adicional por acúmulo de função com reflexos em férias + 1/3, 13º salário, FGTS, DSR, e honorários advocatícios.`,
|
||||
|
||||
legalReferences: [
|
||||
"art. 456, parágrafo único CLT",
|
||||
"art. 468 CLT",
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
import type { FlowConfig } from "../types.js";
|
||||
|
||||
export const contestacaoTrabalhistaFlow: FlowConfig = {
|
||||
area: "trabalhista",
|
||||
areaLabel: "Trabalhista",
|
||||
subtipo: "contestacao",
|
||||
subtipoLabel: "Contestação Trabalhista",
|
||||
tipoTarefa: "contestacao",
|
||||
steps: [
|
||||
{
|
||||
stepNumber: 1,
|
||||
title: "Dados do Processo",
|
||||
requiresLlm: false,
|
||||
groups: [
|
||||
{
|
||||
title: "Processo",
|
||||
questions: [
|
||||
{
|
||||
id: "numero_processo",
|
||||
text: "Qual o número do processo?",
|
||||
type: "text",
|
||||
placeholder: "0000000-00.0000.0.00.0000",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "vara_tribunal",
|
||||
text: "Qual a vara e tribunal?",
|
||||
type: "text",
|
||||
placeholder: "Ex: 1ª Vara do Trabalho de São Paulo",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "tipo_acao",
|
||||
text: "Qual o tipo de ação?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Reclamatória trabalhista",
|
||||
"Ação de consignação",
|
||||
"Inquérito para apuração de falta grave",
|
||||
"Outra",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Partes",
|
||||
questions: [
|
||||
{
|
||||
id: "porte_empresa",
|
||||
text: "Qual o porte da empresa reclamada?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Microempresa",
|
||||
"Pequena empresa",
|
||||
"Média empresa",
|
||||
"Grande empresa",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "segmento",
|
||||
text: "Qual o segmento da empresa?",
|
||||
type: "text",
|
||||
placeholder: "Ex: Comércio, Indústria, Serviços",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
title: "Pedidos do Reclamante",
|
||||
requiresLlm: true,
|
||||
groups: [
|
||||
{
|
||||
title: "Pedidos a Contestar",
|
||||
questions: [
|
||||
{
|
||||
id: "pedidos_principais",
|
||||
text: "Quais os principais pedidos do reclamante?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Horas extras",
|
||||
"Verbas rescisórias",
|
||||
"Dano moral",
|
||||
"Vínculo empregatício",
|
||||
"Equiparação salarial",
|
||||
"Adicional de insalubridade",
|
||||
"Desvio de função",
|
||||
"Outro",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "valor_causa",
|
||||
text: "Qual o valor da causa?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Até R$ 20 mil",
|
||||
"R$ 20 a 50 mil",
|
||||
"R$ 50 a 100 mil",
|
||||
"Acima de R$ 100 mil",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Defesa",
|
||||
questions: [
|
||||
{
|
||||
id: "teses_defesa",
|
||||
text: "Quais teses de defesa serão utilizadas?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Prescrição",
|
||||
"Ausência de provas",
|
||||
"Acordo coletivo válido",
|
||||
"Justa causa comprovada",
|
||||
"Inexistência de vínculo",
|
||||
"Pagamento correto",
|
||||
"Culpa do reclamante",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "documentos_empresa",
|
||||
text: "Quais documentos a empresa possui?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Controle de ponto",
|
||||
"Contracheques",
|
||||
"Contrato de trabalho",
|
||||
"TRCT",
|
||||
"Acordo coletivo",
|
||||
"Regulamento interno",
|
||||
"Nenhum",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
promptTemplate: `Elabore uma contestação trabalhista com os seguintes dados:
|
||||
|
||||
**Processo:**
|
||||
- Número: {{numero_processo}}
|
||||
- Vara/Tribunal: {{vara_tribunal}}
|
||||
- Tipo de ação: {{tipo_acao}}
|
||||
|
||||
**Reclamada:**
|
||||
- Porte: {{porte_empresa}}
|
||||
- Segmento: {{segmento}}
|
||||
|
||||
**Pedidos do reclamante a contestar:**
|
||||
- Pedidos principais: {{pedidos_principais}}
|
||||
- Valor da causa: {{valor_causa}}
|
||||
|
||||
**Teses de defesa:**
|
||||
- Teses: {{teses_defesa}}
|
||||
- Documentos disponíveis: {{documentos_empresa}}
|
||||
|
||||
{{#refinement_context}}
|
||||
|
||||
**Fundamente com base em:**
|
||||
- Art. 818 da CLT (ônus da prova)
|
||||
- Art. 373 do CPC (distribuição do ônus da prova)
|
||||
- Art. 769 da CLT (aplicação subsidiária do CPC)
|
||||
|
||||
Inclua preliminares pertinentes, conteste cada pedido individualmente com fundamentos fáticos e jurídicos, e formule pedidos finais de improcedência total dos pedidos do reclamante, com condenação em honorários advocatícios sucumbenciais.`,
|
||||
|
||||
legalReferences: [
|
||||
"art. 818 CLT",
|
||||
"art. 373 CPC",
|
||||
"art. 769 CLT",
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
import type { FlowConfig } from "../types.js";
|
||||
|
||||
export const danoMoralFlow: FlowConfig = {
|
||||
area: "trabalhista",
|
||||
areaLabel: "Trabalhista",
|
||||
subtipo: "dano-moral",
|
||||
subtipoLabel: "Dano Moral",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
steps: [
|
||||
{
|
||||
stepNumber: 1,
|
||||
title: "Dados do Vínculo",
|
||||
requiresLlm: false,
|
||||
groups: [
|
||||
{
|
||||
title: "Relação de Trabalho",
|
||||
questions: [
|
||||
{
|
||||
id: "empregador_tipo",
|
||||
text: "O empregador é pessoa jurídica ou física?",
|
||||
type: "select",
|
||||
options: ["Pessoa Jurídica (empresa)", "Pessoa Física"],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "regime",
|
||||
text: "Qual o regime de trabalho?",
|
||||
type: "select",
|
||||
options: ["CLT", "PJ", "Autônomo", "Temporário"],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "cargo",
|
||||
text: "Qual o cargo exercido?",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "tempo_servico",
|
||||
text: "Quanto tempo de serviço?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Menos de 1 ano",
|
||||
"1 a 3 anos",
|
||||
"3 a 5 anos",
|
||||
"Mais de 5 anos",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Remuneração",
|
||||
questions: [
|
||||
{
|
||||
id: "faixa_salarial",
|
||||
text: "Qual a faixa salarial?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Até 2 SM",
|
||||
"2 a 5 SM",
|
||||
"5 a 10 SM",
|
||||
"Acima de 10 SM",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
title: "Detalhes do Dano",
|
||||
requiresLlm: true,
|
||||
groups: [
|
||||
{
|
||||
title: "Caracterização do Dano",
|
||||
questions: [
|
||||
{
|
||||
id: "tipo_dano",
|
||||
text: "Qual o tipo de dano sofrido?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Assédio moral",
|
||||
"Assédio sexual",
|
||||
"Discriminação",
|
||||
"Acidente de trabalho",
|
||||
"Exposição indevida",
|
||||
"Revista íntima",
|
||||
"Outro",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "frequencia",
|
||||
text: "Qual a frequência das ocorrências?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Episódio único",
|
||||
"Recorrente",
|
||||
"Sistemático",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "periodo_ocorrencias",
|
||||
text: "Por quanto tempo ocorreram os fatos?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Menos de 3 meses",
|
||||
"3 a 6 meses",
|
||||
"6 a 12 meses",
|
||||
"Mais de 1 ano",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Impacto e Provas",
|
||||
questions: [
|
||||
{
|
||||
id: "consequencias",
|
||||
text: "Quais consequências o dano causou?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Afastamento médico",
|
||||
"Tratamento psicológico",
|
||||
"Perda salarial",
|
||||
"Danos à reputação",
|
||||
"Nenhuma consequência formal",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "provas_disponiveis",
|
||||
text: "Quais provas estão disponíveis?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Mensagens",
|
||||
"E-mails",
|
||||
"Testemunhas",
|
||||
"Laudos médicos",
|
||||
"Câmeras",
|
||||
"Documentos",
|
||||
"Nenhuma",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "registrou_ocorrencia",
|
||||
text: "Registrou a ocorrência formalmente?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Sim, BO",
|
||||
"Sim, RH ou ouvidoria",
|
||||
"Não",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
promptTemplate: `Elabore uma petição inicial trabalhista de indenização por dano moral com os seguintes dados:
|
||||
|
||||
**Partes:**
|
||||
- Reclamante: [a ser preenchido pelo advogado]
|
||||
- Reclamada: {{empregador_tipo}}
|
||||
|
||||
**Vínculo empregatício:**
|
||||
- Regime: {{regime}}
|
||||
- Cargo: {{cargo}}
|
||||
- Tempo de serviço: {{tempo_servico}}
|
||||
- Faixa salarial: {{faixa_salarial}}
|
||||
|
||||
**Caracterização do dano:**
|
||||
- Tipo de dano: {{tipo_dano}}
|
||||
- Frequência: {{frequencia}}
|
||||
- Período das ocorrências: {{periodo_ocorrencias}}
|
||||
|
||||
**Impacto e provas:**
|
||||
- Consequências: {{consequencias}}
|
||||
- Provas disponíveis: {{provas_disponiveis}}
|
||||
- Registro de ocorrência: {{registrou_ocorrencia}}
|
||||
|
||||
{{#refinement_context}}
|
||||
|
||||
**Fundamente com base em:**
|
||||
- Arts. 223-A a 223-G da CLT (dano extrapatrimonial nas relações de trabalho)
|
||||
- Art. 186 do Código Civil (ato ilícito)
|
||||
- Art. 927 do Código Civil (obrigação de reparar o dano)
|
||||
|
||||
Inclua pedidos de: indenização por dano moral com arbitramento judicial do valor, considerando a gravidade da ofensa, a condição econômica das partes e o caráter pedagógico, além de honorários advocatícios.`,
|
||||
|
||||
legalReferences: [
|
||||
"art. 223-A a 223-G CLT",
|
||||
"art. 186 CC",
|
||||
"art. 927 CC",
|
||||
],
|
||||
};
|
||||
|
|
@ -3,9 +3,9 @@ export const trabalhistaArea = {
|
|||
label: "Trabalhista",
|
||||
subtipos: [
|
||||
{ value: "horas-extras", label: "Horas Extras" },
|
||||
{ value: "rescisao-indireta", label: "Rescisão Indireta", available: false },
|
||||
{ value: "dano-moral", label: "Dano Moral", available: false },
|
||||
{ value: "acumulo-funcao", label: "Acúmulo de Função", available: false },
|
||||
{ value: "contestacao", label: "Contestação Trabalhista", available: false },
|
||||
{ value: "rescisao-indireta", label: "Rescisão Indireta" },
|
||||
{ value: "dano-moral", label: "Dano Moral" },
|
||||
{ value: "acumulo-funcao", label: "Acúmulo de Função" },
|
||||
{ value: "contestacao", label: "Contestação Trabalhista" },
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,192 @@
|
|||
import type { FlowConfig } from "../types.js";
|
||||
|
||||
export const rescisaoIndiretaFlow: FlowConfig = {
|
||||
area: "trabalhista",
|
||||
areaLabel: "Trabalhista",
|
||||
subtipo: "rescisao-indireta",
|
||||
subtipoLabel: "Rescisão Indireta",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
steps: [
|
||||
{
|
||||
stepNumber: 1,
|
||||
title: "Dados do Vínculo",
|
||||
requiresLlm: false,
|
||||
groups: [
|
||||
{
|
||||
title: "Relação de Trabalho",
|
||||
questions: [
|
||||
{
|
||||
id: "regime",
|
||||
text: "Qual o regime de trabalho?",
|
||||
type: "select",
|
||||
options: ["CLT", "PJ", "Autônomo", "Temporário"],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "cargo",
|
||||
text: "Qual o cargo exercido?",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "tempo_servico",
|
||||
text: "Quanto tempo de serviço?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Menos de 1 ano",
|
||||
"1 a 3 anos",
|
||||
"3 a 5 anos",
|
||||
"Mais de 5 anos",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Remuneração",
|
||||
questions: [
|
||||
{
|
||||
id: "salario_tipo",
|
||||
text: "Qual o tipo de salário?",
|
||||
type: "select",
|
||||
options: ["Fixo", "Comissão", "Misto"],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "faixa_salarial",
|
||||
text: "Qual a faixa salarial?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Até 2 SM",
|
||||
"2 a 5 SM",
|
||||
"5 a 10 SM",
|
||||
"Acima de 10 SM",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
title: "Motivos da Rescisão",
|
||||
requiresLlm: true,
|
||||
groups: [
|
||||
{
|
||||
title: "Falta Grave do Empregador",
|
||||
questions: [
|
||||
{
|
||||
id: "motivo_principal",
|
||||
text: "Qual o motivo principal da rescisão indireta?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Atraso reiterado de salários",
|
||||
"Não recolhimento de FGTS",
|
||||
"Assédio moral",
|
||||
"Desvio de função",
|
||||
"Redução salarial",
|
||||
"Condições de trabalho inadequadas",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "motivos_adicionais",
|
||||
text: "Há motivos adicionais?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Atraso reiterado de salários",
|
||||
"Não recolhimento de FGTS",
|
||||
"Assédio moral",
|
||||
"Desvio de função",
|
||||
"Redução salarial",
|
||||
"Condições de trabalho inadequadas",
|
||||
],
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: "inicio_problemas",
|
||||
text: "Há quanto tempo os problemas começaram?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Menos de 3 meses",
|
||||
"3 a 6 meses",
|
||||
"6 a 12 meses",
|
||||
"Mais de 1 ano",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Evidências",
|
||||
questions: [
|
||||
{
|
||||
id: "provas_disponiveis",
|
||||
text: "Quais provas estão disponíveis?",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
"Contracheques",
|
||||
"Extratos FGTS",
|
||||
"Mensagens",
|
||||
"Testemunhas",
|
||||
"E-mails",
|
||||
"Fotos ou vídeos",
|
||||
"Documentos médicos",
|
||||
"Nenhuma",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "comunicou_empregador",
|
||||
text: "Comunicou o empregador sobre os problemas?",
|
||||
type: "select",
|
||||
options: [
|
||||
"Sim, formalmente",
|
||||
"Sim, verbalmente",
|
||||
"Não",
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
promptTemplate: `Elabore uma petição inicial trabalhista de rescisão indireta do contrato de trabalho com os seguintes dados:
|
||||
|
||||
**Partes:**
|
||||
- Reclamante: [a ser preenchido pelo advogado]
|
||||
- Reclamada: [a ser preenchido pelo advogado]
|
||||
|
||||
**Vínculo empregatício:**
|
||||
- Regime: {{regime}}
|
||||
- Cargo: {{cargo}}
|
||||
- Tempo de serviço: {{tempo_servico}}
|
||||
- Tipo de salário: {{salario_tipo}}
|
||||
- Faixa salarial: {{faixa_salarial}}
|
||||
|
||||
**Motivos da rescisão indireta:**
|
||||
- Motivo principal: {{motivo_principal}}
|
||||
- Motivos adicionais: {{motivos_adicionais}}
|
||||
- Início dos problemas: {{inicio_problemas}}
|
||||
|
||||
**Evidências:**
|
||||
- Provas disponíveis: {{provas_disponiveis}}
|
||||
- Comunicação ao empregador: {{comunicou_empregador}}
|
||||
|
||||
{{#refinement_context}}
|
||||
|
||||
**Fundamente com base em:**
|
||||
- Art. 483 da CLT (hipóteses de rescisão indireta)
|
||||
- Art. 487 da CLT (aviso prévio)
|
||||
- Súmula 13 do TST (mora salarial)
|
||||
|
||||
Inclua pedidos de: reconhecimento da rescisão indireta, pagamento de verbas rescisórias como dispensa sem justa causa (saldo de salário, aviso prévio, 13º proporcional, férias + 1/3, FGTS + 40%), guias para seguro-desemprego, e honorários advocatícios.`,
|
||||
|
||||
legalReferences: [
|
||||
"art. 483 CLT",
|
||||
"art. 487 CLT",
|
||||
"Súmula 13 TST",
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { calculateTotalSteps, getVisualStep, isStepComplete } from "../flow-engine.js";
|
||||
import type { FlowConfig, FlowStep } from "../../flows/types.js";
|
||||
|
||||
const mockFlow: FlowConfig = {
|
||||
area: "trabalhista",
|
||||
areaLabel: "Trabalhista",
|
||||
subtipo: "horas-extras",
|
||||
subtipoLabel: "Horas Extras",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
steps: [
|
||||
{ stepNumber: 1, title: "Step 1", groups: [], requiresLlm: false },
|
||||
{ stepNumber: 2, title: "Step 2", groups: [], requiresLlm: true },
|
||||
],
|
||||
promptTemplate: "",
|
||||
legalReferences: [],
|
||||
};
|
||||
|
||||
describe("calculateTotalSteps", () => {
|
||||
it("includes selection screens when no deep link", () => {
|
||||
expect(calculateTotalSteps(mockFlow, false)).toBe(5); // 2 selection + 2 flow + 1 preview
|
||||
});
|
||||
|
||||
it("excludes selection screens with deep link", () => {
|
||||
expect(calculateTotalSteps(mockFlow, true)).toBe(3); // 0 selection + 2 flow + 1 preview
|
||||
});
|
||||
});
|
||||
|
||||
describe("getVisualStep", () => {
|
||||
it("adds offset for non-deep-link", () => {
|
||||
expect(getVisualStep(mockFlow, 1, false)).toBe(3); // offset 2 + step 1
|
||||
});
|
||||
|
||||
it("no offset for deep link", () => {
|
||||
expect(getVisualStep(mockFlow, 1, true)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isStepComplete", () => {
|
||||
const step: FlowStep = {
|
||||
stepNumber: 1,
|
||||
title: "Test",
|
||||
requiresLlm: false,
|
||||
groups: [
|
||||
{
|
||||
title: "Group 1",
|
||||
questions: [
|
||||
{ id: "q1", text: "Question 1", type: "select", options: ["A", "B"], required: true },
|
||||
{ id: "q2", text: "Question 2", type: "text", required: false },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
it("returns true when all required questions answered", () => {
|
||||
expect(isStepComplete(step, { q1: "A" })).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when required question missing", () => {
|
||||
expect(isStepComplete(step, {})).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false when required answer is empty", () => {
|
||||
expect(isStepComplete(step, { q1: "" })).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true when optional question missing", () => {
|
||||
expect(isStepComplete(step, { q1: "A" })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { getRefinementQuestions } from "../llm-client.js";
|
||||
import type { FlowState } from "../../flows/types.js";
|
||||
|
||||
const mockState: FlowState = {
|
||||
area: "trabalhista",
|
||||
subtipo: "horas-extras",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
currentStep: 1,
|
||||
totalSteps: 5,
|
||||
responses: { regime: "CLT" },
|
||||
};
|
||||
|
||||
describe("getRefinementQuestions", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns empty array when no API key configured", async () => {
|
||||
const result = await getRefinementQuestions(mockState, "Trabalhista", "Horas Extras");
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns empty array on fetch failure (graceful fallback)", async () => {
|
||||
// Even if somehow config had a key, network failure should return []
|
||||
const result = await getRefinementQuestions(mockState, "Trabalhista", "Horas Extras");
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { buildPrompt } from "../prompt-builder.js";
|
||||
import type { FlowConfig, FlowState } from "../../flows/types.js";
|
||||
|
||||
const mockFlow: FlowConfig = {
|
||||
area: "trabalhista",
|
||||
areaLabel: "Trabalhista",
|
||||
subtipo: "horas-extras",
|
||||
subtipoLabel: "Horas Extras",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
steps: [],
|
||||
promptTemplate: "Regime: {{regime}}, Jornada: {{jornada_contratual}}, Extra: {{horas_extras_semana}}",
|
||||
legalReferences: ["art. 59 CLT"],
|
||||
};
|
||||
|
||||
const mockState: FlowState = {
|
||||
area: "trabalhista",
|
||||
subtipo: "horas-extras",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
currentStep: 2,
|
||||
totalSteps: 5,
|
||||
responses: {
|
||||
regime: "CLT",
|
||||
jornada_contratual: "44h semanais",
|
||||
horas_extras_semana: "5 a 10 horas",
|
||||
},
|
||||
};
|
||||
|
||||
describe("buildPrompt", () => {
|
||||
it("interpolates template variables", () => {
|
||||
const result = buildPrompt(mockFlow, mockState);
|
||||
expect(result.text).toContain("CLT");
|
||||
expect(result.text).toContain("44h semanais");
|
||||
expect(result.text).toContain("5 a 10 horas");
|
||||
expect(result.text).not.toContain("{{");
|
||||
});
|
||||
|
||||
it("returns legal references", () => {
|
||||
const result = buildPrompt(mockFlow, mockState);
|
||||
expect(result.legalReferences).toEqual(["art. 59 CLT"]);
|
||||
});
|
||||
|
||||
it("calculates char count", () => {
|
||||
const result = buildPrompt(mockFlow, mockState);
|
||||
expect(result.charCount).toBe(result.text.length);
|
||||
});
|
||||
|
||||
it("determines fitsInUrl correctly for short prompt", () => {
|
||||
const result = buildPrompt(mockFlow, mockState);
|
||||
expect(result.fitsInUrl).toBe(true);
|
||||
expect(result.encodedUrl).toBeDefined();
|
||||
});
|
||||
|
||||
it("determines fitsInUrl correctly for long prompt", () => {
|
||||
const longFlow = {
|
||||
...mockFlow,
|
||||
promptTemplate: "A".repeat(2000),
|
||||
};
|
||||
const result = buildPrompt(longFlow, mockState);
|
||||
expect(result.fitsInUrl).toBe(false);
|
||||
expect(result.encodedUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it("removes unreplaced variables", () => {
|
||||
const flowWithExtra = {
|
||||
...mockFlow,
|
||||
promptTemplate: "{{regime}} - {{campo_inexistente}}",
|
||||
};
|
||||
const result = buildPrompt(flowWithExtra, mockState);
|
||||
expect(result.text).not.toContain("{{campo_inexistente}}");
|
||||
expect(result.text).toContain("CLT");
|
||||
});
|
||||
|
||||
it("handles array responses by joining with comma", () => {
|
||||
const stateWithArray = {
|
||||
...mockState,
|
||||
responses: { ...mockState.responses, provas: ["Doc1", "Doc2"] },
|
||||
};
|
||||
const flowWithArray = {
|
||||
...mockFlow,
|
||||
promptTemplate: "Provas: {{provas}}",
|
||||
};
|
||||
const result = buildPrompt(flowWithArray, stateWithArray);
|
||||
expect(result.text).toContain("Doc1, Doc2");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { buildRedirectUrl, getJusIaDirectUrl } from "../url-builder.js";
|
||||
|
||||
describe("buildRedirectUrl", () => {
|
||||
it("builds correct URL with encoded prompt", () => {
|
||||
const url = buildRedirectUrl("teste prompt");
|
||||
expect(url).toContain("https://ia.jusbrasil.com.br/conversa?q=");
|
||||
expect(url).toContain("teste%20prompt");
|
||||
expect(url.endsWith("&send")).toBe(true);
|
||||
});
|
||||
|
||||
it("encodes special characters", () => {
|
||||
const url = buildRedirectUrl("art. 59 da CLT (limite)");
|
||||
expect(url).toContain(encodeURIComponent("art. 59 da CLT (limite)"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("getJusIaDirectUrl", () => {
|
||||
it("returns base Jus IA URL", () => {
|
||||
expect(getJusIaDirectUrl()).toBe("https://ia.jusbrasil.com.br/conversa");
|
||||
});
|
||||
});
|
||||
|
|
@ -67,13 +67,26 @@ Gere perguntas de refinamento para capturar nuances deste caso.`;
|
|||
signal: controller.signal,
|
||||
});
|
||||
} else {
|
||||
// Default: OpenAI-compatible
|
||||
response = await fetch("https://api.openai.com/v1/chat/completions", {
|
||||
// OpenRouter (default) and OpenAI use the same OpenAI-compatible API
|
||||
const baseUrl =
|
||||
config.llm.provider === "openrouter"
|
||||
? config.llm.baseUrl
|
||||
: "https://api.openai.com/v1";
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${config.llm.apiKey}`,
|
||||
};
|
||||
|
||||
// OpenRouter-specific headers
|
||||
if (config.llm.provider === "openrouter") {
|
||||
headers["HTTP-Referer"] = "https://jus-ia-start-kit.app";
|
||||
headers["X-Title"] = "Jus IA Start Kit";
|
||||
}
|
||||
|
||||
response = await fetch(`${baseUrl}/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${config.llm.apiKey}`,
|
||||
},
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
model: config.llm.model,
|
||||
messages: [
|
||||
|
|
@ -99,6 +112,7 @@ Gere perguntas de refinamento para capturar nuances deste caso.`;
|
|||
if (config.llm.provider === "anthropic") {
|
||||
content = data.content?.[0]?.text || "{}";
|
||||
} else {
|
||||
// OpenRouter and OpenAI both use the same response format
|
||||
content = data.choices?.[0]?.message?.content || "{}";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { parseFlowState, serializeFlowState } from "../parse-flow-state.js";
|
||||
|
||||
describe("parseFlowState", () => {
|
||||
it("parses basic form body", () => {
|
||||
const state = parseFlowState({
|
||||
_area: "trabalhista",
|
||||
_subtipo: "horas-extras",
|
||||
_tipo_tarefa: "peticao-inicial",
|
||||
_step: "1",
|
||||
_total_steps: "5",
|
||||
_responses: "{}",
|
||||
regime: "CLT",
|
||||
});
|
||||
expect(state.area).toBe("trabalhista");
|
||||
expect(state.subtipo).toBe("horas-extras");
|
||||
expect(state.currentStep).toBe(1);
|
||||
expect(state.responses.regime).toBe("CLT");
|
||||
});
|
||||
|
||||
it("merges existing responses with new ones", () => {
|
||||
const state = parseFlowState({
|
||||
_area: "trabalhista",
|
||||
_subtipo: "horas-extras",
|
||||
_step: "2",
|
||||
_total_steps: "5",
|
||||
_responses: JSON.stringify({ regime: "CLT", jornada: "44h" }),
|
||||
testemunhas: "Sim",
|
||||
});
|
||||
expect(state.responses.regime).toBe("CLT");
|
||||
expect(state.responses.jornada).toBe("44h");
|
||||
expect(state.responses.testemunhas).toBe("Sim");
|
||||
});
|
||||
|
||||
it("handles invalid JSON in _responses", () => {
|
||||
const state = parseFlowState({
|
||||
_area: "civel",
|
||||
_subtipo: "cobranca",
|
||||
_step: "1",
|
||||
_total_steps: "4",
|
||||
_responses: "invalid-json",
|
||||
campo: "valor",
|
||||
});
|
||||
expect(state.responses.campo).toBe("valor");
|
||||
});
|
||||
|
||||
it("handles empty body gracefully", () => {
|
||||
const state = parseFlowState({});
|
||||
expect(state.area).toBe("");
|
||||
expect(state.currentStep).toBe(1);
|
||||
expect(Object.keys(state.responses)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("sanitizes input values", () => {
|
||||
const state = parseFlowState({
|
||||
_area: "trabalhista",
|
||||
_subtipo: "test",
|
||||
_step: "1",
|
||||
_total_steps: "3",
|
||||
_responses: "{}",
|
||||
campo: "<script>alert('xss')</script>Hello",
|
||||
});
|
||||
expect(state.responses.campo).toBe("alert('xss')Hello");
|
||||
});
|
||||
|
||||
it("handles array values", () => {
|
||||
const state = parseFlowState({
|
||||
_area: "trabalhista",
|
||||
_subtipo: "test",
|
||||
_step: "1",
|
||||
_total_steps: "3",
|
||||
_responses: "{}",
|
||||
provas: ["Doc1", "Doc2"],
|
||||
});
|
||||
expect(state.responses.provas).toEqual(["Doc1", "Doc2"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("serializeFlowState", () => {
|
||||
it("serializes responses to JSON", () => {
|
||||
const json = serializeFlowState({
|
||||
area: "trabalhista",
|
||||
subtipo: "horas-extras",
|
||||
tipoTarefa: "peticao-inicial",
|
||||
currentStep: 1,
|
||||
totalSteps: 5,
|
||||
responses: { regime: "CLT", jornada: "44h" },
|
||||
});
|
||||
const parsed = JSON.parse(json);
|
||||
expect(parsed.regime).toBe("CLT");
|
||||
expect(parsed.jornada).toBe("44h");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { sanitizeText, validateSelect } from "../sanitize.js";
|
||||
|
||||
describe("sanitizeText", () => {
|
||||
it("strips HTML tags", () => {
|
||||
expect(sanitizeText("<b>bold</b> text")).toBe("bold text");
|
||||
});
|
||||
it("trims whitespace", () => {
|
||||
expect(sanitizeText(" hello ")).toBe("hello");
|
||||
});
|
||||
it("caps at MAX_INPUT_LENGTH", () => {
|
||||
const long = "a".repeat(600);
|
||||
expect(sanitizeText(long).length).toBe(500);
|
||||
});
|
||||
it("handles empty string", () => {
|
||||
expect(sanitizeText("")).toBe("");
|
||||
});
|
||||
it("passes through clean text", () => {
|
||||
expect(sanitizeText("Texto limpo")).toBe("Texto limpo");
|
||||
});
|
||||
it("strips nested HTML", () => {
|
||||
expect(sanitizeText("<div><p>test</p></div>")).toBe("test");
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateSelect", () => {
|
||||
const options = ["CLT", "PJ", "Autônomo"];
|
||||
it("returns true for valid option", () => {
|
||||
expect(validateSelect("CLT", options)).toBe(true);
|
||||
});
|
||||
it("returns false for invalid option", () => {
|
||||
expect(validateSelect("Outro", options)).toBe(false);
|
||||
});
|
||||
it("returns false for empty string", () => {
|
||||
expect(validateSelect("", options)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "node",
|
||||
},
|
||||
});
|
||||
Loading…
Reference in New Issue