feat: version 4.0.1 -- Nuevo con IA: aviso de proyectos parecidos y objetivo redactado
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,8 @@ import { HttpError } from '../../utils/http.js';
|
||||
import { askStructured } from '../../utils/ai.js';
|
||||
import { logAudit } from '../../utils/audit.js';
|
||||
import { getAiConfig } from '../settings/service.js';
|
||||
import { getProject, canWriteProject, createProject } from '../projects/service.js';
|
||||
import { getProject, canWriteProject, createProject, FULL_ACCESS_ROLES } from '../projects/service.js';
|
||||
import { keywords, isSimilar } from '../../utils/similarity.js';
|
||||
import { createActivity } from '../activities/service.js';
|
||||
import { createTask } from '../tasks/service.js';
|
||||
|
||||
@@ -193,8 +194,10 @@ export async function proposeNewProject({ objective, startDate, lang }, user) {
|
||||
const plan = cleanPlan(result.activities);
|
||||
if (!plan.length) throw new HttpError(422, 'No se pudo generar un plan con tareas para ese objetivo. Intenta describirlo con mas detalle.');
|
||||
const type = types.find((t) => t.name === result.projectType);
|
||||
const name = clip(result.name, 80) || goal.slice(0, 80);
|
||||
return {
|
||||
name: clip(result.name, 80) || goal.slice(0, 80),
|
||||
similarProjects: await findSimilarProjects(`${goal} ${name}`, user),
|
||||
name,
|
||||
// El texto del usuario suele ser corto: la IA lo redacta como objetivo y deja el alcance en la descripcion.
|
||||
objective: clip(result.objective, 500) || goal,
|
||||
description: clip(result.description, 1000),
|
||||
@@ -240,3 +243,21 @@ export async function createNewProject(payload, user) {
|
||||
await logAudit({ userId: user.id, action: 'ai_create_project', entityType: 'project', entityId: project.id });
|
||||
return { id: project.id, code: project.code };
|
||||
}
|
||||
|
||||
// Proyectos visibles para el usuario (no en papelera) con nombre/objetivo parecido. Solo SQL.
|
||||
async function findSimilarProjects(text, user) {
|
||||
const target = keywords(text);
|
||||
if (target.size < 2) return [];
|
||||
const fullAccess = FULL_ACCESS_ROLES.includes(user.roleCode);
|
||||
const [rows] = await pool.query(
|
||||
`SELECT p.id, p.code, p.name, p.objective, ps.name AS status_name
|
||||
FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
||||
WHERE p.deleted_at IS NULL
|
||||
${fullAccess ? '' : 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)'}`,
|
||||
fullAccess ? [] : [user.id]
|
||||
);
|
||||
return rows
|
||||
.filter((r) => isSimilar(target, keywords(`${r.name} ${r.objective || ''}`)))
|
||||
.slice(0, 5)
|
||||
.map((r) => ({ id: r.id, code: r.code, name: r.name, statusName: r.status_name }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user