import { z } from "zod"; import { NextResponse } from "next/server"; import { requireAdminApiUser } from "@/lib/auth/admin"; import { callOpenAiJsonSchema } from "@/lib/ai/openai"; import { storeAiSuggestionFromEnvelope } from "@/lib/ai/suggestions"; import { buildInstitutionalDossier, getAuditSimulationDetail, getLatestInstitutionalDossierSnapshot, listAuditSimulationsForUser } from "@/lib/audits/server"; const M10_PROMPT_VERSION = "m10_findings_v1"; const M10FindingsSchema = z.object({ auditorLikelyFindings: z .array( z.object({ area: z.string().min(4).max(120), finding: z.string().min(8).max(500), severity: z.enum(["alto", "medio", "bajo"]), }), ) .max(20), missingEvidence: z.array(z.string().min(6).max(280)).max(20), topRisks: z.array(z.string().min(6).max(280)).max(12), remediationPlan: z .array( z.object({ step: z.string().min(8).max(420), priority: z.enum(["alta", "media", "baja"]), ownerSuggestion: z.string().min(3).max(120), targetDate: z.string().min(4).max(40), }), ) .max(20), confidence: z.enum(["low", "medium", "high"]), }); const M10FindingsJsonSchema = { type: "object", additionalProperties: false, required: ["auditorLikelyFindings", "missingEvidence", "topRisks", "remediationPlan", "confidence"], properties: { auditorLikelyFindings: { type: "array", items: { type: "object", additionalProperties: false, required: ["area", "finding", "severity"], properties: { area: { type: "string" }, finding: { type: "string" }, severity: { type: "string", enum: ["alto", "medio", "bajo"] }, }, }, }, missingEvidence: { type: "array", items: { type: "string" }, }, topRisks: { type: "array", items: { type: "string" }, }, remediationPlan: { type: "array", items: { type: "object", additionalProperties: false, required: ["step", "priority", "ownerSuggestion", "targetDate"], properties: { step: { type: "string" }, priority: { type: "string", enum: ["alta", "media", "baja"] }, ownerSuggestion: { type: "string" }, targetDate: { type: "string" }, }, }, }, confidence: { type: "string", enum: ["low", "medium", "high"] }, }, } as const; function parseString(value: unknown) { return typeof value === "string" ? value.trim() : ""; } async function resolveSimulation(userId: string, simulationId: string) { if (simulationId) { return getAuditSimulationDetail(userId, simulationId); } const simulations = await listAuditSimulationsForUser(userId); return simulations.find((simulation) => simulation.status === "COMPLETED") ?? simulations[0] ?? null; } export async function POST(request: Request) { const user = await requireAdminApiUser(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const body = (await request.json().catch(() => ({}))) as Record; const simulationId = parseString(body.simulationId); const simulation = await resolveSimulation(user.id, simulationId); if (!simulation) { return NextResponse.json({ error: "No hay simulaciones disponibles para generar dictamen IA." }, { status: 400 }); } const latestDossier = await getLatestInstitutionalDossierSnapshot(user.id); const dossier = latestDossier?.payload ?? (await buildInstitutionalDossier(user.id)); const condensedContext = { simulation: { id: simulation.id, name: simulation.name, auditType: simulation.auditType, status: simulation.status, overallScore: simulation.overallScore, completedAt: simulation.completedAt, sections: simulation.sections, }, dossier: { generatedAt: dossier.generatedAt, summary: dossier.summary, components: dossier.components, }, }; const systemPrompt = [ "Eres auditor preventivo especializado en contratacion publica y cumplimiento documental en Mexico.", "Simula hallazgos probables de una revision formal sin modificar resultados deterministas existentes.", "Debes responder solo JSON valido en espanol.", ].join(" "); const userPrompt = [ "Contexto actual de simulacion y expediente institucional:", JSON.stringify(condensedContext), "", "Genera:", "- auditorLikelyFindings: hallazgos probables por area y severidad.", "- missingEvidence: evidencia concreta faltante.", "- topRisks: riesgos principales priorizados.", "- remediationPlan: plan de remediacion con prioridad, responsable sugerido y fecha objetivo.", ].join("\n"); const envelope = await callOpenAiJsonSchema({ promptVersion: M10_PROMPT_VERSION, systemPrompt, userPrompt, outputSchema: M10FindingsSchema, schemaName: "m10_audit_findings", jsonSchema: M10FindingsJsonSchema as unknown as Record, model: process.env.OPENAI_M10_MODEL?.trim() || undefined, }); const payload = envelope.data ?? ({ auditorLikelyFindings: [], missingEvidence: [], topRisks: [], remediationPlan: [], confidence: envelope.confidence ?? "low", } satisfies z.infer); const persisted = await storeAiSuggestionFromEnvelope({ userId: user.id, moduleKey: "M10", featureKey: "audit_findings", subjectType: "audit_simulation", subjectId: simulation.id, inputForHash: condensedContext, envelope, responsePayload: payload, }); return NextResponse.json({ ok: true, simulationId: simulation.id, ...payload, suggestionId: persisted.suggestionId, meta: { engine: envelope.engine, model: envelope.model, usage: envelope.usage, warnings: envelope.warnings, confidence: envelope.confidence, }, }); }