timezone: parseo UTC Teams + conversion Colombia + doc RUMBO
- services/timezone.ts: parseTeamsUTC(), toColombiaTime(), isTeamsFile() - TranscriptionsView: fecha prioriza UTC del filename sobre AI/hoy - TranscriptionsView: muestra conversion UTC → Colombia en resultados - rumbo/timezone.md: documentacion arquitectura horaria para RUMBO
This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Utilidades de huso horario para Alpha.
|
||||||
|
*
|
||||||
|
* Colombia: UTC-5 (sin horario de verano).
|
||||||
|
* Microsoft Teams transcripts incluyen UTC en el filename.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const COLOMBIA_OFFSET = -5 // UTC-5
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsea timestamp UTC desde filename de Microsoft Teams.
|
||||||
|
* Formato: "305 Equilibrium Logicas Dashboard Informes-20260525_185916UTC-Meeting Recording"
|
||||||
|
* ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
* Devuelve: { dateStr: "2026-05-25", timeStr: "13:59", utc: "18:59" }
|
||||||
|
*/
|
||||||
|
export function parseTeamsUTC(filename: string): { dateStr: string; timeStr: string; utc: string } | null {
|
||||||
|
// Busca patrón YYYYMMDD_HHMMSSUTC
|
||||||
|
const match = filename.match(/(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})UTC/)
|
||||||
|
if (!match) return null
|
||||||
|
|
||||||
|
const [, y, m, d, hh, mm] = match
|
||||||
|
|
||||||
|
// Crear fecha UTC
|
||||||
|
const utcDate = new Date(Date.UTC(Number(y), Number(m) - 1, Number(d), Number(hh), Number(mm)))
|
||||||
|
|
||||||
|
// Convertir a Colombia (UTC-5)
|
||||||
|
const colombia = new Date(utcDate.getTime() + COLOMBIA_OFFSET * 3600000)
|
||||||
|
|
||||||
|
const pad2 = (n: number) => String(n).padStart(2, '0')
|
||||||
|
|
||||||
|
const dateStr = `${colombia.getFullYear()}-${pad2(colombia.getMonth() + 1)}-${pad2(colombia.getDate())}`
|
||||||
|
const timeStr = `${pad2(colombia.getHours())}:${pad2(colombia.getMinutes())}`
|
||||||
|
const utcStr = `${hh}:${mm}`
|
||||||
|
|
||||||
|
return { dateStr, timeStr, utc: utcStr }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detecta si el filename es de Microsoft Teams.
|
||||||
|
*/
|
||||||
|
export function isTeamsFile(filename: string): boolean {
|
||||||
|
return /Meetings?\s*(Recording|Transcript)/i.test(filename) || /^\d{8}_\d{6}UTC/.test(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convierte una fecha UTC a string en hora Colombia.
|
||||||
|
*/
|
||||||
|
export function toColombiaTime(isoString: string): string {
|
||||||
|
const d = new Date(isoString)
|
||||||
|
const col = new Date(d.getTime() + COLOMBIA_OFFSET * 3600000)
|
||||||
|
const pad2 = (n: number) => String(n).padStart(2, '0')
|
||||||
|
return `${col.getFullYear()}-${pad2(col.getMonth() + 1)}-${pad2(col.getDate())} ${pad2(col.getHours())}:${pad2(col.getMinutes())}`
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import { analyzeSession, type SessionExtraction } from '@/services/session-analy
|
|||||||
import { generateMasterDoc } from '@/services/project-doc'
|
import { generateMasterDoc } from '@/services/project-doc'
|
||||||
import { parseFile } from '@/services/parse-transcription'
|
import { parseFile } from '@/services/parse-transcription'
|
||||||
import { saveSession, saveSessionSummary, saveProjectState, getSessionCount } from '@/services/transcriptions-db'
|
import { saveSession, saveSessionSummary, saveProjectState, getSessionCount } from '@/services/transcriptions-db'
|
||||||
|
import { parseTeamsUTC, toColombiaTime } from '@/services/timezone'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -68,6 +69,7 @@ const uploadError = ref<string | null>(null)
|
|||||||
const sessionLoading = ref(false)
|
const sessionLoading = ref(false)
|
||||||
const sessionResult = ref<SessionExtraction | null>(null)
|
const sessionResult = ref<SessionExtraction | null>(null)
|
||||||
const sessionError = ref<string | null>(null)
|
const sessionError = ref<string | null>(null)
|
||||||
|
const sessionTeamsInfo = ref<{ utc: string; timeStr: string } | null>(null)
|
||||||
const docGenerating = ref(false)
|
const docGenerating = ref(false)
|
||||||
const docGenerated = ref(false)
|
const docGenerated = ref(false)
|
||||||
|
|
||||||
@@ -275,11 +277,16 @@ async function analyzeAsSession() {
|
|||||||
selectedProject.value?.name || '',
|
selectedProject.value?.name || '',
|
||||||
)
|
)
|
||||||
|
|
||||||
// 1. Guardar transcripción en BD
|
// 1. Determinar fecha (prioridad: UTC del filename → AI → hoy)
|
||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
|
const teamsDate = parseTeamsUTC(parsedFileName.value)
|
||||||
|
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
|
||||||
const sessionId = await saveSession({
|
const sessionId = await saveSession({
|
||||||
projectId: selectedProjectId.value,
|
projectId: selectedProjectId.value,
|
||||||
date: result.sessionDate || now.slice(0, 10),
|
date: sessionDate,
|
||||||
title: result.sessionTitle,
|
title: result.sessionTitle,
|
||||||
fileName: parsedFileName.value,
|
fileName: parsedFileName.value,
|
||||||
fileType: parsedFileName.value.split('.').pop() || 'txt',
|
fileType: parsedFileName.value.split('.').pop() || 'txt',
|
||||||
@@ -706,6 +713,12 @@ function clearAll() {
|
|||||||
<ListChecks class="size-4" />
|
<ListChecks class="size-4" />
|
||||||
{{ sessionResult.sessionTitle }}
|
{{ sessionResult.sessionTitle }}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
|
<div class="flex items-center gap-2 mt-1">
|
||||||
|
<span class="text-xs text-muted-foreground">{{ sessionResult.sessionDate || '' }}</span>
|
||||||
|
<span v-if="sessionTeamsInfo" class="text-xs text-muted-foreground/60">
|
||||||
|
· UTC {{ sessionTeamsInfo.utc }} → Colombia {{ sessionTeamsInfo.timeStr }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<Button size="sm" @click="generateDoc()" :disabled="docGenerating">
|
<Button size="sm" @click="generateDoc()" :disabled="docGenerating">
|
||||||
<Loader2 v-if="docGenerating" class="size-4 mr-1 animate-spin" />
|
<Loader2 v-if="docGenerating" class="size-4 mr-1 animate-spin" />
|
||||||
<CheckCircle2 v-else class="size-4 mr-1" />
|
<CheckCircle2 v-else class="size-4 mr-1" />
|
||||||
|
|||||||
Reference in New Issue
Block a user