Skip to content

Commit 0b001d2

Browse files
committed
refactor(i18n): unificar brief ejecutivo y navegación en t() (paridad ES/EN)
1 parent 23c564d commit 0b001d2

7 files changed

Lines changed: 44 additions & 28 deletions

‎scripts/navigation-flow-smoke.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ check(!shell.includes('{to:"/proveedores-clientes",labelKey:'),'partner master d
2424
check(shell.includes('<NavLink to="/lineage"')&&app.includes('<Route path="/pescamar-ia"')&&app.includes('<Route path="/rentabilidad"'),'advanced intelligence capabilities must remain reachable without competing in primary daily navigation')
2525
check(modules.includes("{to:'/uni',label:'Revisión visual (Uni)'")&&app.includes('<Route path="/uni"')&&app.includes('<Route path="/edgevision" element={<Navigate to="/uni" replace/>}/>'),'specialized visual review must remain reachable from administration with the legacy redirect')
2626
check(shell.includes("t('shell.plantFlow')")&&shell.includes("t('shell.commercialFlow')")&&shell.includes('aria-label={tabsLabel}'),'detailed workspace flows must expose localized process semantics for assistive technology')
27-
check(shell.includes("aria-label={ceoView?(locale==='es'?'Dirección':'Executive'):'Trabajo diario'}")&&shell.includes('aria-label="Consulta y configuración"'),'primary executive/daily and conditional secondary navigation groups must expose explicit assistive labels')
27+
check(shell.includes('aria-label={workspaceNavLabel}')&&shell.includes("aria-label={t('nav.consultSettings')}")&&shell.includes("workspaceNavLabel=ceoView?t('nav.executive'):t('nav.dailyWork')"),'primary executive/daily and conditional secondary navigation groups must expose explicit assistive labels')
2828
check(shell.includes('workspace-step-index'),'detailed workspace flows must render stage numbers')
2929
check(css.includes('.operation-flow')&&css.includes('.commercial-flow')&&css.includes('.workspace-step-index'),'workspace flow hierarchy must be styled explicitly')
3030
check(css.includes('a:not(:last-child):before'),'workspace stages must preserve visible directional continuity')

‎scripts/operational-intelligence-overview-smoke.mjs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ assert(copilot.includes("const intelligenceCapabilities={canAssessCommercialComm
4141
assert(copilot.includes('buildOperationalIntelligence(ordered,intelligenceCapabilities)'),'Seafood AI must use permission-aware Operational Intelligence')
4242
assert(copilot.includes("commercialRole?sql`select a.id allocation_id")&&copilot.includes("commercialRole?sql`select s.id,s.dispatch_id"),'Seafood AI must not retrieve restricted commercial evidence merely to suppress a false positive')
4343
assert(brief.includes("fetch('/api/operational-intelligence-overview'"),'Executive Decision Brief must consume the live overview')
44-
assert(brief.includes('topOperational?<Link')&&brief.includes('topOperational.signal.title')&&brief.includes('Hacer: {topOperational.signal.action}'),'Control Tower must surface one dominant operational priority and next action')
45-
assert(brief.includes('ownerForPath')&&brief.includes('<b>Responsable:</b> {operationalOwner}'),'Control Tower must route the dominant exception to one responsible function without pretending an individual assignment')
46-
assert(brief.includes("return 'Comercial'")&&brief.includes("return 'Gerencia'")&&brief.includes("return 'Operación'")&&brief.includes("return 'Administración'"),'responsible routing must cover commercial, management, operations and technical administration functions')
44+
assert(brief.includes('topOperational?<Link')&&brief.includes('topOperational.signal.title')&&brief.includes("t('brief.do',{action:topOperational.signal.action})"),'Control Tower must surface one dominant operational priority and next action')
45+
assert(brief.includes('ownerForPath')&&brief.includes("<b>{t('home.owner')}:</b> {operationalOwner}"),'Control Tower must route the dominant exception to one responsible function without pretending an individual assignment')
46+
assert(brief.includes("return t('brief.ownerCommercial')")&&brief.includes("return t('brief.ownerManagement')")&&brief.includes("return t('home.ownerOperations')")&&brief.includes("return t('brief.ownerAdministration')"),'responsible routing must cover commercial, management, operations and technical administration functions')
4747
assert(brief.includes('topOperational.signal.blockers'),'Control Tower must expose blockers before continuation')
4848
assert(brief.includes('operationalCounts.p1')&&brief.includes('operationalCounts.p2')&&brief.includes('operationalCounts.p3'),'Control Tower must show P1/P2/P3 counts')
4949
assert(brief.includes('topOperational.path'),'dominant signal must link back to attributable lineage evidence')
50-
assert(brief.includes("operational?'No hay acciones pendientes ahora.':'No se asume un estado sin datos.'"),'zero-signal state must distinguish observed clearance from unavailable state rather than fabricate certainty')
50+
assert(brief.includes("operational?t('brief.clearDetail'):t('brief.noDataDetail')"),'zero-signal state must distinguish observed clearance from unavailable state rather than fabricate certainty')
5151
assert(brief.includes('comparableCandidates=candidates.filter(item=>item.supplier.coverage>=40'),'supplier recommendation must not prefer raw high scores without minimum comparable coverage')
52-
assert(brief.includes("`${nf.format(preferred.purchaseScore)} · ${nf.format(preferred.supplier.coverage)}% de información · confianza ${preferred.supplier.confidence}`"),'supplier score presentation must expose score, evidence coverage and confidence')
52+
assert(brief.includes("t('brief.scoreDetail',{score:nf.format(preferred.purchaseScore),coverage:nf.format(preferred.supplier.coverage),confidence:preferred.supplier.confidence})"),'supplier score presentation must expose score, evidence coverage and confidence')
5353
assert(today.includes("readJson<OperationalPayload>(`/api/operational-intelligence-overview"),'Today must consume the same Operational Intelligence overview as the executive Control Tower')
5454
assert(!today.includes("readJson<TowerPayload>(`/api/control-tower"),'Today must not use the legacy parallel lot ranking as a priority source')
5555
assert(today.includes("source:'event_graph' as const")&&today.includes("?'Origen':'Source'")&&today.includes("'registros del lote y operación actual'"),'Today must label Event Graph/live operational provenance explicitly')

‎src/components/AppShell.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,6 @@ export function AppShell({children,onNewReception}:{children:ReactNode;onNewRece
7272
const allowed=(path:string)=>Boolean(operator&&canAccessPath(operator.role,path));
7373
const primary=(path:string)=>Boolean(operator&&canAccessPath(operator.role,path)&&isPrimaryOperatorPath(operator,path));
7474
const cleanExecutiveView=operationsView||ceoView;
75-
return <div className="app-shell"><a className="skip-link" href="#main-content">{t('shell.skip')}</a><aside ref={drawerRef} className={`sidebar ${mobileOpen?"is-open":""}`} aria-label={`${t('shell.navigation')} · ${seafoodProduct.name}`}><div className="brand"><span className="pescamar-symbol" aria-hidden="true"><svg viewBox="0 0 52 34" role="img"><path d="M4 17c8-8 17-12 27-10 5 1 10 4 15 10-5 6-10 9-15 10-10 2-19-2-27-10Z"/><path d="M36 11c4-4 8-6 12-6-1 5-1 8 0 12-4 0-8-2-12-6Z"/><circle cx="14" cy="15" r="1.35"/><path className="brand-wave" d="M3 27c8-3 15-3 22 0s15 3 24-1"/></svg></span><div className="brand-copy"><strong className="brand-name">PESCAMAR</strong><small className="brand-product">Seafood Intelligence OS</small></div><button className="icon-btn mobile-close" onClick={()=>setMobileOpen(false)} aria-label={t('shell.closeMenu')}><X size={18}/></button></div><div className="sidebar-section-label">{ceoView?(locale==='es'?'Dirección':'Executive'): 'Trabajo diario'}</div><nav className="side-nav workspace-nav" aria-label={ceoView?(locale==='es'?'Dirección':'Executive'):'Trabajo diario'}>{primary('/')?<NavLink to="/" end><LayoutDashboard size={18}/><span>{t('nav.home')}</span></NavLink>:null}{primary('/pescamar-ia')?<NavLink to="/pescamar-ia"><Bot size={18}/><span>Seafood AI</span></NavLink>:null}{primary('/rentabilidad')?<NavLink to="/rentabilidad"><TrendingUp size={18}/><span>{locale==='es'?'Rentabilidad':'Profitability'}</span></NavLink>:null}{primary('/recepciones')?<NavLink to="/recepciones"><Activity size={18}/><span>{t('nav.reception')}</span></NavLink>:null}{primary('/lineas')?<NavLink to="/lineas"><Factory size={18}/><span>{t('nav.production')}</span></NavLink>:null}{primary('/control-regulatorio')?<NavLink to="/control-regulatorio"><ShieldCheck size={18}/><span>{t('nav.quality')}</span></NavLink>:null}{primary('/inventario')?<NavLink to="/inventario"><PackageCheck size={18}/><span>{t('nav.inventory')}</span></NavLink>:null}{primary('/ordenes-venta')?<NavLink to="/ordenes-venta"><ShoppingCart size={18}/><span>{t('nav.sales')}</span></NavLink>:null}</nav><div className="sidebar-spacer"/>{!cleanExecutiveView?<><div className="sidebar-section-label secondary">Consulta</div><nav className="side-nav sidebar-secondary" aria-label="Consulta y configuración">{allowed('/lineage')?<NavLink to="/lineage"><History size={18}/><span>{t('nav.history')}</span></NavLink>:null}<NavLink to="/inicio/detalle"><Activity size={18}/><span>{t('nav.reports')}</span></NavLink>{allowed('/modulos')?<NavLink to="/modulos" title={t('nav.settings')}><Settings2 size={18}/><span>{t('nav.admin')}</span></NavLink>:null}</nav></>:null}<a className="powered-by-link" href="https://www.n3uralia.com" target="_blank" rel="noreferrer">Powered by N3uralia</a></aside>{mobileOpen?<button className="mobile-nav-backdrop" type="button" onClick={()=>setMobileOpen(false)} aria-label={t('shell.closeNavigation')}/>:null}<div className="workspace"><header className="topbar"><button ref={menuButtonRef} className="icon-btn menu-btn" onClick={()=>setMobileOpen(true)} aria-label={t('shell.openMenu')}><Menu size={20}/></button><div className="topbar-context"><span>{ceoView?'CEO':contextStage}</span><b>{ceoView?(locale==='es'?'Pescamar · Dirección ejecutiva':'Pescamar · Executive'):context}</b></div><div className="topbar-actions"><button className="compact-control" type="button" onClick={switchLocale} title={locale==='es'?'English':'Español'} aria-label={locale==='es'?'Switch to English':'Cambiar a español'}>{locale==='es'?'EN':'ES'}</button><button className="theme-toggle compact-control" type="button" title={themeLabel} aria-label={themeLabel} onClick={()=>setTheme(theme==="dark"?"light":"dark")}>{theme==="dark"?<Sun size={15}/>:<Moon size={15}/>}</button>{operator&&canAccessPath(operator.role,"/modulos")&&!ceoView?<NavLink className={`system-state compact-control ${status?.ok?"is-ok":"is-pending"}`} to="/modulos" title={`${platformLabel} · ${databaseLabel}`} aria-label={`${platformLabel}. ${databaseLabel}`}><Activity size={15}/></NavLink>:!ceoView?<div className={`system-state compact-control ${status?.ok?"is-ok":"is-pending"}`} title={`${platformLabel} · ${databaseLabel}`} aria-label={`${platformLabel}. ${databaseLabel}`}><Activity size={15}/></div>:null}<button className="operator-state operator-session-button compact-operator" type="button" onClick={()=>void logout()} title={`${sessionLabel} · ${t('shell.logout')}`} aria-label={`${t('shell.logout')} · ${sessionLabel}`}><span>{initials}</span><LogOut size={13}/></button></div></header>{tabs.length&&!ceoView?<nav className={`workspace-tabs ${flowClass}`} aria-label={tabsLabel}>{tabs.map(item=><NavLink key={item.to} to={item.to} className={()=>pathname===item.to.split('?')[0]||pathname.startsWith(`${item.to.split('?')[0]}/`)?"active":undefined}>{item.step?<span className="workspace-step-index">{String(item.step).padStart(2,"0")}</span>:null}<span>{t(item.labelKey)}</span></NavLink>)}</nav>:null}<main id="main-content" tabIndex={-1} className="main-content">{children}</main></div>{showFloatingReception?<button className="floating-action" onClick={onNewReception}>{t('shell.newReception')}</button>:null}</div>
75+
const workspaceNavLabel=ceoView?t('nav.executive'):t('nav.dailyWork');
76+
return <div className="app-shell"><a className="skip-link" href="#main-content">{t('shell.skip')}</a><aside ref={drawerRef} className={`sidebar ${mobileOpen?"is-open":""}`} aria-label={`${t('shell.navigation')} · ${seafoodProduct.name}`}><div className="brand"><span className="pescamar-symbol" aria-hidden="true"><svg viewBox="0 0 52 34" role="img"><path d="M4 17c8-8 17-12 27-10 5 1 10 4 15 10-5 6-10 9-15 10-10 2-19-2-27-10Z"/><path d="M36 11c4-4 8-6 12-6-1 5-1 8 0 12-4 0-8-2-12-6Z"/><circle cx="14" cy="15" r="1.35"/><path className="brand-wave" d="M3 27c8-3 15-3 22 0s15 3 24-1"/></svg></span><div className="brand-copy"><strong className="brand-name">PESCAMAR</strong><small className="brand-product">Seafood Intelligence OS</small></div><button className="icon-btn mobile-close" onClick={()=>setMobileOpen(false)} aria-label={t('shell.closeMenu')}><X size={18}/></button></div><div className="sidebar-section-label">{workspaceNavLabel}</div><nav className="side-nav workspace-nav" aria-label={workspaceNavLabel}>{primary('/')?<NavLink to="/" end><LayoutDashboard size={18}/><span>{t('nav.home')}</span></NavLink>:null}{primary('/pescamar-ia')?<NavLink to="/pescamar-ia"><Bot size={18}/><span>Seafood AI</span></NavLink>:null}{primary('/rentabilidad')?<NavLink to="/rentabilidad"><TrendingUp size={18}/><span>{locale==='es'?'Rentabilidad':'Profitability'}</span></NavLink>:null}{primary('/recepciones')?<NavLink to="/recepciones"><Activity size={18}/><span>{t('nav.reception')}</span></NavLink>:null}{primary('/lineas')?<NavLink to="/lineas"><Factory size={18}/><span>{t('nav.production')}</span></NavLink>:null}{primary('/control-regulatorio')?<NavLink to="/control-regulatorio"><ShieldCheck size={18}/><span>{t('nav.quality')}</span></NavLink>:null}{primary('/inventario')?<NavLink to="/inventario"><PackageCheck size={18}/><span>{t('nav.inventory')}</span></NavLink>:null}{primary('/ordenes-venta')?<NavLink to="/ordenes-venta"><ShoppingCart size={18}/><span>{t('nav.sales')}</span></NavLink>:null}</nav><div className="sidebar-spacer"/>{!cleanExecutiveView?<><div className="sidebar-section-label secondary">Consulta</div><nav className="side-nav sidebar-secondary" aria-label={t('nav.consultSettings')}>{allowed('/lineage')?<NavLink to="/lineage"><History size={18}/><span>{t('nav.history')}</span></NavLink>:null}<NavLink to="/inicio/detalle"><Activity size={18}/><span>{t('nav.reports')}</span></NavLink>{allowed('/modulos')?<NavLink to="/modulos" title={t('nav.settings')}><Settings2 size={18}/><span>{t('nav.admin')}</span></NavLink>:null}</nav></>:null}<a className="powered-by-link" href="https://www.n3uralia.com" target="_blank" rel="noreferrer">Powered by N3uralia</a></aside>{mobileOpen?<button className="mobile-nav-backdrop" type="button" onClick={()=>setMobileOpen(false)} aria-label={t('shell.closeNavigation')}/>:null}<div className="workspace"><header className="topbar"><button ref={menuButtonRef} className="icon-btn menu-btn" onClick={()=>setMobileOpen(true)} aria-label={t('shell.openMenu')}><Menu size={20}/></button><div className="topbar-context"><span>{ceoView?'CEO':contextStage}</span><b>{ceoView?(locale==='es'?'Pescamar · Dirección ejecutiva':'Pescamar · Executive'):context}</b></div><div className="topbar-actions"><button className="compact-control" type="button" onClick={switchLocale} title={locale==='es'?'English':'Español'} aria-label={locale==='es'?'Switch to English':'Cambiar a español'}>{locale==='es'?'EN':'ES'}</button><button className="theme-toggle compact-control" type="button" title={themeLabel} aria-label={themeLabel} onClick={()=>setTheme(theme==="dark"?"light":"dark")}>{theme==="dark"?<Sun size={15}/>:<Moon size={15}/>}</button>{operator&&canAccessPath(operator.role,"/modulos")&&!ceoView?<NavLink className={`system-state compact-control ${status?.ok?"is-ok":"is-pending"}`} to="/modulos" title={`${platformLabel} · ${databaseLabel}`} aria-label={`${platformLabel}. ${databaseLabel}`}><Activity size={15}/></NavLink>:!ceoView?<div className={`system-state compact-control ${status?.ok?"is-ok":"is-pending"}`} title={`${platformLabel} · ${databaseLabel}`} aria-label={`${platformLabel}. ${databaseLabel}`}><Activity size={15}/></div>:null}<button className="operator-state operator-session-button compact-operator" type="button" onClick={()=>void logout()} title={`${sessionLabel} · ${t('shell.logout')}`} aria-label={`${t('shell.logout')} · ${sessionLabel}`}><span>{initials}</span><LogOut size={13}/></button></div></header>{tabs.length&&!ceoView?<nav className={`workspace-tabs ${flowClass}`} aria-label={tabsLabel}>{tabs.map(item=><NavLink key={item.to} to={item.to} className={()=>pathname===item.to.split('?')[0]||pathname.startsWith(`${item.to.split('?')[0]}/`)?"active":undefined}>{item.step?<span className="workspace-step-index">{String(item.step).padStart(2,"0")}</span>:null}<span>{t(item.labelKey)}</span></NavLink>)}</nav>:null}<main id="main-content" tabIndex={-1} className="main-content">{children}</main></div>{showFloatingReception?<button className="floating-action" onClick={onNewReception}>{t('shell.newReception')}</button>:null}</div>
7677
}

0 commit comments

Comments
 (0)