From 8cec93b90a4eac95c96381a093a9cf6f9ad9d976 Mon Sep 17 00:00:00 2001 From: Ricardo Gonzalez Date: Thu, 28 May 2026 14:02:35 -0500 Subject: [PATCH] dedup sesiones + calendario desde BD + scroll por fecha - analyzeAsSession: detecta duplicados por UTC timestamp antes de guardar - TranscriptionsView: sessionDatesList reemplaza sessionOffsets - session dates se leen directo de tabla sessions (no desde markdown) - scrollToSession busca por fecha en markdown con regex escapada - calendario usa BD como fuente de verdad --- src/views/TranscriptionsView.vue | 88 ++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/src/views/TranscriptionsView.vue b/src/views/TranscriptionsView.vue index d54bc66..a3e46b5 100644 --- a/src/views/TranscriptionsView.vue +++ b/src/views/TranscriptionsView.vue @@ -14,7 +14,7 @@ import { import { analyzeSession, type SessionExtraction } from '@/services/session-analyzer' import { generateMasterDoc } from '@/services/project-doc' import { parseFile } from '@/services/parse-transcription' -import { saveSession, saveSessionSummary, saveProjectState, getSessionCount } from '@/services/transcriptions-db' +import { saveSession, saveSessionSummary, saveProjectState, getSessionCount, getSessionsByProject } from '@/services/transcriptions-db' import { parseTeamsUTC, toColombiaTime } from '@/services/timezone' import { Card, @@ -77,7 +77,7 @@ const docGenerated = ref(false) const docMarkdown = ref('') const docSessionCount = ref(0) const docUpdatedAt = ref('') -const sessionOffsets = ref<{ date: string; title: string; offset: number }[]>([]) +const sessionDatesList = ref<{ date: string; title: string; fileName: string }[]>([]) const viewerRef = ref(null) // ─── Calendar state ────────────────────────────────────── @@ -96,45 +96,43 @@ const selectedProject = computed(() => watch(selectedProjectId, async (id) => { if (!id) return clearAll() - const count = await getSessionCount(id) + await loadSessionDates(id) + const count = sessionDatesList.value.length docSessionCount.value = count if (count > 0) { const md = await generateMasterDoc(id, selectedProject.value?.name || '') docMarkdown.value = md - parseSessionOffsets(md) } }, { immediate: false }) -function parseSessionOffsets(md: string) { - const offsets: { date: string; title: string; offset: number }[] = [] - const re = /## 📍 Sesión \d+:\s*(.+?)\n\*\*Archivo fuente:/g - let match - while ((match = re.exec(md)) !== null) { - const title = match[1].trim() - const dateMatch = title.match(/(\d{4}-\d{2}-\d{2})/) - offsets.push({ - date: dateMatch?.[1] || '', - title, - offset: match.index, - }) - } - sessionOffsets.value = offsets +async function loadSessionDates(projectId: number) { + const sessions = await getSessionsByProject(projectId) + sessionDatesList.value = sessions.map(s => ({ + date: s.date, + title: s.title, + fileName: s.fileName, + })) } -function scrollToSession(offset: number) { - if (viewerRef.value) { - const textarea = viewerRef.value - const md = docMarkdown.value - // Calculate line number from offset - const lineNum = md.slice(0, offset).split('\n').length - const lineHeight = 20 // approximate - textarea.scrollTop = (lineNum - 5) * lineHeight - textarea.focus() - } +function scrollToSession(date: string) { + if (!viewerRef.value || !docMarkdown.value) return + const md = docMarkdown.value + // Buscar la sesión por fecha en el markdown: ## 📍 Sesión: YYYY-MM-DD + const re = new RegExp(`## 📍 Sesión: ${escapeRegex(date)}`) + const match = re.exec(md) + if (!match) return + const lineNum = md.slice(0, match.index).split('\n').length + const lineHeight = 20 + viewerRef.value.scrollTop = (lineNum - 5) * lineHeight + viewerRef.value.focus() +} + +function escapeRegex(s: string) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } function sessionDates(): string[] { - return sessionOffsets.value.map(s => s.date).filter(Boolean) + return sessionDatesList.value.map(s => s.date).filter(Boolean) } // ─── Calendar helpers ──────────────────────────────────── @@ -149,14 +147,13 @@ function firstDayOfMonth(m: number, y: number) { } function isSessionDate(d: number) { - const ds = sessionDates() const dateStr = `${calYear.value}-${String(calMonth.value + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}` - return ds.includes(dateStr) + return sessionDatesList.value.some(s => s.date === dateStr) } function getSessionForDate(d: number) { const dateStr = `${calYear.value}-${String(calMonth.value + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}` - return sessionOffsets.value.find(s => s.date === dateStr) + return sessionDatesList.value.find(s => s.date === dateStr) || null } function prevMonth() { @@ -283,7 +280,20 @@ async function analyzeAsSession() { sessionTeamsInfo.value = teamsDate ? { utc: teamsDate.utc, timeStr: teamsDate.timeStr } : null const sessionDate = teamsDate?.dateStr || result.sessionDate || now.slice(0, 10) - // 1b. Guardar transcripción en BD + // 1b. Deduplicación: verificar si ya existe una sesión con el mismo UTC timestamp + const existing = await getSessionsByProject(selectedProjectId.value) + const isDuplicate = existing.some(s => { + // Comparar por UTC timestamp en filename o por fecha + título similar + const existingUTC = parseTeamsUTC(s.fileName) + return teamsDate !== null && existingUTC !== null && teamsDate.utc === existingUTC.utc + }) + if (isDuplicate) { + sessionError.value = `Esta sesión ya fue registrada (UTC ${teamsDate?.utc}). Se descarta.` + sessionLoading.value = false + return + } + + // 1c. Guardar transcripción en BD const sessionId = await saveSession({ projectId: selectedProjectId.value, date: sessionDate, @@ -313,9 +323,9 @@ async function analyzeAsSession() { // 4. Regenerar documento MD const md = await generateMasterDoc(selectedProjectId.value, selectedProject.value?.name || '') docMarkdown.value = md - docSessionCount.value = await getSessionCount(selectedProjectId.value) + await loadSessionDates(selectedProjectId.value) + docSessionCount.value = sessionDatesList.value.length docUpdatedAt.value = now.slice(0, 16).replace('T', ' ') - parseSessionOffsets(md) docGenerated.value = true // 5. Mostrar resultado en UI @@ -391,9 +401,9 @@ async function generateDoc() { try { const md = await generateMasterDoc(selectedProjectId.value, selectedProject.value?.name || '') docMarkdown.value = md - docSessionCount.value = await getSessionCount(selectedProjectId.value) + await loadSessionDates(selectedProjectId.value) + docSessionCount.value = sessionDatesList.value.length docUpdatedAt.value = new Date().toISOString().slice(0, 16).replace('T', ' ') - parseSessionOffsets(md) docGenerated.value = true } catch (e: any) { sessionError.value = e.message @@ -667,7 +677,7 @@ function clearAll() { -
+
{{ t('transcriptions.noSessions') }}