Sistema de prompts dinámicos: prompts editables desde Settings + tabla Dexie + fallback a defaults

- prompts-db.ts: CRUD de prompts con 5 tipos (transcription, project_gap, session, qa, chat)
- db.ts: schema v9 con tabla prompts (&key)
- App.vue: initPrompts() al arrancar la app
- Servicios AI modificados: usan getPrompt(key) con fallback a default hardcodeado
- AiProjectChat.vue: prompt con {{variables}} reemplazadas por datos reales
- SettingsView: editor de prompts con textarea + guardar/restaurar
This commit is contained in:
2026-05-29 14:41:40 -05:00
parent b45caee583
commit 9909f16229
9 changed files with 328 additions and 141 deletions
+3 -27
View File
@@ -1,5 +1,6 @@
import { PROVIDER_CONFIG, getProviderApiKey, type AIProvider } from '@/stores/settings'
import { storage } from '@/services/storage'
import { getPrompt } from '@/services/prompts-db'
export interface AIExtractedHU {
title: string
@@ -134,32 +135,6 @@ export async function chatWithAI(
return callAI(msgs, 0.7, 2048, signal)
}
const SYSTEM_PROMPT = `Eres un analista funcional experto en metodologías ágiles. Tu tarea es analizar transcripciones de reuniones y extraer Historias de Usuario (HUs) en formato estructurado.
Reglas:
1. Identifica cada requisito, funcionalidad, bug o mejora mencionada en la transcripción
2. Convierte cada uno en una HU con: título claro, descripción detallada, criterios de aceptación
3. Los criterios de aceptación deben ser verificables (condiciones específicas)
4. Usa el formato "Como [rol] quiero [funcionalidad] para [beneficio]" cuando sea posible
5. Asigna prioridad (Alta/Media/Baja) basada en urgencia implícita
6. No inventes información que no esté en la transcripción
7. Si el texto no contiene información relevante para HUs, devuelve un arreglo vacío
Responde SOLO con JSON válido en este formato:
{
"hus": [
{
"title": "Título de la HU",
"description": "Descripción detallada",
"acceptance_criteria": ["Criterio 1", "Criterio 2"],
"priority": "Alta|Media|Baja",
"story_points": 3,
"type": "feature|bug|task|improvement"
}
],
"summary": "Resumen breve del análisis (2-3 líneas)"
}`
export async function analyzeTranscription(
text: string,
projectName?: string,
@@ -171,9 +146,10 @@ export async function analyzeTranscription(
console.log(`[Alpha] AI analyze — text: ${text.length} chars`)
const systemPrompt = await getPrompt('analysis_transcription')
const content = await callAI(
[
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userContent },
],
0.3,
+10 -1
View File
@@ -128,6 +128,13 @@ export interface DexieUserStory {
created_at: string | null
}
export interface PromptRecord {
key: string
content: string
label: string
updatedAt: string
}
const db = new Dexie('alpha-core') as Dexie & {
settings: Dexie.Table<SettingEntry, string>
project_docs: Dexie.Table<ProjectDocRecord, number>
@@ -141,9 +148,10 @@ const db = new Dexie('alpha-core') as Dexie & {
cells: Dexie.Table<CellRecord, string>
cell_members: Dexie.Table<CellMemberRecord, string>
user_stories: Dexie.Table<DexieUserStory, number>
prompts: Dexie.Table<PromptRecord, string>
}
db.version(8).stores({
db.version(9).stores({
settings: '&key',
project_docs: '&projectId, projectName, updatedAt',
sessions: '++id, projectId, date',
@@ -156,6 +164,7 @@ db.version(8).stores({
cells: '&id',
cell_members: '[cellId+userId], cellId, userId',
user_stories: '&id, initiative_id',
prompts: '&key',
})
export default db
+3 -35
View File
@@ -1,4 +1,5 @@
import { callAI } from '@/services/ai'
import { getPrompt } from '@/services/prompts-db'
import { getSessionsByProject, getSessionSummary, getProjectState } from '@/services/transcriptions-db'
import { saveDraft, createDraftId } from '@/services/hu-drafts-db'
import { generateAndSavePlan } from '@/services/qa-analyzer'
@@ -25,40 +26,6 @@ interface AnalysisResult {
summary: string
}
const ANALYSIS_SYSTEM_PROMPT = `Eres un analista funcional experto. Tu tarea es analizar TODO el contexto de un proyecto y generar las Épicas e Historias de Usuario (HUs) que faltan.
Reglas:
1. Analizá TODA la información disponible: sesiones, resúmenes, estado del proyecto, HUs existentes
2. Identificá requisitos, funcionalidades, mejoras o bugs que NO estén cubiertos
3. Agrupá HUs relacionadas en Épicas. Cada épica agrupa funcionalidades de un mismo tema
4. Cada HU debe tener: título claro, descripción, criterios de aceptación verificables
5. No generes duplicados. Compará con la lista existente
6. Priorizá según urgencia implícita (Alta/Media/Baja)
7. Si todo ya está cubierto, devolvé arreglos vacíos
8. Respondé SOLO con JSON válido
Formato de respuesta:
{
"epics": [
{
"name": "Nombre de la Épica",
"description": "Descripción de la épica",
"linkedHuTitles": ["Título HU 1", "Título HU 2"],
"estimatedStart": "YYYY-MM-DD",
"estimatedEnd": "YYYY-MM-DD"
}
],
"hus": [
{
"title": "Título de la HU",
"description": "Descripción detallada",
"acceptance_criteria": ["Criterio 1", "Criterio 2"],
"priority": "Alta|Media|Baja"
}
],
"summary": "Resumen del análisis"
}`
export async function analyzeProject(
projectId: number,
projectName: string,
@@ -96,8 +63,9 @@ export async function analyzeProject(
console.log(`[Alpha] Project analysis — ${projectId}, ${existingHUs.length} HUs existentes, ${sessions.length} sesiones`)
const systemPrompt = await getPrompt('project_gap')
const content = await callAI(
[{ role: 'system', content: ANALYSIS_SYSTEM_PROMPT }, { role: 'user', content: userContent }],
[{ role: 'system', content: systemPrompt }, { role: 'user', content: userContent }],
0.3, 8192, signal,
)
+213
View File
@@ -0,0 +1,213 @@
import db from '@/services/db'
export type PromptKey = 'analysis_transcription' | 'project_gap' | 'session' | 'qa' | 'chat'
export interface PromptRecord {
key: PromptKey
content: string
label: string
updatedAt: string
}
const DEFAULT_PROMPTS: Record<PromptKey, { label: string; content: string }> = {
analysis_transcription: {
label: 'Análisis de transcripciones',
content: `Eres un analista funcional experto en metodologías ágiles. Tu tarea es analizar transcripciones de reuniones y extraer Historias de Usuario (HUs) en formato estructurado.
Reglas:
1. Identifica cada requisito, funcionalidad, bug o mejora mencionada en la transcripción
2. Convierte cada uno en una HU con: título claro, descripción detallada, criterios de aceptación
3. Los criterios de aceptación deben ser verificables (condiciones específicas)
4. Usa el formato "Como [rol] quiero [funcionalidad] para [beneficio]" cuando sea posible
5. Asigna prioridad (Alta/Media/Baja) basada en urgencia implícita
6. No inventes información que no esté en la transcripción
7. Si el texto no contiene información relevante para HUs, devuelve un arreglo vacío
Responde SOLO con JSON válido en este formato:
{
"hus": [
{
"title": "Título de la HU",
"description": "Descripción detallada",
"acceptance_criteria": ["Criterio 1", "Criterio 2"],
"priority": "Alta|Media|Baja",
"story_points": 3,
"type": "feature|bug|task|improvement"
}
],
"summary": "Resumen breve del análisis (2-3 líneas)"
}`,
},
project_gap: {
label: 'Análisis de brechas del proyecto',
content: `Eres un analista funcional experto. Tu tarea es analizar TODO el contexto de un proyecto y generar las Épicas e Historias de Usuario (HUs) que faltan.
Reglas:
1. Analizá TODA la información disponible: sesiones, resúmenes, estado del proyecto, HUs existentes
2. Identificá requisitos, funcionalidades, mejoras o bugs que NO estén cubiertos
3. Agrupá HUs relacionadas en Épicas. Cada épica agrupa funcionalidades de un mismo tema
4. Cada HU debe tener: título claro, descripción, criterios de aceptación verificables
5. No generes duplicados. Compará con la lista existente
6. Priorizá según urgencia implícita (Alta/Media/Baja)
7. Si todo ya está cubierto, devolvé arreglos vacíos
8. Respondé SOLO con JSON válido
Formato de respuesta:
{
"epics": [
{
"name": "Nombre de la Épica",
"description": "Descripción de la épica",
"linkedHuTitles": ["Título HU 1", "Título HU 2"],
"estimatedStart": "YYYY-MM-DD",
"estimatedEnd": "YYYY-MM-DD"
}
],
"hus": [
{
"title": "Título de la HU",
"description": "Descripción detallada",
"acceptance_criteria": ["Criterio 1", "Criterio 2"],
"priority": "Alta|Media|Baja"
}
],
"summary": "Resumen del análisis"
}`,
},
session: {
label: 'Análisis de sesiones',
content: `Eres un asistente de gestión de proyectos. Analizás transcripciones de reuniones y extraés información estructurada.
Reglas:
1. Identificá la fecha de la sesión (si no está explícita, usá la fecha actual)
2. Identificá el título de la sesión basado en el contenido
3. Extraé un resumen ejecutivo de 2-3 oraciones
4. Listá objetivos mencionados, marcando cuáles son NUEVOS vs existentes
5. Extraé tareas pendientes con su origen y prioridad (Alta/Media/Baja)
6. Identificá compromisos con responsable, fecha límite y estado
7. Listá decisiones tomadas durante la sesión
8. Detectá tareas completadas (si hay evidencia)
9. Incluí puntos clave, bloqueos o descubrimientos
10. No inventes información que no esté en la transcripción
11. Respondé SOLO con JSON válido
Formato de respuesta JSON:
{
"sessionDate": "YYYY-MM-DD",
"sessionTitle": "Título descriptivo de la sesión",
"summary": "Resumen ejecutivo de 2-3 oraciones",
"objectives": [
{ "text": "Descripción del objetivo", "isNew": true }
],
"pendingTasks": [
{ "description": "Descripción de la tarea", "origin": "Sesión o contexto", "priority": "Alta|Media|Baja" }
],
"commitments": [
{ "description": "Compromiso", "responsible": "Nombre", "dueDate": "YYYY-MM-DD", "status": "Pendiente|Cumplido|Vencido" }
],
"decisions": ["Decisión 1", "Decisión 2"],
"completedTasks": ["Tarea completada 1"],
"keyPoints": ["Punto clave 1"]
}`,
},
qa: {
label: 'Generación de planes QA',
content: `Eres un ingeniero de QA experto. Generá un plan de pruebas detallado para una Historia de Usuario.
Formato de respuesta JSON:
{
"acceptanceCriteria": ["Criterio 1", "Criterio 2"],
"testCases": [
{
"type": "Tipo de prueba",
"description": "Descripción de lo que verifica",
"automatizable": "SÍ|PARCIAL|MANUAL",
"tool": "Playwright|API Testing|Manual"
}
],
"manualSteps": ["Paso manual 1"],
"criticalTests": ["Prueba crítica manual"]
}
Reglas:
- SÍ = completamente automatizable
- PARCIAL = requiere validación manual complementaria
- MANUAL = requiere intervención humana
- Incluí al menos 3-5 casos de prueba
- Marcá como crítica pruebas con datos reales, ERPs externos, o cálculos financieros`,
},
chat: {
label: 'Chat del proyecto',
content: `Eres un asistente de gestión de proyectos ágiles para el equipo de desarrollo.
Datos del proyecto:
- Nombre: {{projectName}}
- Descripción: {{projectDescription}}
- Épicas: {{epicCount}}
- HUs pendientes: {{huCount}}
Respondé en el mismo idioma del usuario (español o inglés).
Usá la data del proyecto para dar respuestas contextualizadas.
Si te preguntan por información que no está en el contexto, decí que no tenés esa información.`,
},
}
const cache = new Map<PromptKey, string>()
export async function initPrompts(): Promise<void> {
for (const [key, def] of Object.entries(DEFAULT_PROMPTS)) {
const existing = await db.prompts.get(key as PromptKey)
if (!existing) {
await db.prompts.put({
key: key as PromptKey,
content: def.content,
label: def.label,
updatedAt: new Date().toISOString(),
})
}
}
}
export async function getPrompt(key: PromptKey): Promise<string> {
if (cache.has(key)) return cache.get(key)!
const record = await db.prompts.get(key)
if (record) {
cache.set(key, record.content)
return record.content
}
const def = DEFAULT_PROMPTS[key]
if (def) {
cache.set(key, def.content)
return def.content
}
return ''
}
export async function savePrompt(key: PromptKey, content: string): Promise<void> {
const def = DEFAULT_PROMPTS[key]
await db.prompts.put({
key,
content,
label: def?.label ?? key,
updatedAt: new Date().toISOString(),
})
cache.set(key, content)
}
export async function resetPrompt(key: PromptKey): Promise<void> {
const def = DEFAULT_PROMPTS[key]
if (def) {
await savePrompt(key, def.content)
}
}
export function getAllPromptKeys(): { key: PromptKey; label: string }[] {
return Object.entries(DEFAULT_PROMPTS).map(([key, def]) => ({
key: key as PromptKey,
label: def.label,
}))
}
export function getDefaultPrompt(key: PromptKey): string {
return DEFAULT_PROMPTS[key]?.content ?? ''
}
+3 -25
View File
@@ -1,4 +1,5 @@
import { callAI } from '@/services/ai'
import { getPrompt } from '@/services/prompts-db'
import db from '@/services/db'
export interface QATestCase {
@@ -18,34 +19,11 @@ export interface HUQAPlan {
criticalTests: string[]
}
const QA_PROMPT = `Eres un ingeniero de QA experto. Generá un plan de pruebas detallado para una Historia de Usuario.
Formato de respuesta JSON:
{
"acceptanceCriteria": ["Criterio 1", "Criterio 2"],
"testCases": [
{
"type": "Tipo de prueba",
"description": "Descripción de lo que verifica",
"automatizable": "SÍ|PARCIAL|MANUAL",
"tool": "Playwright|API Testing|Manual"
}
],
"manualSteps": ["Paso manual 1"],
"criticalTests": ["Prueba crítica manual"]
}
Reglas:
- SÍ = completamente automatizable
- PARCIAL = requiere validación manual complementaria
- MANUAL = requiere intervención humana
- Incluí al menos 3-5 casos de prueba
- Marcá como crítica pruebas con datos reales, ERPs externos, o cálculos financieros`
export async function generateQAPlan(huTitle: string, huDescription: string, acceptanceCriteria: string): Promise<HUQAPlan> {
const userContent = `HU: ${huTitle}\nDescripción: ${huDescription}\nCriterios: ${acceptanceCriteria}`
const systemPrompt = await getPrompt('qa')
const content = await callAI(
[{ role: 'system', content: QA_PROMPT }, { role: 'user', content: userContent }],
[{ role: 'system', content: systemPrompt }, { role: 'user', content: userContent }],
0.3, 4096,
)
try {
+3 -35
View File
@@ -1,4 +1,5 @@
import { callAI } from '@/services/ai'
import { getPrompt } from '@/services/prompts-db'
export interface SessionExtraction {
sessionDate: string // YYYY-MM-DD
@@ -12,40 +13,6 @@ export interface SessionExtraction {
keyPoints: string[]
}
const SESSION_SYSTEM_PROMPT = `Eres un asistente de gestión de proyectos. Analizás transcripciones de reuniones y extraés información estructurada.
Reglas:
1. Identificá la fecha de la sesión (si no está explícita, usá la fecha actual)
2. Identificá el título de la sesión basado en el contenido
3. Extraé un resumen ejecutivo de 2-3 oraciones
4. Listá objetivos mencionados, marcando cuáles son NUEVOS vs existentes
5. Extraé tareas pendientes con su origen y prioridad (Alta/Media/Baja)
6. Identificá compromisos con responsable, fecha límite y estado
7. Listá decisiones tomadas durante la sesión
8. Detectá tareas completadas (si hay evidencia)
9. Incluí puntos clave, bloqueos o descubrimientos
10. No inventes información que no esté en la transcripción
11. Respondé SOLO con JSON válido
Formato de respuesta JSON:
{
"sessionDate": "YYYY-MM-DD",
"sessionTitle": "Título descriptivo de la sesión",
"summary": "Resumen ejecutivo de 2-3 oraciones",
"objectives": [
{ "text": "Descripción del objetivo", "isNew": true }
],
"pendingTasks": [
{ "description": "Descripción de la tarea", "origin": "Sesión o contexto", "priority": "Alta|Media|Baja" }
],
"commitments": [
{ "description": "Compromiso", "responsible": "Nombre", "dueDate": "YYYY-MM-DD", "status": "Pendiente|Cumplido|Vencido" }
],
"decisions": ["Decisión 1", "Decisión 2"],
"completedTasks": ["Tarea completada 1"],
"keyPoints": ["Punto clave 1"]
}`
export async function analyzeSession(
transcription: string,
projectName: string,
@@ -55,9 +22,10 @@ export async function analyzeSession(
console.log(`[Alpha] Session analyze — project: ${projectName}, text: ${transcription.length} chars`)
const systemPrompt = await getPrompt('session')
const content = await callAI(
[
{ role: 'system', content: SESSION_SYSTEM_PROMPT },
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userContent },
],
0.3,