@@ -59,6 +59,7 @@ export default function TrainingCalendarView() {
5959 } , [ activeAccountId ] )
6060
6161 const empById = useMemo ( ( ) => new Map ( staff . map ( e => [ e . id , e ] ) ) , [ staff ] )
62+ const locationById = useMemo ( ( ) => new Map ( locations . map ( l => [ l . id , l ] ) ) , [ locations ] )
6263 const positions = useMemo ( ( ) => [ ...new Set ( staff . map ( e => e . position ) . filter ( Boolean ) ) ] . sort ( ) , [ staff ] )
6364
6465 function passesFilter ( employeeId : string ) : boolean {
@@ -69,11 +70,25 @@ export default function TrainingCalendarView() {
6970 return true
7071 }
7172
73+ // Homónimos son reales (fichas distintas con el mismo nombre, en distinta
74+ // cuenta/local) — visto en producción con dos "Pamela Guzmán Velásquez"
75+ // (ids distintos, una en Sala y otra en Cocina). El Map de abajo agrupa por
76+ // employeeId (nunca por nombre: dos ids nunca comparten entrada aquí), pero
77+ // dos tarjetas con el mismo nombre y SIN nada más que las distinga eran
78+ // indistinguibles a la vista — de ahí el aviso. Cada tarjeta lleva ahora
79+ // puesto + local para que un vistazo baste.
80+ function employeeDetail ( employeeId : string ) : string {
81+ const emp = empById . get ( employeeId )
82+ if ( ! emp ) return ''
83+ const loc = emp . locationId ? locationById . get ( emp . locationId ) ?. name : null
84+ return [ emp . position , loc ] . filter ( Boolean ) . join ( ' · ' )
85+ }
86+
7287 const venceByEmployee = useMemo ( ( ) => {
73- const map = new Map < string , { name : string ; items : TrainingGap [ ] } > ( )
88+ const map = new Map < string , { name : string ; detail : string ; items : TrainingGap [ ] } > ( )
7489 for ( const g of gaps ) {
7590 if ( ! VENCE_KINDS . includes ( g . gapKind ) || ! passesFilter ( g . employeeId ) ) continue
76- const entry = map . get ( g . employeeId ) ?? { name : g . employeeName , items : [ ] }
91+ const entry = map . get ( g . employeeId ) ?? { name : g . employeeName , detail : employeeDetail ( g . employeeId ) , items : [ ] }
7792 entry . items . push ( g )
7893 map . set ( g . employeeId , entry )
7994 }
@@ -86,7 +101,7 @@ export default function TrainingCalendarView() {
86101 if ( aBlocking !== bBlocking ) return aBlocking ? - 1 : 1
87102 return b [ 1 ] . items . length - a [ 1 ] . items . length
88103 } )
89- } , [ gaps , empById , locationFilter , positionFilter , blockingIds ] )
104+ } , [ gaps , empById , locationById , locationFilter , positionFilter , blockingIds ] )
90105
91106 const caducaInternal = useMemo (
92107 ( ) => gaps . filter ( g => CADUCA_KINDS . includes ( g . gapKind ) && passesFilter ( g . employeeId ) ) ,
@@ -159,12 +174,15 @@ export default function TrainingCalendarView() {
159174 < Card className = "p-6 text-center text-sm text-text-secondary" > Nadie tiene formación pendiente con este filtro.</ Card >
160175 ) : (
161176 < div className = "grid gap-3 sm:grid-cols-2 lg:grid-cols-3" >
162- { venceByEmployee . map ( ( [ employeeId , { name, items } ] ) => {
177+ { venceByEmployee . map ( ( [ employeeId , { name, detail , items } ] ) => {
163178 const hasBlocking = items . some ( i => blockingIds . has ( i . courseId ) )
164179 return (
165180 < Card key = { employeeId } className = { `p-4 ${ hasBlocking ? 'border-danger/40' : '' } ` } >
166181 < div className = "flex items-center justify-between gap-2" >
167- < p className = "font-semibold text-text-primary truncate" > { name } </ p >
182+ < div className = "min-w-0" >
183+ < p className = "font-semibold text-text-primary truncate" > { name } </ p >
184+ { detail && < p className = "text-xs text-text-secondary truncate" > { detail } </ p > }
185+ </ div >
168186 < Badge color = { hasBlocking ? 'red' : 'gray' } > { items . length } </ Badge >
169187 </ div >
170188 < ul className = "mt-2 space-y-1" >
@@ -197,7 +215,9 @@ export default function TrainingCalendarView() {
197215 < Card key = { `int-${ i } ` } className = "p-3 flex items-center justify-between gap-3" >
198216 < div className = "min-w-0" >
199217 < p className = "text-sm text-text-primary truncate" > { g . employeeName } · { g . courseTitle } </ p >
200- < p className = "text-xs text-text-secondary" > Formación interna</ p >
218+ < p className = "text-xs text-text-secondary truncate" >
219+ Formación interna{ employeeDetail ( g . employeeId ) ? ` · ${ employeeDetail ( g . employeeId ) } ` : '' }
220+ </ p >
201221 </ div >
202222 < Badge color = { g . gapKind === 'caducado' ? 'red' : 'yellow' } >
203223 { g . gapKind === 'caducado' ? 'Caducado' : g . daysLeft != null ? `${ g . daysLeft } días` : 'Caduca pronto' }
@@ -208,7 +228,9 @@ export default function TrainingCalendarView() {
208228 < Card key = { `ext-${ i } ` } className = "p-3 flex items-center justify-between gap-3" >
209229 < div className = "min-w-0" >
210230 < p className = "text-sm text-text-primary truncate" > { empById . get ( formation . employeeId ) ?. name ?? '—' } · { formation . name } </ p >
211- < p className = "text-xs text-text-secondary" > Certificado externo</ p >
231+ < p className = "text-xs text-text-secondary truncate" >
232+ Certificado externo{ employeeDetail ( formation . employeeId ) ? ` · ${ employeeDetail ( formation . employeeId ) } ` : '' }
233+ </ p >
212234 </ div >
213235 < Badge color = { status . color === 'red' ? 'red' : status . color === 'orange' ? 'yellow' : 'yellow' } > { status . label } </ Badge >
214236 </ Card >
0 commit comments