From c8228b315d200c6f728347b28e9730b2592e4d8f Mon Sep 17 00:00:00 2001 From: Ricardo Gonzalez Date: Fri, 29 May 2026 03:15:33 -0500 Subject: [PATCH] =?UTF-8?q?Fix:=20mapeo=20employee=5Fid=E2=86=92user=5Fid?= =?UTF-8?q?=20+=20status=5Fname=20de=20KAPPA=20+=20filtro=20asignado=20sol?= =?UTF-8?q?o=20desarrolladores=20asignados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - KappaUserStory: status_name, status/priority aceptan number - parseAssignedUser: separa employee_id (asignado_a) de user_id (assigned_to) employee_id se guarda negativo en Turso para distinguirlo - enrichHU: usa status_name de KAPPA directo, fallback a resolveStatusName - DashboardView: resolveEmployeeToUser() busca employee→user via employees store - assignedName: muestra nombre real desde employee→user, fallback asignado_a_names - Filtros: placeholder Estado/Prioridad/Asignado en vez de Todos - assignedUsers: filtro solo muestra desarrolladores asignados a HUs del proyecto --- src/services/statuses-db.ts | 12 ++++++ src/stores/workitems.ts | 39 +++++++++++------ src/types/kappa.ts | 5 ++- src/views/DashboardView.vue | 86 ++++++++++++++++++++++++------------- 4 files changed, 97 insertions(+), 45 deletions(-) diff --git a/src/services/statuses-db.ts b/src/services/statuses-db.ts index 8dda985..68b5497 100644 --- a/src/services/statuses-db.ts +++ b/src/services/statuses-db.ts @@ -17,8 +17,11 @@ const STATUSES: StatusDef[] = [ ] const STATUS_MAP: Record = { + '1': 1, 'todo': 1, 'por hacer': 1, + 'not started': 1, + '2': 2, 'in_progress': 2, 'doing': 2, 'wip': 2, @@ -26,16 +29,25 @@ const STATUS_MAP: Record = { 'in progress': 2, 'en progreso': 2, 'true': 2, + '3': 3, 'review': 3, 'revisión': 3, + 'qa-ready': 3, + '4': 4, 'testing': 4, 'pruebas': 4, + 'qa-testing': 4, + '5': 5, 'done': 5, 'completed': 5, 'closed': 5, 'finalizado': 5, + '6': 6, 'blocked': 6, 'bloqueado': 6, + 'ready to deploy': 6, + 'qa-client': 6, + '7': 7, 'cancelled': 7, 'cancelado': 7, 'false': 1, diff --git a/src/stores/workitems.ts b/src/stores/workitems.ts index e8cafaa..dcf573f 100644 --- a/src/stores/workitems.ts +++ b/src/stores/workitems.ts @@ -18,6 +18,7 @@ export interface EnrichedUserStory extends KappaUserStory { _assignedUserId: number | null _assignedName: string _statusName: string + _assignedEmployeeId: number | null } export interface EnrichedEpic extends KappaEpicDevelopment { @@ -63,29 +64,36 @@ export const useWorkItemsStore = defineStore('workitems', () => { } } - function parseAssignedUser(hu: any): { id: number | null; name: string } { - // Intenta múltiples formatos que KAPPA puede devolver - if (hu.assigned_to != null && hu.assigned_to !== '') { - const id = Number(hu.assigned_to) - if (!isNaN(id)) return { id, name: hu.assigned_name || '' } - } + function parseAssignedUser(hu: any): { id: number | null; name: string; employeeId: number | null } { + // 1. asignado_a → EMPLOYEE IDs (ej: [1135]). NO son user_ids. if (hu.asignado_a != null) { if (Array.isArray(hu.asignado_a)) { const first = hu.asignado_a[0] if (first != null) { - const id = typeof first === 'object' ? Number(first.id) : Number(first) - if (!isNaN(id)) return { id, name: typeof first === 'object' ? first.name || first.full_name || '' : '' } + const employeeId = typeof first === 'object' ? Number(first.id) : Number(first) + if (!isNaN(employeeId)) { + const name = Array.isArray(hu.asignado_a_names) ? (hu.asignado_a_names[0] ?? '') : (hu.asignado_a_names ?? '') + return { id: null, name, employeeId } + } } } } - return { id: null, name: '' } + // 2. assigned_to con valor negativo → employee_id (convención Turso) + if (hu.assigned_to != null && hu.assigned_to !== '') { + const raw = Number(hu.assigned_to) + if (!isNaN(raw)) { + if (raw < 0) return { id: null, name: '', employeeId: Math.abs(raw) } + return { id: raw, name: hu.assigned_name || '', employeeId: null } + } + } + return { id: null, name: '', employeeId: null } } - function enrichHU(hu: { title?: string; id?: number; status?: string | null; acceptance_criteria?: string | null; criterios_aceptacion?: string | null; assigned_to?: number | null; asignado_a?: number[] | string[] | null; assigned_name?: string }, initiativeId: number): EnrichedUserStory { + function enrichHU(hu: { title?: string; id?: number; status?: string | number | null; status_name?: string | null; acceptance_criteria?: string | null; criterios_aceptacion?: string | null; assigned_to?: number | null; asignado_a?: number[] | string[] | null; asignado_a_names?: string[] | string | null; assigned_name?: string }, initiativeId: number): EnrichedUserStory { const h = parseHierarchy(hu.title || '') const rawCriteria = hu.acceptance_criteria || hu.criterios_aceptacion || '' const criteriaList = rawCriteria ? parseQuillList(rawCriteria) : [] - const { id: assignedUserId, name: assignedName } = parseAssignedUser(hu) + const { id: assignedUserId, name: assignedName, employeeId } = parseAssignedUser(hu) return { id: hu.id ?? 0, title: hu.title || '', @@ -99,7 +107,8 @@ export const useWorkItemsStore = defineStore('workitems', () => { has_impairment: false, _assignedUserId: assignedUserId, _assignedName: assignedName, - _statusName: resolveStatusName(hu.status), + _statusName: hu.status_name || resolveStatusName(hu.status), + _assignedEmployeeId: employeeId, } } @@ -210,7 +219,9 @@ export const useWorkItemsStore = defineStore('workitems', () => { hasImpairment = impairments.some(p => !p.status) } catch {} - const { id: assignedUserId } = parseAssignedUser(hu) + const { id: assignedUserId, employeeId } = parseAssignedUser(hu) + // Convención: employee_id se guarda como negativo para distinguirlo de user_id + const assignedTo = employeeId != null ? -employeeId : assignedUserId await tauriDb.saveUserStory({ id: huId, @@ -225,7 +236,7 @@ export const useWorkItemsStore = defineStore('workitems', () => { story_points: hu.story_points ?? null, estimated_hours: null, actual_hours: null, - assigned_to: assignedUserId, + assigned_to: assignedTo, sprint: safeStr(hu.sprint), has_impairment: hasImpairment, item_type: null, diff --git a/src/types/kappa.ts b/src/types/kappa.ts index ca80b86..40a4111 100644 --- a/src/types/kappa.ts +++ b/src/types/kappa.ts @@ -43,8 +43,9 @@ export interface KappaUserStory { description?: string acceptance_criteria?: string criterios_aceptacion?: string - status?: string - priority?: string + status?: string | number + status_name?: string + priority?: string | number initiative: number | string story_points?: number sprint?: string diff --git a/src/views/DashboardView.vue b/src/views/DashboardView.vue index 5a4879a..68e22a7 100644 --- a/src/views/DashboardView.vue +++ b/src/views/DashboardView.vue @@ -51,21 +51,19 @@ const projectTeam = computed(() => { .map(u => ({ id: u!.id, name: u!.full_name || u!.email })) }) -const allAssignableUsers = computed(() => { - // Todos los usuarios activos + cualquier usuario asignado por KAPPA +const assignedUsers = computed(() => { const seen = new Set() const list: { id: number; name: string }[] = [] - for (const u of usersStore.activeUsers) { - seen.add(u.id) - list.push({ id: u.id, name: u.full_name || u.email }) - } - // Incluir usuarios asignados por KAPPA aunque no estén en activeUsers for (const hu of workItems.userStories) { - const uid = hu._assignedUserId - if (uid != null && !seen.has(uid)) { - seen.add(uid) - list.push({ id: uid, name: hu._assignedName || `Usuario #${uid}` }) - } + const employeeId = hu._assignedEmployeeId + if (employeeId == null || employeeId <= 0 || seen.has(employeeId)) continue + seen.add(employeeId) + // Resolver employee → user para mostrar el nombre real + const uid = resolveEmployeeToUser(employeeId) + const name = uid != null + ? (usersStore.users.find(u => u.id === uid)?.full_name || usersStore.users.find(u => u.id === uid)?.email || '') + : '' + list.push({ id: employeeId, name: name || hu._assignedName || `Usuario #${employeeId}` }) } return list.sort((a, b) => a.name.localeCompare(b.name)) }) @@ -87,10 +85,21 @@ function saveAssignment(huId: number, userId: number | null) { storage.setJSON(STORAGE_KEY, all) } -function getAssignedUserId(hu: { id?: number; _assignedUserId?: number | null }): number | null { - // 1. KAPPA asigna directo +function resolveEmployeeToUser(employeeId: number | null): number | null { + if (employeeId == null) return null + const emp = usersStore.employees.find(e => e.id === employeeId) + return emp?.user ?? null +} + +function getAssignedUserId(hu: { _assignedEmployeeId?: number | null; _assignedUserId?: number | null; id?: number }): number | null { + // 1. KAPPA asigna por employee_id (asignado_a: [1135]) → resolver a user_id + if (hu._assignedEmployeeId != null && hu._assignedEmployeeId > 0) { + const userId = resolveEmployeeToUser(hu._assignedEmployeeId) + if (userId != null) return userId + } + // 2. KAPPA asigna directo por user_id (assigned_to) if (hu._assignedUserId != null && hu._assignedUserId > 0) return hu._assignedUserId - // 2. Fallback: asignación manual en localStorage + // 3. Fallback: asignación manual en localStorage if (hu.id != null) { const all = loadAssignments() return all[hu.id] ?? null @@ -98,11 +107,30 @@ function getAssignedUserId(hu: { id?: number; _assignedUserId?: number | null }) return null } -function assignedName(hu: { id?: number; _assignedUserId?: number | null; _assignedName?: string }): string { +function assignedName(hu: { _assignedName?: string; _assignedEmployeeId?: number | null; _assignedUserId?: number | null; id?: number }): string { + // 1. Intentar resolver employee_id → user_id → nombre real + if (hu._assignedEmployeeId != null && hu._assignedEmployeeId > 0) { + const userId = resolveEmployeeToUser(hu._assignedEmployeeId) + if (userId != null) { + const u = usersStore.users.find(u => u.id === userId) + if (u) return u.full_name || u.email + } + // Si el employee no se ha cargado aún, mostrar el nombre de KAPPA (asignado_a_names) + if (hu._assignedName) return hu._assignedName + } + // 2. user_id directo + if (hu._assignedUserId != null && hu._assignedUserId > 0) { + const u = usersStore.users.find(u => u.id === hu._assignedUserId) + if (u) return u.full_name || u.email + if (hu._assignedName) return hu._assignedName + } + // 3. Fallback localStorage const uid = getAssignedUserId(hu) - if (!uid) return '' - const u = usersStore.users.find(u => u.id === uid) - return u?.full_name || u?.email || hu._assignedName || '' + if (uid) { + const u = usersStore.users.find(u => u.id === uid) + if (u) return u.full_name || u.email + } + return hu._assignedName || '' } // ─── Priority helpers ───────────────────────────────── @@ -132,30 +160,30 @@ function priorityLabel(p: unknown) { } // ─── Filters ───────────────────────────────────────── -const filterStatus = ref('__all') -const filterPriority = ref('__all') -const filterAssigned = ref('__all') +const filterStatus = ref('') +const filterPriority = ref('') +const filterAssigned = ref('') const filteredHUs = computed(() => { let list = workItems.userStories - if (filterStatus.value !== '__all') { + if (filterStatus.value && filterStatus.value !== '__all') { list = list.filter(h => { const s = String(h.status ?? '').toLowerCase() return s === filterStatus.value }) } - if (filterPriority.value !== '__all') { + if (filterPriority.value && filterPriority.value !== '__all') { list = list.filter(h => { const p = String(h.priority ?? '').toLowerCase() return p === filterPriority.value }) } - if (filterAssigned.value !== '__all') { + if (filterAssigned.value && filterAssigned.value !== '__all') { if (filterAssigned.value === '__none') { - list = list.filter(h => !getAssignedUserId(h)) + list = list.filter(h => !getAssignedUserId(h) && !h._assignedEmployeeId) } else { - const uid = Number(filterAssigned.value) - list = list.filter(h => getAssignedUserId(h) === uid) + const targetId = Number(filterAssigned.value) + list = list.filter(h => h._assignedEmployeeId === targetId || getAssignedUserId(h) === targetId) } } return list @@ -674,7 +702,7 @@ const statusLabel = (status: unknown) => { {{ t('dashboard.filterAll') }} {{ t('dashboard.unassigned') }} - {{ m.name }} + {{ m.name }}