AiProjectChat: contexto de equipo + SP/sprint para que la IA asigne HUs
- buildContext() ahora incluye team members del proyecto (nombres, roles, célula) desde storage per-project - HUs incluyen story_points (sp) y sprint para que la IA pueda balancear cargas de trabajo - Épicas incluyen assigned_name y story_points - System prompt instruye a la IA a: · Usar el equipo para asignar HUs · Detectar sobrecarga por sprint · Sugerir recursos transversales cuando aplique · Alertar sobre riesgo de cronograma
This commit is contained in:
@@ -2,8 +2,10 @@
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { chatWithAI } from '@/services/ai'
|
import { chatWithAI } from '@/services/ai'
|
||||||
|
import { storage } from '@/services/storage'
|
||||||
import { useSettingsStore, AVAILABLE_MODELS, PROVIDER_CONFIG, hasProviderApiKey, type AIProvider } from '@/stores/settings'
|
import { useSettingsStore, AVAILABLE_MODELS, PROVIDER_CONFIG, hasProviderApiKey, type AIProvider } from '@/stores/settings'
|
||||||
import { useWorkItemsStore } from '@/stores/workitems'
|
import { useWorkItemsStore } from '@/stores/workitems'
|
||||||
|
import { useUsersStore } from '@/stores/users'
|
||||||
import { getSessionsByProject, getProjectState } from '@/services/transcriptions-db'
|
import { getSessionsByProject, getProjectState } from '@/services/transcriptions-db'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@@ -21,6 +23,7 @@ import { Send, Loader2, AlertCircle, Brain, Settings2, Check, ChevronDown } from
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const settings = useSettingsStore()
|
const settings = useSettingsStore()
|
||||||
const workItems = useWorkItemsStore()
|
const workItems = useWorkItemsStore()
|
||||||
|
const usersStore = useUsersStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
projectId: { type: Number, required: true },
|
projectId: { type: Number, required: true },
|
||||||
@@ -53,20 +56,55 @@ function switchModel(provider: AIProvider, modelId: string) {
|
|||||||
async function buildContext(): Promise<string> {
|
async function buildContext(): Promise<string> {
|
||||||
const ctx: Record<string, unknown> = {}
|
const ctx: Record<string, unknown> = {}
|
||||||
|
|
||||||
// 1. HUs: todas, formato compacto (sin límite)
|
// 1. Team members asignados a este proyecto
|
||||||
|
const teamIds = storage.getJSON<number[]>(`project_team_${props.projectId}`)
|
||||||
|
if (teamIds && teamIds.length > 0) {
|
||||||
|
const members = teamIds
|
||||||
|
.map(id => usersStore.users.find(u => u.id === id))
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(u => ({
|
||||||
|
n: u!.full_name || u!.email,
|
||||||
|
r: u!.role || '—',
|
||||||
|
c: u!.cell || '—',
|
||||||
|
}))
|
||||||
|
ctx.team = { count: members.length, members }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. HUs: todas, con story points, sprint y estado
|
||||||
const hus = workItems.userStories
|
const hus = workItems.userStories
|
||||||
if (hus.length > 0) {
|
if (hus.length > 0) {
|
||||||
const pending = hus.filter(h => !['done','completed','closed','finalizado'].includes(String(h.status ?? '').toLowerCase()))
|
const pending = hus.filter(h => !['done','completed','closed','finalizado'].includes(String(h.status ?? '').toLowerCase()))
|
||||||
ctx.hus = { total: hus.length, pending: pending.length, items: hus.map(h => ({ t: h._cleanTitle || h.title, s: h.status, p: h.priority })) }
|
ctx.hus = {
|
||||||
|
total: hus.length,
|
||||||
|
pending: pending.length,
|
||||||
|
items: hus.map(h => ({
|
||||||
|
t: h._cleanTitle || h.title,
|
||||||
|
s: h.status,
|
||||||
|
p: h.priority,
|
||||||
|
sp: h.story_points ?? null,
|
||||||
|
sprint: h.sprint ?? null,
|
||||||
|
})),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Sesiones: últimas 3
|
// 3. Épicas con asignación
|
||||||
|
const epics = workItems.epics
|
||||||
|
if (epics.length > 0) {
|
||||||
|
ctx.epics = epics.map(e => ({
|
||||||
|
n: e._cleanName || e.name || e.title,
|
||||||
|
s: e.status,
|
||||||
|
assigned: e.assigned_name || null,
|
||||||
|
sp: e.story_points ?? null,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Sesiones: últimas 3
|
||||||
const sessions = await getSessionsByProject(props.projectId)
|
const sessions = await getSessionsByProject(props.projectId)
|
||||||
if (sessions.length > 0) {
|
if (sessions.length > 0) {
|
||||||
ctx.sessions = { total: sessions.length, recent: sessions.slice(-3).reverse().map(s => ({ d: s.date, t: s.title })) }
|
ctx.sessions = { total: sessions.length, recent: sessions.slice(-3).reverse().map(s => ({ d: s.date, t: s.title })) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Estado
|
// 5. Estado
|
||||||
const state = await getProjectState(props.projectId)
|
const state = await getProjectState(props.projectId)
|
||||||
if (state?.summary) ctx.summary = state.summary.slice(0, 300)
|
if (state?.summary) ctx.summary = state.summary.slice(0, 300)
|
||||||
|
|
||||||
@@ -84,9 +122,22 @@ async function sendPrompt() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const context = await buildContext()
|
const context = await buildContext()
|
||||||
const systemPrompt = `Proyecto: ${props.projectName}. ${props.projectDescription || ''} Épicas: ${props.epicCount} HUs: ${props.huCount}.
|
const systemPrompt = `Eres un asistente de gestión de proyectos ágiles para el equipo de desarrollo.
|
||||||
Datos del proyecto (JSON): ${context || '{}'}
|
|
||||||
Respondé en el mismo idioma del usuario. Sé conciso. Si no sabés, decilo.`
|
Proyecto: ${props.projectName}. ${props.projectDescription || ''} Épicas: ${props.epicCount} HUs: ${props.huCount}.
|
||||||
|
|
||||||
|
Tienes acceso a estos datos del proyecto (JSON):
|
||||||
|
${context || '{}'}
|
||||||
|
|
||||||
|
El campo "team" contiene los miembros del equipo asignados a este proyecto con su rol. Úsalo para responder quién trabaja en qué, sugerir asignaciones de HUs, y alertar sobre capacidad.
|
||||||
|
|
||||||
|
Cada HU puede tener "sp" (story points) y "sprint". Usa esta información para:
|
||||||
|
- Recomendar asignaciones equilibradas según capacidad del equipo
|
||||||
|
- Detectar sobrecarga de trabajo en un sprint
|
||||||
|
- Sugerir recursos adicionales si una HU requiere expertise que no está en el equipo (célula transversal)
|
||||||
|
- Alertar cuando el cronograma está en riesgo por falta de capacidad
|
||||||
|
|
||||||
|
Respondé en el mismo idioma del usuario. Sé conciso y práctico. Si algo no está en los datos, decilo.`
|
||||||
|
|
||||||
const msgs = [{ role: 'user' as const, content: text }]
|
const msgs = [{ role: 'user' as const, content: text }]
|
||||||
const result = await chatWithAI(msgs, systemPrompt)
|
const result = await chatWithAI(msgs, systemPrompt)
|
||||||
|
|||||||
Reference in New Issue
Block a user