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
+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 ?? ''
}