hu_drafts en Dexie + push individual a KAPPA + project-analyzer

- db.ts v4: tabla hu_drafts (id, projectId, title, description, syncStatus)
- hu-drafts-db.ts: CRUD Dexie para drafts
- project-analyzer.ts: saveAsDrafts() guarda en BD local, no crea en KAPPA
- DashboardView: borradores desde Dexie con boton Enviar individual
- pushDraft: endpoint /api/userstorys/create/ con payload exacto
- pushDraft: elimina draft solo si KAPPA responde ok
This commit is contained in:
2026-05-28 14:35:02 -05:00
parent eb4fae78b3
commit 63804a2cb6
4 changed files with 186 additions and 54 deletions
+23 -31
View File
@@ -1,9 +1,9 @@
import { callAI } from '@/services/ai'
import { getSessionsByProject, getSessionSummary, getProjectState } from '@/services/transcriptions-db'
import { kappa } from '@/services/kappa-api'
import { saveDraft, createDraftId } from '@/services/hu-drafts-db'
import type { EnrichedUserStory } from '@/stores/workitems'
interface AnalysisHU {
export interface AnalysisHU {
title: string
description: string
acceptance_criteria: string[]
@@ -109,51 +109,43 @@ export async function analyzeProject(
}
/**
* Crea las HUs propuestas en KAPPA (solo las que no existen).
* Devuelve cuántas se crearon.
* Guarda las HUs propuestas como borradores en la BD local.
* No crea nada en KAPPA — el usuario revisa y envía desde HuDrafts.
*/
export async function createMissingHUs(
export async function saveAsDrafts(
projectId: number,
analysis: AnalysisResult,
existingHUs: EnrichedUserStory[],
): Promise<{ created: number; skipped: number; errors: string[] }> {
let created = 0
sourceSessionId?: number,
): Promise<{ saved: number; skipped: number }> {
let saved = 0
let skipped = 0
const errors: string[] = []
const existingTitles = new Set(existingHUs.map(h => (h._cleanTitle || h.title).toLowerCase().trim()))
for (const hu of analysis.hus) {
const normalizedTitle = hu.title.toLowerCase().trim()
// Verificar duplicado por título similar
const isDuplicate = existingHUs.some(ex => {
const et = (ex._cleanTitle || ex.title).toLowerCase().trim()
return et === normalizedTitle || et.includes(normalizedTitle) || normalizedTitle.includes(et)
})
if (isDuplicate) {
skipped++
continue
}
if (isDuplicate) { skipped++; continue }
try {
await kappa.createUserStory({
title: hu.title,
description: hu.description + (hu.acceptance_criteria.length > 0
? `\n\n**Criterios de aceptación:**\n${hu.acceptance_criteria.map(c => `- ${c}`).join('\n')}`
: ''),
initiative: projectId,
priority: hu.priority || 'Media',
status: 'todo',
})
created++
} catch (e: any) {
errors.push(`${hu.title}: ${e.message}`)
}
await saveDraft({
id: createDraftId(),
projectId,
title: hu.title,
description: hu.description,
acceptanceCriteria: hu.acceptance_criteria.join('\n'),
priority: hu.priority,
type: 'U',
sourceSessionId,
syncStatus: 'draft',
createdAt: new Date().toISOString(),
})
saved++
}
return { created, skipped, errors }
return { saved, skipped }
}
function extractJSON(text: string): string {