@stackcraftmx/calcita 0.23.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/README.md +127 -22
  2. package/cli/projectFiles.js +5 -3
  3. package/cli/templates/project/README.md.tpl +28 -0
  4. package/cli/templates/project/public/manifest.webmanifest.tpl +6 -6
  5. package/cli/templates/project/src/app/AppProviders.jsx.tpl +9 -4
  6. package/cli/templates/project/src/app/appRouter.jsx.tpl +4 -2
  7. package/cli/templates/project/src/app/appSettings.js.tpl +14 -0
  8. package/cli/templates/project/src/core/AppLogo.jsx.tpl +3 -1
  9. package/cli/templates/project/src/core/index.js.tpl +1 -0
  10. package/cli/templates/project/src/core/publicPaths.js.tpl +24 -0
  11. package/cli/templates/project/vite.config.js.tpl +27 -1
  12. package/dist/DataTable/AuditTrailTable.js +5 -3
  13. package/dist/DataTable/AuditTrailTable.js.map +1 -1
  14. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +2 -1
  15. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  16. package/dist/DataTable/EmptyState.js +2 -2
  17. package/dist/DataTable/EmptyState.js.map +1 -1
  18. package/dist/DataTable/FilterChips.js +2 -2
  19. package/dist/DataTable/FilterChips.js.map +1 -1
  20. package/dist/DataTable/GoToPage.js +4 -3
  21. package/dist/DataTable/GoToPage.js.map +1 -1
  22. package/dist/DataTable/ServerTable.js +153 -83
  23. package/dist/DataTable/ServerTable.js.map +1 -1
  24. package/dist/DataTable/rowSelection.js +61 -0
  25. package/dist/DataTable/rowSelection.js.map +1 -0
  26. package/dist/access/PermissionMatrix.js +3 -2
  27. package/dist/access/PermissionMatrix.js.map +1 -1
  28. package/dist/account/AccountSecuritySection.js +2 -2
  29. package/dist/account/AccountSecuritySection.js.map +1 -1
  30. package/dist/account/ForgotPasswordScreen.js +5 -4
  31. package/dist/account/ForgotPasswordScreen.js.map +1 -1
  32. package/dist/account/ProfileScreen.js +39 -13
  33. package/dist/account/ProfileScreen.js.map +1 -1
  34. package/dist/account/ResetPasswordScreen.js +3 -2
  35. package/dist/account/ResetPasswordScreen.js.map +1 -1
  36. package/dist/account/SignInScreen.js +7 -6
  37. package/dist/account/SignInScreen.js.map +1 -1
  38. package/dist/account/accountConnector.js.map +1 -1
  39. package/dist/account/restAccountConnector.js +2 -1
  40. package/dist/account/restAccountConnector.js.map +1 -1
  41. package/dist/adapters/reactRouterRoutes.js +7 -6
  42. package/dist/adapters/reactRouterRoutes.js.map +1 -1
  43. package/dist/api/createApiClient.js +40 -5
  44. package/dist/api/createApiClient.js.map +1 -1
  45. package/dist/appShell/AccountMenu.js +15 -23
  46. package/dist/appShell/AccountMenu.js.map +1 -1
  47. package/dist/appShell/AppLayout.js +42 -15
  48. package/dist/appShell/AppLayout.js.map +1 -1
  49. package/dist/appShell/AppearanceSettings.js +29 -6
  50. package/dist/appShell/AppearanceSettings.js.map +1 -1
  51. package/dist/appShell/AuthLayout.js +2 -0
  52. package/dist/appShell/AuthLayout.js.map +1 -1
  53. package/dist/appShell/BarActionButton.js +3 -3
  54. package/dist/appShell/BarActionButton.js.map +1 -1
  55. package/dist/appShell/BarActions.js +19 -8
  56. package/dist/appShell/BarActions.js.map +1 -1
  57. package/dist/appShell/BarCapsule.js +19 -4
  58. package/dist/appShell/BarCapsule.js.map +1 -1
  59. package/dist/appShell/BarSearch.js +92 -0
  60. package/dist/appShell/BarSearch.js.map +1 -0
  61. package/dist/appShell/HeaderTools.js +4 -2
  62. package/dist/appShell/HeaderTools.js.map +1 -1
  63. package/dist/appShell/MenuLinks.js +36 -0
  64. package/dist/appShell/MenuLinks.js.map +1 -0
  65. package/dist/appShell/MobileTabBar.js +6 -4
  66. package/dist/appShell/MobileTabBar.js.map +1 -1
  67. package/dist/appShell/SettingsMenu.js +10 -5
  68. package/dist/appShell/SettingsMenu.js.map +1 -1
  69. package/dist/appShell/SideMenu.js +5 -3
  70. package/dist/appShell/SideMenu.js.map +1 -1
  71. package/dist/appShell/SideMenuEntry.js +3 -3
  72. package/dist/appShell/SideMenuEntry.js.map +1 -1
  73. package/dist/appShell/SubmenuActionsMenu.js +3 -4
  74. package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
  75. package/dist/appShell/TopMenu.js +5 -6
  76. package/dist/appShell/TopMenu.js.map +1 -1
  77. package/dist/appShell/Wordmark.js +11 -0
  78. package/dist/appShell/Wordmark.js.map +1 -0
  79. package/dist/appShell/menuItemText.js +4 -0
  80. package/dist/appShell/menuItemText.js.map +1 -1
  81. package/dist/appShell/shellSizes.js +2 -0
  82. package/dist/appShell/shellSizes.js.map +1 -1
  83. package/dist/auth/PasswordChecklist.js +2 -2
  84. package/dist/auth/PasswordChecklist.js.map +1 -1
  85. package/dist/calendar/EventCalendar.js +16 -5
  86. package/dist/calendar/EventCalendar.js.map +1 -1
  87. package/dist/calendar/EventDetails.js +74 -0
  88. package/dist/calendar/EventDetails.js.map +1 -0
  89. package/dist/calendar/calendarEvents.js +31 -0
  90. package/dist/calendar/calendarEvents.js.map +1 -0
  91. package/dist/calendar.js +8 -1
  92. package/dist/calendar.js.map +1 -1
  93. package/dist/charts.js +10 -1
  94. package/dist/charts.js.map +1 -1
  95. package/dist/datePickers/DateInput.js +3 -2
  96. package/dist/datePickers/DateInput.js.map +1 -1
  97. package/dist/downloads/DownloadManager.js +14 -20
  98. package/dist/downloads/DownloadManager.js.map +1 -1
  99. package/dist/downloads/downloadQueue.js +4 -2
  100. package/dist/downloads/downloadQueue.js.map +1 -1
  101. package/dist/downloads/downloadsMachine.js +4 -1
  102. package/dist/downloads/downloadsMachine.js.map +1 -1
  103. package/dist/downloads/fileTypes.js +22 -0
  104. package/dist/downloads/fileTypes.js.map +1 -0
  105. package/dist/feedback/ConfirmDialog.js +3 -2
  106. package/dist/feedback/ConfirmDialog.js.map +1 -1
  107. package/dist/feedback/ModalWaiting.js +2 -2
  108. package/dist/feedback/ModalWaiting.js.map +1 -1
  109. package/dist/feedback/NewBuildNotice.js +2 -2
  110. package/dist/feedback/NewBuildNotice.js.map +1 -1
  111. package/dist/filepond/FileDropzone.js +5 -3
  112. package/dist/filepond/FileDropzone.js.map +1 -1
  113. package/dist/formatting.js +171 -143
  114. package/dist/formatting.js.map +1 -1
  115. package/dist/forms/FormWizard.js +18 -45
  116. package/dist/forms/FormWizard.js.map +1 -1
  117. package/dist/forms/auth/ChangePasswordForm.js +5 -4
  118. package/dist/forms/auth/ChangePasswordForm.js.map +1 -1
  119. package/dist/forms/auth/ForgotPasswordForm.js +2 -2
  120. package/dist/forms/auth/ForgotPasswordForm.js.map +1 -1
  121. package/dist/forms/auth/SignInForm.js +2 -2
  122. package/dist/forms/auth/SignInForm.js.map +1 -1
  123. package/dist/forms/security/SecondStepForm.js +2 -2
  124. package/dist/forms/security/SecondStepForm.js.map +1 -1
  125. package/dist/forms/security/TwoStepVerificationCard.js +8 -6
  126. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  127. package/dist/hookForm/FormWizard.js +59 -0
  128. package/dist/hookForm/FormWizard.js.map +1 -0
  129. package/dist/inbox/InboxLayout.js +44 -8
  130. package/dist/inbox/InboxLayout.js.map +1 -1
  131. package/dist/inbox/MessageList.js +71 -21
  132. package/dist/inbox/MessageList.js.map +1 -1
  133. package/dist/inbox/MessageView.js +80 -29
  134. package/dist/inbox/MessageView.js.map +1 -1
  135. package/dist/inbox/inboxDates.js +4 -3
  136. package/dist/inbox/inboxDates.js.map +1 -1
  137. package/dist/index.js +163 -136
  138. package/dist/index.js.map +1 -1
  139. package/dist/inputs/CatalogInput.js +8 -4
  140. package/dist/inputs/CatalogInput.js.map +1 -1
  141. package/dist/inputs/ColorInput.js +3 -2
  142. package/dist/inputs/ColorInput.js.map +1 -1
  143. package/dist/inputs/FormSection.js +3 -3
  144. package/dist/inputs/FormSection.js.map +1 -1
  145. package/dist/inputs/NumberInput.js +3 -2
  146. package/dist/inputs/NumberInput.js.map +1 -1
  147. package/dist/inputs/SelectInput.js +29 -11
  148. package/dist/inputs/SelectInput.js.map +1 -1
  149. package/dist/inputs/TextInput.js +6 -2
  150. package/dist/inputs/TextInput.js.map +1 -1
  151. package/dist/inputs/WizardLayout.js +54 -0
  152. package/dist/inputs/WizardLayout.js.map +1 -0
  153. package/dist/kanban/KanbanBoard.js +239 -14
  154. package/dist/kanban/KanbanBoard.js.map +1 -1
  155. package/dist/kanban/kanbanCards.js +19 -0
  156. package/dist/kanban/kanbanCards.js.map +1 -0
  157. package/dist/kanban.js +7 -1
  158. package/dist/kanban.js.map +1 -1
  159. package/dist/locales/en.js +74 -1
  160. package/dist/locales/en.js.map +1 -1
  161. package/dist/locales/es.js +74 -1
  162. package/dist/locales/es.js.map +1 -1
  163. package/dist/maps/MapView.js +28 -8
  164. package/dist/maps/MapView.js.map +1 -1
  165. package/dist/navigation/appMenu.js +13 -1
  166. package/dist/navigation/appMenu.js.map +1 -1
  167. package/dist/observability/logger.js +18 -0
  168. package/dist/observability/logger.js.map +1 -1
  169. package/dist/observability/usePublishedBuildWatch.js +4 -3
  170. package/dist/observability/usePublishedBuildWatch.js.map +1 -1
  171. package/dist/pwa/InstallAppSetting.js +6 -5
  172. package/dist/pwa/InstallAppSetting.js.map +1 -1
  173. package/dist/pwa/InstallInstructionsDialog.js +2 -2
  174. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  175. package/dist/pwa/InstallPromptBanner.js +15 -9
  176. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  177. package/dist/pwa/useInstallPrompt.js +2 -1
  178. package/dist/pwa/useInstallPrompt.js.map +1 -1
  179. package/dist/react-hook-form.js +6 -4
  180. package/dist/react-hook-form.js.map +1 -1
  181. package/dist/security/ActivityLog.js +4 -3
  182. package/dist/security/ActivityLog.js.map +1 -1
  183. package/dist/security/RecoveryCodesDialog.js +4 -3
  184. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  185. package/dist/security/SessionsCard.js +8 -6
  186. package/dist/security/SessionsCard.js.map +1 -1
  187. package/dist/table.js +2 -0
  188. package/dist/table.js.map +1 -1
  189. package/dist/theme/components/MuiAppBar.js +6 -5
  190. package/dist/theme/components/MuiAppBar.js.map +1 -1
  191. package/dist/theme/components/MuiDialog.js +13 -0
  192. package/dist/theme/components/MuiDialog.js.map +1 -0
  193. package/dist/theme/components/glassComponents.js +32 -31
  194. package/dist/theme/components/glassComponents.js.map +1 -1
  195. package/dist/theme/components/minimalComponents.js +8 -4
  196. package/dist/theme/components/minimalComponents.js.map +1 -1
  197. package/dist/theme/createToolkitTheme.js.map +1 -1
  198. package/dist/theme/glassSurface.js +36 -38
  199. package/dist/theme/glassSurface.js.map +1 -1
  200. package/dist/theme/lightDarkStyles.js +48 -0
  201. package/dist/theme/lightDarkStyles.js.map +1 -0
  202. package/dist/theme/toolkitComponents.js +2 -0
  203. package/dist/theme/toolkitComponents.js.map +1 -1
  204. package/dist/toolkit/ComponentTexts.js +13 -0
  205. package/dist/toolkit/ComponentTexts.js.map +1 -0
  206. package/dist/toolkit/ToolkitProvider.js +36 -28
  207. package/dist/toolkit/ToolkitProvider.js.map +1 -1
  208. package/dist/toolkit/browserNavigation.js +19 -20
  209. package/dist/toolkit/browserNavigation.js.map +1 -1
  210. package/dist/toolkit/componentTextsContext.js +15 -0
  211. package/dist/toolkit/componentTextsContext.js.map +1 -0
  212. package/dist/toolkit/hashNavigation.js +40 -0
  213. package/dist/toolkit/hashNavigation.js.map +1 -0
  214. package/dist/toolkit/navigationPaths.js +49 -0
  215. package/dist/toolkit/navigationPaths.js.map +1 -0
  216. package/dist/toolkit/toolkitSettings.js +24 -4
  217. package/dist/toolkit/toolkitSettings.js.map +1 -1
  218. package/dist/toolkit/useFormatters.js +12 -0
  219. package/dist/toolkit/useFormatters.js.map +1 -0
  220. package/dist/toolkit/useToolkitTranslation.js +11 -7
  221. package/dist/toolkit/useToolkitTranslation.js.map +1 -1
  222. package/dist/types/DataTable/AuditTrailTable.d.ts +3 -1
  223. package/dist/types/DataTable/EmptyState.d.ts +7 -2
  224. package/dist/types/DataTable/FilterChips.d.ts +3 -1
  225. package/dist/types/DataTable/GoToPage.d.ts +7 -2
  226. package/dist/types/DataTable/SelectionBar.d.ts +32 -0
  227. package/dist/types/DataTable/ServerTable.d.ts +64 -3
  228. package/dist/types/DataTable/index.d.ts +1 -1
  229. package/dist/types/DataTable/rowSelection.d.ts +113 -0
  230. package/dist/types/access/PermissionMatrix.d.ts +3 -1
  231. package/dist/types/account/AccountSecuritySection.d.ts +3 -1
  232. package/dist/types/account/ForgotPasswordScreen.d.ts +3 -1
  233. package/dist/types/account/ProfileScreen.d.ts +31 -1
  234. package/dist/types/account/ResetPasswordScreen.d.ts +3 -1
  235. package/dist/types/account/SignInScreen.d.ts +3 -1
  236. package/dist/types/account/accountConnector.d.ts +7 -2
  237. package/dist/types/adapters/reactRouterRoutes.d.ts +14 -2
  238. package/dist/types/api/createApiClient.d.ts +46 -1
  239. package/dist/types/appShell/AppLayout.d.ts +15 -1
  240. package/dist/types/appShell/AppearanceSettings.d.ts +8 -1
  241. package/dist/types/appShell/BarActionButton.d.ts +2 -2
  242. package/dist/types/appShell/BarActions.d.ts +6 -1
  243. package/dist/types/appShell/BarCapsule.d.ts +8 -0
  244. package/dist/types/appShell/BarSearch.d.ts +24 -0
  245. package/dist/types/appShell/MenuLinks.d.ts +17 -0
  246. package/dist/types/appShell/MobileTabBar.d.ts +4 -1
  247. package/dist/types/appShell/SettingsMenu.d.ts +3 -1
  248. package/dist/types/appShell/SideMenu.d.ts +3 -1
  249. package/dist/types/appShell/Wordmark.d.ts +6 -0
  250. package/dist/types/appShell/menuItemText.d.ts +16 -0
  251. package/dist/types/appShell/shellSizes.d.ts +1 -0
  252. package/dist/types/auth/PasswordChecklist.d.ts +3 -1
  253. package/dist/types/calendar/EventCalendar.d.ts +18 -1
  254. package/dist/types/calendar/EventDetails.d.ts +36 -0
  255. package/dist/types/calendar/calendarEvents.d.ts +27 -0
  256. package/dist/types/calendar.d.ts +2 -0
  257. package/dist/types/charts.d.ts +4 -0
  258. package/dist/types/datePickers/DateInput.d.ts +3 -1
  259. package/dist/types/downloads/DownloadManager.d.ts +7 -1
  260. package/dist/types/downloads/downloadQueue.d.ts +8 -3
  261. package/dist/types/downloads/fileTypes.d.ts +39 -0
  262. package/dist/types/downloads/index.d.ts +1 -0
  263. package/dist/types/feedback/ConfirmDialog.d.ts +3 -1
  264. package/dist/types/feedback/ModalWaiting.d.ts +7 -2
  265. package/dist/types/feedback/NewBuildNotice.d.ts +3 -1
  266. package/dist/types/filepond/FileDropzone.d.ts +3 -1
  267. package/dist/types/formatting.d.ts +45 -90
  268. package/dist/types/forms/FormWizard.d.ts +3 -1
  269. package/dist/types/forms/auth/ChangePasswordForm.d.ts +3 -1
  270. package/dist/types/forms/auth/ForgotPasswordForm.d.ts +3 -1
  271. package/dist/types/forms/auth/SignInForm.d.ts +3 -1
  272. package/dist/types/forms/security/SecondStepForm.d.ts +3 -1
  273. package/dist/types/forms/security/TwoStepVerificationCard.d.ts +3 -1
  274. package/dist/types/hookForm/FormWizard.d.ts +22 -0
  275. package/dist/types/inbox/InboxLayout.d.ts +14 -2
  276. package/dist/types/inbox/MessageList.d.ts +30 -5
  277. package/dist/types/inbox/MessageView.d.ts +35 -3
  278. package/dist/types/inbox/inboxDates.d.ts +6 -1
  279. package/dist/types/index.d.ts +15 -8
  280. package/dist/types/inputs/CatalogInput.d.ts +9 -5
  281. package/dist/types/inputs/ColorInput.d.ts +3 -1
  282. package/dist/types/inputs/FormSection.d.ts +2 -2
  283. package/dist/types/inputs/NumberInput.d.ts +3 -1
  284. package/dist/types/inputs/SelectInput.d.ts +9 -5
  285. package/dist/types/inputs/TextInput.d.ts +3 -1
  286. package/dist/types/inputs/WizardLayout.d.ts +34 -0
  287. package/dist/types/kanban/KanbanBoard.d.ts +61 -6
  288. package/dist/types/kanban/kanbanCards.d.ts +22 -0
  289. package/dist/types/kanban.d.ts +1 -0
  290. package/dist/types/locales/en.d.ts +77 -0
  291. package/dist/types/locales/es.d.ts +77 -0
  292. package/dist/types/maps/MapView.d.ts +12 -3
  293. package/dist/types/navigation/appMenu.d.ts +8 -0
  294. package/dist/types/observability/logger.d.ts +13 -0
  295. package/dist/types/observability/usePublishedBuildWatch.d.ts +3 -1
  296. package/dist/types/pwa/InstallAppSetting.d.ts +7 -1
  297. package/dist/types/pwa/InstallInstructionsDialog.d.ts +3 -1
  298. package/dist/types/pwa/InstallPromptBanner.d.ts +10 -1
  299. package/dist/types/pwa/useInstallPrompt.d.ts +3 -2
  300. package/dist/types/react-hook-form.d.ts +1 -0
  301. package/dist/types/security/ActivityLog.d.ts +3 -1
  302. package/dist/types/security/RecoveryCodesDialog.d.ts +6 -1
  303. package/dist/types/security/SessionsCard.d.ts +3 -1
  304. package/dist/types/table.d.ts +3 -1
  305. package/dist/types/theme/components/MuiAppBar.d.ts +3 -1
  306. package/dist/types/theme/components/MuiDialog.d.ts +9 -0
  307. package/dist/types/theme/components/glassComponents.d.ts +5 -0
  308. package/dist/types/theme/lightDarkStyles.d.ts +8 -0
  309. package/dist/types/toolkit/ComponentTexts.d.ts +11 -0
  310. package/dist/types/toolkit/ToolkitProvider.d.ts +18 -2
  311. package/dist/types/toolkit/browserNavigation.d.ts +23 -28
  312. package/dist/types/toolkit/componentTextsContext.d.ts +20 -0
  313. package/dist/types/toolkit/hashNavigation.d.ts +12 -0
  314. package/dist/types/toolkit/navigationPaths.d.ts +43 -0
  315. package/dist/types/toolkit/toolkitSettings.d.ts +49 -4
  316. package/dist/types/toolkit/useFormatters.d.ts +6 -0
  317. package/dist/types/toolkit/useToolkitTranslation.d.ts +2 -1
  318. package/dist/types/ui/actions/ActionsMenu.d.ts +20 -0
  319. package/dist/types/ui/actions/CopyButton.d.ts +3 -1
  320. package/dist/types/ui/actions/DownloadMenu.d.ts +8 -3
  321. package/dist/types/ui/actions/SecretField.d.ts +5 -1
  322. package/dist/types/ui/actions/SelectionActions.d.ts +39 -0
  323. package/dist/types/ui/filters/DateRangeFilter.d.ts +3 -1
  324. package/dist/types/ui/filters/QuickFilters.d.ts +3 -1
  325. package/dist/types/ui/index.d.ts +1 -1
  326. package/dist/types/ui/labels/tagOf.d.ts +13 -0
  327. package/dist/types/ui/layout/CollapsibleSection.d.ts +4 -1
  328. package/dist/types/ui/layout/ContentDialog.d.ts +3 -1
  329. package/dist/types/ui/layout/ErrorPage.d.ts +14 -1
  330. package/dist/types/ui/layout/FullPageLoader.d.ts +7 -1
  331. package/dist/types/ui/layout/PageHeading.d.ts +3 -1
  332. package/dist/types/ui/metrics/BarList.d.ts +7 -1
  333. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +7 -1
  334. package/dist/types/ui/metrics/ChartTooltip.d.ts +9 -3
  335. package/dist/types/ui/metrics/ColumnChart.d.ts +23 -11
  336. package/dist/types/ui/metrics/LineChart.d.ts +41 -0
  337. package/dist/types/ui/metrics/MetricCard.d.ts +9 -1
  338. package/dist/types/ui/metrics/TableToggle.d.ts +9 -2
  339. package/dist/types/ui/metrics/chartParts.d.ts +112 -0
  340. package/dist/types/ui/navigation/BackButton.d.ts +3 -1
  341. package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +8 -2
  342. package/dist/types/ui/navigation/FontSizeToggle.d.ts +7 -2
  343. package/dist/types/ui/navigation/LanguageToggle.d.ts +8 -3
  344. package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +5 -2
  345. package/dist/types/ui/timeline/GroupedTimeline.d.ts +4 -1
  346. package/dist/types/upload/FileDropzone.d.ts +3 -1
  347. package/dist/ui/actions/ActionsMenu.js +51 -0
  348. package/dist/ui/actions/ActionsMenu.js.map +1 -0
  349. package/dist/ui/actions/CopyButton.js +2 -2
  350. package/dist/ui/actions/CopyButton.js.map +1 -1
  351. package/dist/ui/actions/DownloadMenu.js +11 -12
  352. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  353. package/dist/ui/actions/SecretField.js +8 -4
  354. package/dist/ui/actions/SecretField.js.map +1 -1
  355. package/dist/ui/actions/SelectionActions.js +142 -0
  356. package/dist/ui/actions/SelectionActions.js.map +1 -0
  357. package/dist/ui/filters/DateRangeFilter.js +4 -3
  358. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  359. package/dist/ui/filters/QuickFilters.js +2 -2
  360. package/dist/ui/filters/QuickFilters.js.map +1 -1
  361. package/dist/ui/labels/tagOf.js +7 -0
  362. package/dist/ui/labels/tagOf.js.map +1 -0
  363. package/dist/ui/layout/CollapsibleSection.js +6 -2
  364. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  365. package/dist/ui/layout/ContentDialog.js +2 -2
  366. package/dist/ui/layout/ContentDialog.js.map +1 -1
  367. package/dist/ui/layout/ErrorPage.js +21 -7
  368. package/dist/ui/layout/ErrorPage.js.map +1 -1
  369. package/dist/ui/layout/FullPageLoader.js +2 -2
  370. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  371. package/dist/ui/layout/PageHeading.js +2 -2
  372. package/dist/ui/layout/PageHeading.js.map +1 -1
  373. package/dist/ui/metrics/BarList.js +18 -13
  374. package/dist/ui/metrics/BarList.js.map +1 -1
  375. package/dist/ui/metrics/CalendarHeatmap.js +11 -6
  376. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  377. package/dist/ui/metrics/ChartTooltip.js +1 -1
  378. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  379. package/dist/ui/metrics/ColumnChart.js +59 -63
  380. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  381. package/dist/ui/metrics/LineChart.js +117 -0
  382. package/dist/ui/metrics/LineChart.js.map +1 -0
  383. package/dist/ui/metrics/MetricCard.js +41 -8
  384. package/dist/ui/metrics/MetricCard.js.map +1 -1
  385. package/dist/ui/metrics/TableToggle.js +4 -2
  386. package/dist/ui/metrics/TableToggle.js.map +1 -1
  387. package/dist/ui/metrics/chartParts.js +93 -0
  388. package/dist/ui/metrics/chartParts.js.map +1 -0
  389. package/dist/ui/navigation/BackButton.js +2 -2
  390. package/dist/ui/navigation/BackButton.js.map +1 -1
  391. package/dist/ui/navigation/ColorSchemeToggle.js +6 -3
  392. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  393. package/dist/ui/navigation/FontSizeToggle.js +3 -2
  394. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  395. package/dist/ui/navigation/LanguageToggle.js +40 -19
  396. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  397. package/dist/ui/navigation/PageBreadcrumbs.js +2 -2
  398. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  399. package/dist/ui/timeline/GroupedTimeline.js +10 -8
  400. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  401. package/dist/upload/FileDropzone.js +5 -3
  402. package/dist/upload/FileDropzone.js.map +1 -1
  403. package/package.json +16 -7
@@ -1 +1 @@
1
- {"version":3,"file":"es.js","sources":["../../src/locales/es.js"],"sourcesContent":["// Textos del paquete en español (el idioma por omisión). Cada proyecto puede cambiar cualquiera con `texts` o `translate`\n// de ToolkitProvider, con la misma forma: namespace, llaves anidadas, {{variable}} y plurales _one / _other.\nexport default Object.freeze({\n \"table\": {\n \"search\": {\n \"placeholder\": \"Buscar dentro de estos resultados…\"\n },\n \"count\": {\n \"records_one\": \"{{total}} registro\",\n \"records_other\": \"{{total}} registros\",\n \"capped\": \"más de {{total}} registros\",\n \"cappedTag\": \"total aproximado\",\n \"cappedTooltip\": \"Hay al menos estos registros y puede haber más: agregue un filtro para verlos todos.\"\n },\n \"empty\": {\n \"default\": \"Sin registros\",\n \"filtered\": \"Ningún registro cumple con los filtros aplicados.\",\n \"clearFilters\": \"Quitar los filtros\"\n },\n \"chips\": {\n \"applied_one\": \"{{count}} filtro aplicado\",\n \"applied_other\": \"{{count}} filtros aplicados\",\n \"clearAll\": \"Limpiar todo\"\n },\n \"columnFilter\": {\n \"tooltip\": \"Filtrar (se aplica a todos los registros, no solo a esta página)\",\n \"activeTooltip_one\": \"{{count}} filtro en esta columna\",\n \"activeTooltip_other\": \"{{count}} filtros en esta columna\",\n \"clear\": \"Limpiar esta columna\",\n \"contains\": \"Contiene\"\n },\n \"options\": {\n \"searchPlaceholder\": \"Buscar opción…\",\n \"empty\": \"Sin valores en este resultado\",\n \"noMatch\": \"Ninguna opción coincide con “{{text}}”\"\n },\n \"sort\": {\n \"tooltip\": \"Ordenar por esta columna\",\n \"ascending\": \"orden ascendente\",\n \"descending\": \"orden descendente\",\n \"clear\": \"Quitar el orden\"\n },\n \"goToPage\": {\n \"page\": \"Página\",\n \"of_one\": \"de {{total}}\",\n \"of_other\": \"de {{total}}\",\n \"ariaLabel\": \"Ir a la página\"\n },\n \"quickFilters\": {\n \"all\": \"Todos\",\n \"countAriaLabel\": \"{{count}} registros\"\n },\n \"detail\": {\n \"tooltip\": \"Ver detalle\",\n \"ariaLabel\": \"Ver el detalle de este registro\"\n },\n \"error\": {\n \"generic\": \"No pudimos consultar la tabla.\"\n },\n \"dateRange\": {\n \"label\": \"Personalizado\",\n \"title\": \"Rango de fechas\",\n \"from\": \"Desde\",\n \"to\": \"Hasta\",\n \"fromHelp\": \"Desde el inicio de ese día\",\n \"toHelp\": \"Hasta el final de ese día\",\n \"invalid\": \"«Hasta» no puede ser antes de «Desde»\",\n \"summary\": \"{{from}} – {{to}}\",\n \"today\": \"hoy\",\n \"clear\": \"Quitar\",\n \"apply\": \"Aplicar\"\n },\n \"audit\": {\n \"columns\": {\n \"occurredAt\": \"Fecha y hora\",\n \"event\": \"Qué pasó\",\n \"result\": \"Resultado\",\n \"actor\": \"Quién\"\n },\n \"systemActor\": \"Proceso automático\",\n \"eventFilterCaption\": \"Filtrar por evento\",\n \"count_one\": \"{{count}} evento\",\n \"count_other\": \"{{count}} eventos\",\n \"empty\": \"Todavía no hay eventos registrados.\"\n }\n },\n \"common\": {\n \"actions\": {\n \"cancel\": \"Cancelar\",\n \"confirm\": \"Confirmar\",\n \"retry\": \"Intentar de nuevo\",\n \"download\": \"Descargar\",\n \"close\": \"Cerrar\",\n \"back\": \"Regresar\",\n \"reload\": \"Recargar la página\"\n },\n \"status\": {\n \"loading\": \"Cargando…\",\n \"noData\": \"Sin datos\"\n },\n \"form\": {\n \"showPassword\": \"Mostrar contraseña\",\n \"hidePassword\": \"Ocultar contraseña\",\n \"clear\": \"Borrar el texto\",\n \"selectPlaceholder\": \"Elija una opción\",\n \"copy\": \"Copiar\",\n \"copied\": \"Copiado\",\n \"noOptions\": \"Ninguna opción coincide\",\n \"loadingOptions\": \"Cargando opciones…\",\n \"pickColor\": \"Elegir el color\",\n \"colorContrast\": \"Contraste sobre blanco: {{ratio}}:1\",\n \"colorLowContrast\": \"Con ese color el texto casi no se leería sobre blanco. {{suggestion}} es el mismo tono, más oscuro.\",\n \"colorUseSuggestion\": \"Usar {{color}}\"\n },\n \"wizard\": {\n \"next\": \"Siguiente\",\n \"back\": \"Anterior\",\n \"skip\": \"Omitir este paso\",\n \"optional\": \"Opcional\",\n \"skipped\": \"Omitido\",\n \"stepOf\": \"Paso {{current}} de {{total}}\"\n },\n \"language\": {\n \"switchTo\": {\n \"es\": \"Cambiar a español\",\n \"en\": \"Cambiar a inglés\"\n }\n },\n \"colorScheme\": {\n \"label\": \"Claro u oscuro\",\n \"switchTo\": {\n \"light\": \"Cambiar a modo claro\",\n \"dark\": \"Cambiar a modo oscuro\"\n }\n },\n \"toast\": {\n \"done\": \"Listo\",\n \"error\": \"Ocurrió un error\",\n \"warning\": \"Atención\",\n \"info\": \"Aviso\",\n \"working\": \"Procesando…\"\n },\n \"waiting\": {\n \"title\": \"Un momento\",\n \"text\": \"Estamos procesando su solicitud. No cierre esta ventana.\"\n },\n \"newBuild\": {\n \"message\": \"Hay una versión nueva del sistema. Recargue la página para usarla.\",\n \"short\": \"Versión nueva disponible\",\n \"reload\": \"Recargar\"\n },\n \"install\": {\n \"banner\": \"Instale esta aplicación en su equipo para abrirla más rápido\",\n \"action\": \"Instalar\",\n \"howTo\": \"Cómo instalar\",\n \"dismiss\": \"Ahora no\",\n \"gotIt\": \"Entendido\",\n \"menuLabel\": \"Instalar la app\",\n \"iosModern\": {\n \"title\": \"Instalar en su iPhone o iPad\",\n \"step1\": \"Toque ⋯ (los tres puntos) junto a la barra de direcciones.\",\n \"step2\": \"Toque «Compartir».\",\n \"step3\": \"Toque «Agregar a inicio».\",\n \"step4\": \"Deje activado «Abrir como app web» y toque «Agregar».\"\n },\n \"iosClassic\": {\n \"title\": \"Instalar en su iPhone\",\n \"step1\": \"Toque el botón Compartir (el cuadro con una flecha hacia arriba).\",\n \"step2\": \"Toque «Agregar a inicio».\",\n \"step3\": \"Toque «Agregar».\"\n },\n \"androidFirefox\": {\n \"title\": \"Instalar en Firefox\",\n \"step1\": \"Toque el menú ⋮.\",\n \"step2\": \"Toque «Instalar» o «Agregar a pantalla de inicio».\",\n \"step3\": \"Confirme.\"\n }\n },\n \"upload\": {\n \"hint\": \"Arrastre aquí su archivo\",\n \"hintMultiple\": \"Arrastre aquí sus archivos\",\n \"choose\": \"Elegir archivo\",\n \"chooseMultiple\": \"Elegir archivos\",\n \"remove\": \"Quitar {{name}}\",\n \"invalidTitle\": \"Archivo no permitido\",\n \"invalidType\": \"«{{name}}» no es de un tipo permitido. Se aceptan: {{accept}}.\",\n \"tooLarge\": \"«{{name}}» pesa más de {{max}}.\",\n \"tooLargeShort\": \"Pesa más de lo permitido\",\n \"totalTooLargeShort\": \"Entre todos pesan más de lo permitido\",\n \"tooManyFiles_one\": \"Se puede subir {{count}} archivo como máximo.\",\n \"tooManyFiles_other\": \"Se pueden subir {{count}} archivos como máximo.\",\n \"maxSizeHint\": \"Hasta {{max}} por archivo\",\n \"maxTotalSizeHint\": \"Hasta {{max}} entre todos\",\n \"maxFilesHint_one\": \"Hasta {{count}} archivo\",\n \"maxFilesHint_other\": \"Hasta {{count}} archivos\",\n \"summary_one\": \"{{count}} archivo · {{total}}\",\n \"summary_other\": \"{{count}} archivos · {{total}}\",\n \"summaryOf_one\": \"{{count}} archivo · {{total}} de {{max}}\",\n \"summaryOf_other\": \"{{count}} archivos · {{total}} de {{max}}\",\n \"pond\": {\n \"loading\": \"Cargando\",\n \"loadError\": \"No se pudo cargar\",\n \"waitingForSize\": \"Calculando el peso\",\n \"sizeNotAvailable\": \"Peso no disponible\",\n \"invalidField\": \"Hay archivos que no son válidos\",\n \"processing\": \"Subiendo\",\n \"processingComplete\": \"Subido\",\n \"processingAborted\": \"Subida cancelada\",\n \"processingError\": \"No se pudo subir\",\n \"revertError\": \"No se pudo deshacer\",\n \"removeError\": \"No se pudo quitar\",\n \"tapToCancel\": \"toque para cancelar\",\n \"tapToRetry\": \"toque para reintentar\",\n \"tapToUndo\": \"toque para deshacer\",\n \"remove\": \"Quitar\",\n \"abortLoad\": \"Cancelar\",\n \"retryLoad\": \"Reintentar\",\n \"abortProcessing\": \"Cancelar\",\n \"undoProcessing\": \"Deshacer\",\n \"retryProcessing\": \"Reintentar\",\n \"processItem\": \"Subir\"\n }\n },\n \"download\": {\n \"formats\": {\n \"pdf\": {\n \"label\": \"PDF\",\n \"hint\": \"Para leer, imprimir o archivar\"\n },\n \"xlsx\": {\n \"label\": \"Excel\",\n \"hint\": \"Para filtrar y cruzar con otros datos\"\n },\n \"csv\": {\n \"label\": \"CSV\",\n \"hint\": \"Texto plano, para importar a otro sistema\"\n }\n }\n },\n \"downloads\": {\n \"title\": \"Descargas\",\n \"summaryDownloading_one\": \"Descargando {{count}} archivo…\",\n \"summaryDownloading_other\": \"Descargando {{count}} archivos…\",\n \"summaryCompleted_one\": \"{{count}} descarga lista\",\n \"summaryCompleted_other\": \"{{count}} descargas listas\",\n \"summaryFailed_one\": \"{{count}} descarga no se pudo completar\",\n \"summaryFailed_other\": \"{{count}} descargas no se pudieron completar\",\n \"collapse\": \"Minimizar\",\n \"expand\": \"Ver descargas\",\n \"close\": \"Cerrar\",\n \"cancel\": \"Cancelar descarga\",\n \"cancelOf\": \"Cancelar la descarga de {{name}}\",\n \"completedOf\": \"{{name}} descargado\",\n \"failedOf\": \"No se pudo descargar {{name}}\",\n \"canceledOf\": \"Se canceló la descarga de {{name}}\",\n \"preparing\": \"Preparando el archivo…\",\n \"progressOf\": \"{{loaded}} de {{total}}\",\n \"progressLoaded\": \"{{loaded}} descargados\",\n \"failed\": \"No se pudo descargar\",\n \"canceled\": \"Cancelada\"\n },\n \"charts\": {\n \"viewAsTable\": \"Ver como tabla\",\n \"viewAsChart\": \"Ver como gráfica\",\n \"less\": \"Menos\",\n \"more\": \"Más\",\n \"day\": \"Día\"\n }\n },\n \"layout\": {\n \"skipToContent\": \"Saltar al contenido\",\n \"barActions\": {\n \"more\": \"Más acciones\"\n },\n \"drawer\": {\n \"open\": \"Abrir el menú\",\n \"ariaLabel\": \"Menú principal\",\n \"expand\": \"Abrir el menú lateral\",\n \"collapse\": \"Encoger el menú lateral\",\n \"more\": \"Más\",\n \"showTabs\": \"Ver las pestañas\",\n \"editTabs\": \"Editar la barra\",\n \"editTabsHint\": \"Elija hasta {{count}} pantallas para la barra de abajo; las demás quedan en «Más».\",\n \"tabInBar\": \"{{label}} en la barra\",\n \"resetTabs\": \"Como venía\",\n \"doneEditing\": \"Listo\",\n \"scrollDown\": \"Ver más del menú\",\n \"scrollUp\": \"Ver lo de arriba del menú\"\n },\n \"account\": {\n \"open\": \"Abrir el menú de su cuenta\",\n \"myProfile\": \"Mi perfil\"\n },\n \"settings\": {\n \"open\": \"Ajustes\",\n \"language\": \"Idioma\",\n \"colorScheme\": \"Claro u oscuro\",\n \"light\": \"Claro\",\n \"dark\": \"Oscuro\",\n \"system\": \"Automático\"\n },\n \"fontSize\": {\n \"label\": \"Tamaño de letra\",\n \"normal\": \"Normal\",\n \"large\": \"Grande\",\n \"xLarge\": \"Muy grande\"\n },\n \"languageName\": {\n \"es\": \"Español\",\n \"en\": \"English\"\n },\n \"breadcrumbs\": \"Dónde está\",\n \"signOut\": \"Salir\",\n \"navigating\": \"Cargando la pantalla\"\n },\n \"auth\": {\n \"signIn\": {\n \"title\": \"Iniciar sesión\",\n \"subtitle\": \"Entre con su correo y su contraseña.\",\n \"email\": \"Correo electrónico\",\n \"password\": \"Contraseña\",\n \"remember\": \"Mantener la sesión abierta en este equipo\",\n \"submit\": \"Entrar\",\n \"forgot\": \"¿Olvidó su contraseña?\"\n },\n \"forgotPassword\": {\n \"title\": \"Recuperar su contraseña\",\n \"subtitle\": \"Escriba el correo con el que entra y le enviaremos un enlace para elegir una contraseña nueva.\",\n \"email\": \"Correo electrónico\",\n \"submit\": \"Enviar el enlace\",\n \"sentTitle\": \"Revise su correo\",\n \"sent\": \"Si {{email}} tiene una cuenta, le llegará un enlace en unos minutos. Si no lo ve, revise el correo no deseado.\",\n \"backToSignIn\": \"Regresar a iniciar sesión\"\n },\n \"changePassword\": {\n \"title\": \"Cambiar contraseña\",\n \"current\": \"Contraseña actual\",\n \"next\": \"Contraseña nueva\",\n \"confirm\": \"Repita la contraseña nueva\",\n \"submit\": \"Cambiar contraseña\",\n \"mismatch\": \"Las dos contraseñas nuevas no son iguales.\",\n \"sameAsCurrent\": \"La contraseña nueva debe ser distinta de la actual.\",\n \"notAccepted\": \"La contraseña nueva no cumple con lo que se pide abajo.\",\n \"done\": \"Su contraseña cambió. La próxima vez entre con la nueva.\"\n },\n \"passwordRules\": {\n \"title\": \"La contraseña nueva debe tener\",\n \"minLength\": \"Al menos {{count}} caracteres\",\n \"lowercase\": \"Una letra minúscula\",\n \"uppercase\": \"Una letra mayúscula\",\n \"number\": \"Un número\",\n \"symbol\": \"Un símbolo, por ejemplo ! ? # -\",\n \"met\": \"ya cumple\",\n \"missing\": \"falta\"\n }\n },\n \"access\": {\n \"permissions\": {\n \"countOf\": \"{{count}} de {{total}}\",\n \"allOf\": \"Todos los permisos de {{group}}\"\n }\n },\n \"metricCard\": {\n \"trend\": {\n \"up\": \"Subió\",\n \"down\": \"Bajó\",\n \"flat\": \"Sin cambio\"\n }\n },\n \"errorPage\": {\n \"goHome\": \"Ir al inicio\",\n \"retry\": \"Intentar de nuevo\",\n \"reload\": \"Recargar\",\n \"newVersion\": {\n \"title\": \"Hay una versión nueva\",\n \"text\": \"Se publicó una actualización mientras tenía la página abierta. Recargue para seguir donde estaba.\"\n },\n \"notFound\": {\n \"title\": \"Esta página no existe\",\n \"text\": \"La dirección no corresponde a ninguna pantalla. Puede que el enlace esté incompleto o que la página ya no exista.\"\n },\n \"forbidden\": {\n \"title\": \"No tiene permiso para ver esta pantalla\",\n \"text\": \"Su usuario no tiene acceso a esta información. Si considera que es un error, comuníquese con el administrador.\"\n },\n \"serverError\": {\n \"title\": \"Algo falló de nuestro lado\",\n \"text\": \"No pudimos mostrar esta pantalla. Inténtelo de nuevo en un momento; si el problema continúa, comuníquese con soporte.\"\n }\n },\n \"problems\": {\n \"offline\": {\n \"title\": \"Sin conexión\",\n \"detail\": \"No pudimos comunicarnos con el servidor. Revise su conexión e inténtelo de nuevo.\"\n },\n \"unauthenticated\": {\n \"title\": \"Necesita iniciar sesión\",\n \"detail\": \"Su sesión no es válida o ya terminó. Inicie sesión de nuevo para continuar.\"\n },\n \"forbidden\": {\n \"title\": \"No tiene permiso para realizar esta acción\",\n \"detail\": \"Su usuario no tiene acceso a esta información u operación. Si considera que es un error, comuníquese con el administrador.\"\n },\n \"notFound\": {\n \"title\": \"No encontramos lo que busca\",\n \"detail\": \"La dirección o el registro que solicitó no existe o ya no está disponible.\"\n },\n \"conflict\": {\n \"title\": \"No se puede realizar en este momento\",\n \"detail\": \"El registro cambió o no está en el estado que se necesita. Actualice la pantalla e inténtelo de nuevo.\"\n },\n \"payloadTooLarge\": {\n \"title\": \"Lo que envió es demasiado grande\",\n \"detail\": \"El archivo o los datos superan el tamaño máximo permitido.\"\n },\n \"csrfTokenMismatch\": {\n \"title\": \"La página necesita actualizarse\",\n \"detail\": \"Pasó demasiado tiempo o la página quedó desactualizada. Recárguela e inténtelo de nuevo.\"\n },\n \"validation\": {\n \"title\": \"Los datos enviados no son válidos\",\n \"detail\": \"Revise los campos marcados e inténtelo de nuevo.\"\n },\n \"tooManyRequests\": {\n \"title\": \"Demasiados intentos seguidos\",\n \"detail\": \"Hizo muchas solicitudes en poco tiempo. Espere un momento e inténtelo de nuevo.\"\n },\n \"serverError\": {\n \"title\": \"No pudimos completar su solicitud\",\n \"detail\": \"Ocurrió un problema en el servidor. Inténtelo en un momento; si el problema continúa, comuníquese con soporte.\"\n },\n \"serviceUnavailable\": {\n \"title\": \"El servicio no está disponible\",\n \"detail\": \"El servidor no está respondiendo en este momento. Inténtelo más tarde.\"\n },\n \"generic\": {\n \"title\": \"No pudimos completar su solicitud\",\n \"detail\": \"Ocurrió un problema con su solicitud. Si continúa, comuníquese con soporte.\"\n },\n \"gone\": {\n \"title\": \"Esto ya no está disponible\",\n \"detail\": \"El enlace o el registro venció o ya se usó.\"\n }\n },\n \"validation\": {\n \"default\": \"El valor no es válido.\",\n \"required\": \"Este campo es obligatorio.\",\n \"notType\": \"El valor no tiene el formato esperado.\",\n \"oneOf\": \"Elija una de las opciones.\",\n \"notOneOf\": \"Ese valor no está permitido.\",\n \"email\": \"Escriba un correo válido, por ejemplo nombre@empresa.com.\",\n \"url\": \"Escriba una dirección web válida.\",\n \"uuid\": \"El identificador no es válido.\",\n \"stringLength_one\": \"Escriba exactamente {{count}} carácter.\",\n \"stringLength_other\": \"Escriba exactamente {{count}} caracteres.\",\n \"stringMin_one\": \"Escriba al menos {{count}} carácter.\",\n \"stringMin_other\": \"Escriba al menos {{count}} caracteres.\",\n \"stringMax_one\": \"Escriba como máximo {{count}} carácter.\",\n \"stringMax_other\": \"Escriba como máximo {{count}} caracteres.\",\n \"matches\": \"El formato no es válido.\",\n \"trim\": \"Quite los espacios al inicio y al final.\",\n \"lowercase\": \"Escríbalo en minúsculas.\",\n \"uppercase\": \"Escríbalo en mayúsculas.\",\n \"numberMin\": \"No puede ser menor que {{min}}.\",\n \"numberMax\": \"No puede ser mayor que {{max}}.\",\n \"lessThan\": \"Debe ser menor que {{less}}.\",\n \"moreThan\": \"Debe ser mayor que {{more}}.\",\n \"positive\": \"Debe ser un número positivo.\",\n \"negative\": \"Debe ser un número negativo.\",\n \"integer\": \"Debe ser un número entero.\",\n \"dateMin\": \"La fecha no puede ser anterior a {{min}}.\",\n \"dateMax\": \"La fecha no puede ser posterior a {{max}}.\",\n \"arrayMin_one\": \"Elija al menos {{count}} opción.\",\n \"arrayMin_other\": \"Elija al menos {{count}} opciones.\",\n \"arrayMax_one\": \"Elija como máximo {{count}} opción.\",\n \"arrayMax_other\": \"Elija como máximo {{count}} opciones.\",\n \"arrayLength_one\": \"Elija exactamente {{count}} opción.\",\n \"arrayLength_other\": \"Elija exactamente {{count}} opciones.\"\n },\n \"dictionaries\": {\n \"unknown\": {\n \"label\": \"Sin estatus\",\n \"tooltip\": \"\"\n }\n },\n \"security\": {\n \"passkeys\": {\n \"cancelled\": \"No se usó ninguna llave de acceso. Puede intentarlo de nuevo.\",\n \"alreadyRegistered\": \"Esa llave de acceso ya está registrada en su cuenta.\",\n \"wrongDomain\": \"Las llaves de acceso solo funcionan en la dirección oficial del sistema.\",\n \"failed\": \"El navegador no pudo usar la llave de acceso.\",\n \"unsupported\": \"Este navegador no admite llaves de acceso.\"\n },\n \"twoStep\": {\n \"title\": \"Verificación en dos pasos\",\n \"description\": \"Además de su contraseña, un segundo paso al entrar. Si alguien obtiene su contraseña, no podrá entrar sin él.\",\n \"active\": \"Activa\",\n \"activeSince\": \"Desde {{date}}\",\n \"inactive\": \"Sin activar\",\n \"inactiveTooltip\": \"Su cuenta solo está protegida por la contraseña.\",\n \"passkeysTitle\": \"Llaves de acceso\",\n \"add\": \"Agregar\",\n \"passkeysEmpty\": \"La forma más segura: entra con la huella, el rostro o el PIN de su equipo.\",\n \"passkeyUsed\": \"Usada por última vez {{date}}\",\n \"passkeyCreated\": \"Creada {{date}}\",\n \"removePasskey\": \"Quitar {{name}}\",\n \"removePasskeyTooltip\": \"Quitar esta llave\",\n \"appTitle\": \"App autenticadora\",\n \"appActiveSince\": \"Activa desde {{date}}\",\n \"appDescription\": \"Un código de 6 dígitos que cambia cada 30 segundos.\",\n \"activate\": \"Activar\",\n \"deactivate\": \"Desactivar\",\n \"codesTitle\": \"Códigos de recuperación\",\n \"codesRemaining_one\": \"Le queda {{count}} código sin usar.\",\n \"codesRemaining_other\": \"Le quedan {{count}} códigos sin usar.\",\n \"codesNone\": \"Ya no le quedan códigos: genere otros.\",\n \"regenerate\": \"Generar nuevos\",\n \"passwordLabel\": \"Su contraseña actual\",\n \"passwordRequired\": \"Escriba su contraseña.\",\n \"disableTitle\": \"¿Desactivar la app autenticadora?\",\n \"disableText\": \"Si no tiene una llave de acceso, su cuenta quedará protegida solo por la contraseña y sus códigos de recuperación dejarán de servir.\",\n \"removeTitle\": \"¿Quitar «{{name}}»?\",\n \"removeText\": \"Ya no podrá entrar con esta llave. Bórrela también de su equipo o de su gestor de contraseñas.\",\n \"remove\": \"Quitar\",\n \"regenerateTitle\": \"¿Generar códigos nuevos?\",\n \"regenerateText\": \"Los códigos que tenga ahora dejarán de servir.\",\n \"generate\": \"Generar\",\n \"passkeyDialogTitle\": \"Agregar una llave de acceso\",\n \"passkeyDialogText\": \"Una llave de acceso usa la huella, el rostro o el PIN de este equipo o de su teléfono. No se puede robar con una página falsa y le permite entrar sin escribir su contraseña.\",\n \"passkeyName\": \"Nombre para reconocerla (opcional)\",\n \"passkeyNamePlaceholder\": \"Por ejemplo: MacBook del trabajo\",\n \"createPasskey\": \"Crear llave\",\n \"totpDialogTitle\": \"Activar la app autenticadora\",\n \"totpStep1\": \"1. Abra su app (Google Authenticator, Microsoft Authenticator, 1Password…) y escanee este código.\",\n \"totpQrAlt\": \"Código QR para la app autenticadora\",\n \"totpCantScan\": \"¿No puede escanear? Escriba esta clave en su app:\",\n \"totpStep2\": \"2. Escriba el código de 6 dígitos que muestra la app.\",\n \"codeLabel\": \"Código\",\n \"codeRequired\": \"Escriba el código de 6 dígitos.\",\n \"codeFormat\": \"Son 6 dígitos, como aparecen en su app.\",\n \"passkeyAdded\": \"Llave de acceso agregada\",\n \"passkeyRemoved\": \"Llave de acceso quitada\",\n \"appActivated\": \"App autenticadora activada\",\n \"appDeactivated\": \"App autenticadora desactivada\"\n },\n \"recoveryCodes\": {\n \"title\": \"Guarde sus códigos de recuperación\",\n \"warning\": \"Es la única vez que los verá. Con uno de ellos podrá entrar si pierde su teléfono o su llave de acceso; cada uno sirve una sola vez.\",\n \"copyAll\": \"Copiar todos\",\n \"download\": \"Descargar .txt\",\n \"saved\": \"Ya los guardé en un lugar seguro\",\n \"done\": \"Listo\",\n \"fileTitle\": \"{{appName}} · códigos de recuperación\",\n \"fileAccount\": \"Cuenta: {{account}}\",\n \"fileGenerated\": \"Generados: {{date}}\",\n \"fileHowTo\": \"Cada código sirve UNA sola vez para entrar si no tiene su teléfono o su llave de acceso.\",\n \"fileKeepSafe\": \"Guárdelos en un lugar seguro (un gestor de contraseñas o impresos bajo llave).\",\n \"fileName\": \"codigos-recuperacion-{{date}}.txt\"\n },\n \"secondStep\": {\n \"intro\": \"Su cuenta tiene verificación en dos pasos. Confirme que es usted para terminar de entrar.\",\n \"usePasskey\": \"Usar mi llave de acceso\",\n \"or\": \"o\",\n \"appCodeLabel\": \"Código de su app autenticadora\",\n \"appCodeHelp\": \"Ábrala (Google Authenticator, Microsoft Authenticator, 1Password…) y escriba los 6 dígitos.\",\n \"recoveryCodeLabel\": \"Código de recuperación\",\n \"recoveryCodeHelp\": \"Uno de los códigos que guardó al activar la verificación. Cada uno sirve una sola vez.\",\n \"recoveryCodeRequired\": \"Escriba uno de sus códigos de recuperación.\",\n \"recoveryCodeFormat\": \"Tienen la forma ABCDE-FGHJK.\",\n \"confirm\": \"Confirmar\",\n \"useRecovery\": \"No tengo mi teléfono: usar un código de recuperación\",\n \"useApp\": \"Usar el código de mi app autenticadora\",\n \"otherAccount\": \"Entrar con otra cuenta\",\n \"remainingAttempts_one\": \"Le queda {{count}} intento.\",\n \"remainingAttempts_other\": \"Le quedan {{count}} intentos.\"\n },\n \"sessions\": {\n \"title\": \"Mis sesiones\",\n \"description\": \"Dónde tiene la cuenta abierta. Si no reconoce un equipo, cierre la sesión en los demás y cambie su contraseña.\",\n \"current\": \"Este equipo\",\n \"device\": \"{{browser}} en {{platform}}\",\n \"otherBrowser\": \"Otro navegador\",\n \"otherPlatform\": \"otro sistema\",\n \"since\": \"Desde {{date}}\",\n \"network\": \"red {{network}}\",\n \"onlyThis\": \"Solo tiene la sesión abierta en este equipo.\",\n \"closeOthers\": \"Cerrar la sesión en los demás equipos\",\n \"confirmTitle\": \"¿Cerrar la sesión en los demás equipos?\",\n \"confirmText_one\": \"Se cerrará en {{count}} equipo. Este sigue dentro, y en el otro tendrá que volver a entrar con su contraseña.\",\n \"confirmText_other\": \"Se cerrará en {{count}} equipos. Este sigue dentro, y en los demás tendrá que volver a entrar con su contraseña.\",\n \"confirm\": \"Cerrar las demás\",\n \"closed_one\": \"Se cerró la sesión en {{count}} equipo\",\n \"closed_other\": \"Se cerró la sesión en {{count}} equipos\",\n \"noneClosed\": \"No había sesiones abiertas en otros equipos\"\n },\n \"activity\": {\n \"title\": \"Actividad de la cuenta\",\n \"description\": \"Lo último que pasó con su cuenta. Si ve algo que no fue usted, cambie su contraseña y cierre la sesión en los demás equipos.\",\n \"empty\": \"Todavía no hay actividad.\",\n \"from\": \"{{device}} · red {{network}}\",\n \"kinds\": {\n \"signedIn\": \"Entró\",\n \"signInFailed\": \"Intento de entrar con contraseña equivocada\",\n \"signedOut\": \"Salió\",\n \"passwordChanged\": \"Cambió su contraseña\",\n \"twoStepEnabled\": \"Activó la verificación en dos pasos\",\n \"twoStepDisabled\": \"Desactivó la verificación en dos pasos\",\n \"passkeyAdded\": \"Agregó una llave de acceso\",\n \"passkeyRemoved\": \"Quitó una llave de acceso\",\n \"recoveryCodesGenerated\": \"Generó códigos de recuperación nuevos\",\n \"recoveryCodeUsed\": \"Entró con un código de recuperación\",\n \"sessionsClosed\": \"Cerró la sesión en los demás equipos\"\n }\n }\n },\n \"calendar\": {\n \"previous\": \"Anterior\",\n \"next\": \"Siguiente\",\n \"today\": \"Hoy\",\n \"view\": \"Vista del calendario\",\n \"views\": {\n \"month\": \"Mes\",\n \"week\": \"Semana\",\n \"day\": \"Día\",\n \"list\": \"Lista\"\n }\n },\n \"kanban\": {\n \"instructions\": \"Para mover una tarjeta con el teclado: espacio para levantarla, flechas para moverla y espacio para soltarla. Escape la deja donde estaba.\",\n \"lifted\": \"Levantó «{{title}}», en {{column}}, posición {{position}}.\",\n \"moving\": \"Va a {{column}}, posición {{position}}.\",\n \"dropped\": \"Soltó «{{title}}» en {{column}}, posición {{position}}.\",\n \"cancelled\": \"«{{title}}» se quedó donde estaba.\",\n \"overLimit\": \"Pasó el tope de {{limit}} tarjetas.\",\n \"empty\": \"Sin tarjetas. Arrastre una aquí.\"\n },\n \"inbox\": {\n \"listLabel\": \"Mensajes\",\n \"folders\": \"Carpetas\",\n \"empty\": \"No hay mensajes aquí.\",\n \"noMessageSelected\": \"Elija un mensaje para leerlo.\",\n \"star\": \"Destacar\",\n \"unstar\": \"Quitar de destacados\",\n \"unread\": \"Sin leer\",\n \"attachments\": \"Archivos adjuntos\",\n \"to\": \"Para: {{to}}\",\n \"back\": \"Regresar\",\n \"unreadCount_one\": \"{{count}} sin leer\",\n \"unreadCount_other\": \"{{count}} sin leer\"\n },\n \"account\": {\n \"reset\": {\n \"title\": \"Elegir una contraseña nueva\",\n \"subtitle\": \"Llegó aquí desde el enlace del correo. Elija la contraseña con la que va a entrar.\",\n \"done\": \"Ya puede entrar con su contraseña nueva.\",\n \"goToSignIn\": \"Ir a iniciar sesión\",\n \"missingToken\": \"Este enlace no sirve: le falta su clave. Pida uno nuevo desde «¿Olvidó su contraseña?».\",\n \"askAgain\": \"Pedir otro enlace\"\n },\n \"profile\": {\n \"tabs\": {\n \"label\": \"Partes de su perfil\",\n \"data\": \"Sus datos\",\n \"security\": \"Seguridad\",\n \"preferences\": \"Preferencias\"\n },\n \"data\": {\n \"title\": \"Sus datos\",\n \"text\": \"Así le ven las demás personas en la app.\",\n \"name\": \"Nombre\",\n \"email\": \"Correo\",\n \"emailHelp\": \"Es el correo con el que entra; para cambiarlo, pídalo a quien administra la app.\",\n \"phone\": \"Teléfono (opcional)\",\n \"phoneInvalid\": \"Escriba solo dígitos (de 7 a 15), con + al inicio si es de otro país.\",\n \"save\": \"Guardar\",\n \"saved\": \"Se guardaron sus datos.\"\n },\n \"password\": {\n \"title\": \"Contraseña\",\n \"text\": \"Use una contraseña que no use en otros sitios. Al cambiarla, la próxima vez entra con la nueva.\"\n },\n \"preferences\": {\n \"title\": \"Apariencia\",\n \"text\": \"Cómo se ve la app para usted: el tamaño de letra, el idioma y claro u oscuro se recuerdan en este navegador.\"\n }\n },\n \"demo\": {\n \"totpCode\": \"Usar el código de ejemplo\",\n \"totpCodeHelp\": \"Solo en la demostración: llena el código que enseñaría su app ahorita, por si no la tiene a la mano.\",\n \"passkeyNote\": \"En la demostración la llave se guarda de verdad en su llavero (iCloud, Google o su gestor de contraseñas), pero esta página la olvida al recargar. Cuando termine de probar, bórrela también de su llavero.\",\n \"securityNote\": \"Servidor de demostración: la verificación funciona con su teléfono de verdad (escanee el QR o cree una llave con su huella), pero vive en la memoria de la pestaña: al recargar se apaga. Salga y vuelva a entrar para ver el segundo paso.\",\n \"server\": {\n \"wrongCredentialsTitle\": \"No pudo entrar\",\n \"wrongCredentials\": \"En la demostración se entra con {{email}} y cualquier contraseña.\",\n \"noSessionTitle\": \"Su sesión terminó\",\n \"noSession\": \"Vuelva a entrar.\",\n \"validationTitle\": \"Revise los datos\",\n \"validationDetail\": \"Hay campos por corregir.\",\n \"passwordRequired\": \"Escriba su contraseña para confirmar.\",\n \"nameRequired\": \"Escriba su nombre.\",\n \"wrongCodeTitle\": \"El código no coincide\",\n \"wrongCode\": \"Revise que sea el código que enseña su app ahorita.\",\n \"expiredTitle\": \"Se acabó el tiempo\",\n \"expired\": \"Pasaron más de 5 minutos: vuelva a escribir su contraseña.\",\n \"lockedTitle\": \"Demasiados intentos\",\n \"locked\": \"Por seguridad hay que empezar de nuevo: vuelva a escribir su contraseña.\",\n \"defaultPasskeyName\": \"Llave {{number}}\",\n \"profileSaved\": \"Se guardaron sus datos.\"\n }\n }\n }\n});\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"es.js","sources":["../../src/locales/es.js"],"sourcesContent":["// Textos del paquete en español (el idioma por omisión). Cada proyecto puede cambiar cualquiera con `texts` o `translate`\n// de ToolkitProvider, con la misma forma: namespace, llaves anidadas, {{variable}} y plurales _one / _other.\nexport default Object.freeze({\n \"table\": {\n \"search\": {\n \"placeholder\": \"Buscar dentro de estos resultados…\"\n },\n \"count\": {\n \"records_one\": \"{{total}} registro\",\n \"records_other\": \"{{total}} registros\",\n \"capped\": \"más de {{total}} registros\",\n \"cappedTag\": \"total aproximado\",\n \"cappedTooltip\": \"Hay al menos estos registros y puede haber más: agregue un filtro para verlos todos.\"\n },\n \"empty\": {\n \"default\": \"Sin registros\",\n \"filtered\": \"Ningún registro cumple con los filtros aplicados.\",\n \"clearFilters\": \"Quitar los filtros\"\n },\n \"chips\": {\n \"applied_one\": \"{{count}} filtro aplicado\",\n \"applied_other\": \"{{count}} filtros aplicados\",\n \"clearAll\": \"Limpiar todo\"\n },\n \"columnFilter\": {\n \"tooltip\": \"Filtrar (se aplica a todos los registros, no solo a esta página)\",\n \"activeTooltip_one\": \"{{count}} filtro en esta columna\",\n \"activeTooltip_other\": \"{{count}} filtros en esta columna\",\n \"clear\": \"Limpiar esta columna\",\n \"contains\": \"Contiene\"\n },\n \"options\": {\n \"searchPlaceholder\": \"Buscar opción…\",\n \"empty\": \"Sin valores en este resultado\",\n \"noMatch\": \"Ninguna opción coincide con “{{text}}”\"\n },\n \"sort\": {\n \"tooltip\": \"Ordenar por esta columna\",\n \"ascending\": \"orden ascendente\",\n \"descending\": \"orden descendente\",\n \"clear\": \"Quitar el orden\"\n },\n \"selection\": {\n \"count_one\": \"{{total}} elegido\",\n \"count_other\": \"{{total}} elegidos\",\n \"allMatching\": \"Los {{total}} que coinciden están elegidos\",\n \"allMatchingCapped\": \"Todos los que coinciden están elegidos\",\n \"pageSelected_one\": \"Se eligió el único renglón de esta página.\",\n \"pageSelected_other\": \"Se eligieron los {{count}} de esta página.\",\n \"selectAllMatching\": \"Elegir los {{total}} que coinciden\",\n \"selectAllMatchingCapped\": \"Elegir todos los que coinciden\",\n \"onlyThisPage\": \"Elegir solo esta página\",\n \"clear\": \"Quitar selección\",\n \"summaryLabel\": \"Lo elegido\",\n \"actionsLabel\": \"Acciones de los elegidos\"\n },\n \"goToPage\": {\n \"page\": \"Página\",\n \"of_one\": \"de {{total}}\",\n \"of_other\": \"de {{total}}\",\n \"ariaLabel\": \"Ir a la página\"\n },\n \"quickFilters\": {\n \"all\": \"Todos\",\n \"countAriaLabel\": \"{{count}} registros\"\n },\n \"detail\": {\n \"tooltip\": \"Ver detalle\",\n \"ariaLabel\": \"Ver el detalle de este registro\"\n },\n \"error\": {\n \"generic\": \"No pudimos consultar la tabla.\"\n },\n \"dateRange\": {\n \"label\": \"Personalizado\",\n \"title\": \"Rango de fechas\",\n \"from\": \"Desde\",\n \"to\": \"Hasta\",\n \"fromHelp\": \"Desde el inicio de ese día\",\n \"toHelp\": \"Hasta el final de ese día\",\n \"invalid\": \"«Hasta» no puede ser antes de «Desde»\",\n \"summary\": \"{{from}} – {{to}}\",\n \"today\": \"hoy\",\n \"clear\": \"Quitar\",\n \"apply\": \"Aplicar\"\n },\n \"audit\": {\n \"columns\": {\n \"occurredAt\": \"Fecha y hora\",\n \"event\": \"Qué pasó\",\n \"result\": \"Resultado\",\n \"actor\": \"Quién\"\n },\n \"systemActor\": \"Proceso automático\",\n \"eventFilterCaption\": \"Filtrar por evento\",\n \"count_one\": \"{{count}} evento\",\n \"count_other\": \"{{count}} eventos\",\n \"empty\": \"Todavía no hay eventos registrados.\"\n }\n },\n \"common\": {\n \"actions\": {\n \"cancel\": \"Cancelar\",\n \"confirm\": \"Confirmar\",\n \"retry\": \"Intentar de nuevo\",\n \"download\": \"Descargar\",\n \"close\": \"Cerrar\",\n \"back\": \"Regresar\",\n \"reload\": \"Recargar la página\"\n },\n \"status\": {\n \"loading\": \"Cargando…\",\n \"noData\": \"Sin datos\"\n },\n \"form\": {\n \"showPassword\": \"Mostrar contraseña\",\n \"hidePassword\": \"Ocultar contraseña\",\n \"clear\": \"Borrar el texto\",\n \"selectPlaceholder\": \"Elija una opción\",\n \"copy\": \"Copiar\",\n \"copied\": \"Copiado\",\n \"noOptions\": \"Ninguna opción coincide\",\n \"loadingOptions\": \"Cargando opciones…\",\n \"pickColor\": \"Elegir el color\",\n \"colorContrast\": \"Contraste sobre blanco: {{ratio}}:1\",\n \"colorLowContrast\": \"Con ese color el texto casi no se leería sobre blanco. {{suggestion}} es el mismo tono, más oscuro.\",\n \"colorUseSuggestion\": \"Usar {{color}}\"\n },\n \"selection\": {\n \"clear\": \"Quitar selección\",\n \"actionsLabel\": \"Acciones de los elegidos\",\n \"summaryLabel\": \"Lo elegido\"\n },\n \"wizard\": {\n \"next\": \"Siguiente\",\n \"back\": \"Anterior\",\n \"skip\": \"Omitir este paso\",\n \"optional\": \"Opcional\",\n \"skipped\": \"Omitido\",\n \"stepOf\": \"Paso {{current}} de {{total}}\"\n },\n \"language\": {\n \"switchTo\": {\n \"es\": \"Cambiar a español\",\n \"en\": \"Cambiar a inglés\"\n },\n \"switchToNamed\": \"Cambiar a {{name}}\",\n \"choose\": \"Idioma: {{name}}. Elegir otro\"\n },\n \"colorScheme\": {\n \"label\": \"Claro u oscuro\",\n \"switchTo\": {\n \"light\": \"Cambiar a modo claro\",\n \"dark\": \"Cambiar a modo oscuro\"\n }\n },\n \"toast\": {\n \"done\": \"Listo\",\n \"error\": \"Ocurrió un error\",\n \"warning\": \"Atención\",\n \"info\": \"Aviso\",\n \"working\": \"Procesando…\"\n },\n \"waiting\": {\n \"title\": \"Un momento\",\n \"text\": \"Estamos procesando su solicitud. No cierre esta ventana.\"\n },\n \"newBuild\": {\n \"message\": \"Hay una versión nueva del sistema. Recargue la página para usarla.\",\n \"short\": \"Versión nueva disponible\",\n \"reload\": \"Recargar\"\n },\n \"install\": {\n \"banner\": \"Instale esta aplicación en su equipo para abrirla más rápido\",\n \"action\": \"Instalar\",\n \"howTo\": \"Cómo instalar\",\n \"dismiss\": \"Ahora no\",\n \"gotIt\": \"Entendido\",\n \"menuLabel\": \"Instalar la app\",\n \"iosModern\": {\n \"title\": \"Instalar en su iPhone o iPad\",\n \"step1\": \"Toque ⋯ (los tres puntos) junto a la barra de direcciones.\",\n \"step2\": \"Toque «Compartir».\",\n \"step3\": \"Toque «Agregar a inicio».\",\n \"step4\": \"Deje activado «Abrir como app web» y toque «Agregar».\"\n },\n \"iosClassic\": {\n \"title\": \"Instalar en su iPhone\",\n \"step1\": \"Toque el botón Compartir (el cuadro con una flecha hacia arriba).\",\n \"step2\": \"Toque «Agregar a inicio».\",\n \"step3\": \"Toque «Agregar».\"\n },\n \"androidFirefox\": {\n \"title\": \"Instalar en Firefox\",\n \"step1\": \"Toque el menú ⋮.\",\n \"step2\": \"Toque «Instalar» o «Agregar a pantalla de inicio».\",\n \"step3\": \"Confirme.\"\n }\n },\n \"upload\": {\n \"hint\": \"Arrastre aquí su archivo\",\n \"hintMultiple\": \"Arrastre aquí sus archivos\",\n \"choose\": \"Elegir archivo\",\n \"chooseMultiple\": \"Elegir archivos\",\n \"remove\": \"Quitar {{name}}\",\n \"invalidTitle\": \"Archivo no permitido\",\n \"invalidType\": \"«{{name}}» no es de un tipo permitido. Se aceptan: {{accept}}.\",\n \"tooLarge\": \"«{{name}}» pesa más de {{max}}.\",\n \"tooLargeShort\": \"Pesa más de lo permitido\",\n \"totalTooLargeShort\": \"Entre todos pesan más de lo permitido\",\n \"tooManyFiles_one\": \"Se puede subir {{count}} archivo como máximo.\",\n \"tooManyFiles_other\": \"Se pueden subir {{count}} archivos como máximo.\",\n \"maxSizeHint\": \"Hasta {{max}} por archivo\",\n \"maxTotalSizeHint\": \"Hasta {{max}} entre todos\",\n \"maxFilesHint_one\": \"Hasta {{count}} archivo\",\n \"maxFilesHint_other\": \"Hasta {{count}} archivos\",\n \"summary_one\": \"{{count}} archivo · {{total}}\",\n \"summary_other\": \"{{count}} archivos · {{total}}\",\n \"summaryOf_one\": \"{{count}} archivo · {{total}} de {{max}}\",\n \"summaryOf_other\": \"{{count}} archivos · {{total}} de {{max}}\",\n \"pond\": {\n \"loading\": \"Cargando\",\n \"loadError\": \"No se pudo cargar\",\n \"waitingForSize\": \"Calculando el peso\",\n \"sizeNotAvailable\": \"Peso no disponible\",\n \"invalidField\": \"Hay archivos que no son válidos\",\n \"processing\": \"Subiendo\",\n \"processingComplete\": \"Subido\",\n \"processingAborted\": \"Subida cancelada\",\n \"processingError\": \"No se pudo subir\",\n \"revertError\": \"No se pudo deshacer\",\n \"removeError\": \"No se pudo quitar\",\n \"tapToCancel\": \"toque para cancelar\",\n \"tapToRetry\": \"toque para reintentar\",\n \"tapToUndo\": \"toque para deshacer\",\n \"remove\": \"Quitar\",\n \"abortLoad\": \"Cancelar\",\n \"retryLoad\": \"Reintentar\",\n \"abortProcessing\": \"Cancelar\",\n \"undoProcessing\": \"Deshacer\",\n \"retryProcessing\": \"Reintentar\",\n \"processItem\": \"Subir\"\n }\n },\n \"download\": {\n \"formats\": {\n \"pdf\": {\n \"label\": \"PDF\",\n \"hint\": \"Para leer, imprimir o archivar\"\n },\n \"xlsx\": {\n \"label\": \"Excel\",\n \"hint\": \"Para filtrar y cruzar con otros datos\"\n },\n \"csv\": {\n \"label\": \"CSV\",\n \"hint\": \"Texto plano, para importar a otro sistema\"\n }\n }\n },\n \"downloads\": {\n \"title\": \"Descargas\",\n \"summaryDownloading_one\": \"Descargando {{count}} archivo…\",\n \"summaryDownloading_other\": \"Descargando {{count}} archivos…\",\n \"summaryCompleted_one\": \"{{count}} descarga lista\",\n \"summaryCompleted_other\": \"{{count}} descargas listas\",\n \"summaryFailed_one\": \"{{count}} descarga no se pudo completar\",\n \"summaryFailed_other\": \"{{count}} descargas no se pudieron completar\",\n \"collapse\": \"Minimizar\",\n \"expand\": \"Ver descargas\",\n \"close\": \"Cerrar\",\n \"cancel\": \"Cancelar descarga\",\n \"cancelOf\": \"Cancelar la descarga de {{name}}\",\n \"completedOf\": \"{{name}} descargado\",\n \"failedOf\": \"No se pudo descargar {{name}}\",\n \"canceledOf\": \"Se canceló la descarga de {{name}}\",\n \"preparing\": \"Preparando el archivo…\",\n \"progressOf\": \"{{loaded}} de {{total}}\",\n \"progressLoaded\": \"{{loaded}} descargados\",\n \"failed\": \"No se pudo descargar\",\n \"canceled\": \"Cancelada\"\n },\n \"charts\": {\n \"viewAsTable\": \"Ver como tabla\",\n \"legend\": \"Qué es cada serie\",\n \"viewAsChart\": \"Ver como gráfica\",\n \"less\": \"Menos\",\n \"more\": \"Más\",\n \"day\": \"Día\"\n }\n },\n \"layout\": {\n \"skipToContent\": \"Saltar al contenido\",\n \"barActions\": {\n \"more\": \"Más acciones\"\n },\n \"drawer\": {\n \"open\": \"Abrir el menú\",\n \"ariaLabel\": \"Menú principal\",\n \"expand\": \"Abrir el menú lateral\",\n \"collapse\": \"Encoger el menú lateral\",\n \"more\": \"Más\",\n \"showTabs\": \"Ver las pestañas\",\n \"editTabs\": \"Editar la barra\",\n \"editTabsHint\": \"Elija hasta {{count}} pantallas para la barra de abajo; las demás quedan en «Más».\",\n \"tabInBar\": \"{{label}} en la barra\",\n \"resetTabs\": \"Como venía\",\n \"doneEditing\": \"Listo\",\n \"scrollDown\": \"Ver más del menú\",\n \"scrollUp\": \"Ver lo de arriba del menú\"\n },\n \"account\": {\n \"open\": \"Abrir el menú de su cuenta\",\n \"myProfile\": \"Mi perfil\"\n },\n \"settings\": {\n \"open\": \"Ajustes\",\n \"language\": \"Idioma\",\n \"colorScheme\": \"Claro u oscuro\",\n \"light\": \"Claro\",\n \"dark\": \"Oscuro\",\n \"system\": \"Automático\"\n },\n \"fontSize\": {\n \"label\": \"Tamaño de letra\",\n \"normal\": \"Normal\",\n \"large\": \"Grande\",\n \"xLarge\": \"Muy grande\"\n },\n \"languageName\": {\n \"es\": \"Español\",\n \"en\": \"English\"\n },\n \"breadcrumbs\": \"Dónde está\",\n \"signOut\": \"Salir\",\n \"navigating\": \"Cargando la pantalla\"\n },\n \"auth\": {\n \"signIn\": {\n \"title\": \"Iniciar sesión\",\n \"subtitle\": \"Entre con su correo y su contraseña.\",\n \"email\": \"Correo electrónico\",\n \"password\": \"Contraseña\",\n \"remember\": \"Mantener la sesión abierta en este equipo\",\n \"submit\": \"Entrar\",\n \"forgot\": \"¿Olvidó su contraseña?\"\n },\n \"forgotPassword\": {\n \"title\": \"Recuperar su contraseña\",\n \"subtitle\": \"Escriba el correo con el que entra y le enviaremos un enlace para elegir una contraseña nueva.\",\n \"email\": \"Correo electrónico\",\n \"submit\": \"Enviar el enlace\",\n \"sentTitle\": \"Revise su correo\",\n \"sent\": \"Si {{email}} tiene una cuenta, le llegará un enlace en unos minutos. Si no lo ve, revise el correo no deseado.\",\n \"backToSignIn\": \"Regresar a iniciar sesión\"\n },\n \"changePassword\": {\n \"title\": \"Cambiar contraseña\",\n \"current\": \"Contraseña actual\",\n \"next\": \"Contraseña nueva\",\n \"confirm\": \"Repita la contraseña nueva\",\n \"submit\": \"Cambiar contraseña\",\n \"mismatch\": \"Las dos contraseñas nuevas no son iguales.\",\n \"sameAsCurrent\": \"La contraseña nueva debe ser distinta de la actual.\",\n \"notAccepted\": \"La contraseña nueva no cumple con lo que se pide abajo.\",\n \"done\": \"Su contraseña cambió. La próxima vez entre con la nueva.\"\n },\n \"passwordRules\": {\n \"title\": \"La contraseña nueva debe tener\",\n \"minLength\": \"Al menos {{count}} caracteres\",\n \"lowercase\": \"Una letra minúscula\",\n \"uppercase\": \"Una letra mayúscula\",\n \"number\": \"Un número\",\n \"symbol\": \"Un símbolo, por ejemplo ! ? # -\",\n \"met\": \"ya cumple\",\n \"missing\": \"falta\"\n }\n },\n \"access\": {\n \"permissions\": {\n \"countOf\": \"{{count}} de {{total}}\",\n \"allOf\": \"Todos los permisos de {{group}}\"\n }\n },\n \"metricCard\": {\n \"trend\": {\n \"up\": \"Subió\",\n \"down\": \"Bajó\",\n \"flat\": \"Sin cambio\"\n }\n },\n \"errorPage\": {\n \"goHome\": \"Ir al inicio\",\n \"retry\": \"Intentar de nuevo\",\n \"reload\": \"Recargar\",\n \"newVersion\": {\n \"title\": \"Hay una versión nueva\",\n \"text\": \"Se publicó una actualización mientras tenía la página abierta. Recargue para seguir donde estaba.\"\n },\n \"notFound\": {\n \"title\": \"Esta página no existe\",\n \"text\": \"La dirección no corresponde a ninguna pantalla. Puede que el enlace esté incompleto o que la página ya no exista.\"\n },\n \"forbidden\": {\n \"title\": \"No tiene permiso para ver esta pantalla\",\n \"text\": \"Su usuario no tiene acceso a esta información. Si considera que es un error, comuníquese con el administrador.\"\n },\n \"serverError\": {\n \"title\": \"Algo falló de nuestro lado\",\n \"text\": \"No pudimos mostrar esta pantalla. Inténtelo de nuevo en un momento; si el problema continúa, comuníquese con soporte.\"\n }\n },\n \"problems\": {\n \"offline\": {\n \"title\": \"Sin conexión\",\n \"detail\": \"No pudimos comunicarnos con el servidor. Revise su conexión e inténtelo de nuevo.\"\n },\n \"unauthenticated\": {\n \"title\": \"Necesita iniciar sesión\",\n \"detail\": \"Su sesión no es válida o ya terminó. Inicie sesión de nuevo para continuar.\"\n },\n \"forbidden\": {\n \"title\": \"No tiene permiso para realizar esta acción\",\n \"detail\": \"Su usuario no tiene acceso a esta información u operación. Si considera que es un error, comuníquese con el administrador.\"\n },\n \"notFound\": {\n \"title\": \"No encontramos lo que busca\",\n \"detail\": \"La dirección o el registro que solicitó no existe o ya no está disponible.\"\n },\n \"conflict\": {\n \"title\": \"No se puede realizar en este momento\",\n \"detail\": \"El registro cambió o no está en el estado que se necesita. Actualice la pantalla e inténtelo de nuevo.\"\n },\n \"payloadTooLarge\": {\n \"title\": \"Lo que envió es demasiado grande\",\n \"detail\": \"El archivo o los datos superan el tamaño máximo permitido.\"\n },\n \"csrfTokenMismatch\": {\n \"title\": \"La página necesita actualizarse\",\n \"detail\": \"Pasó demasiado tiempo o la página quedó desactualizada. Recárguela e inténtelo de nuevo.\"\n },\n \"validation\": {\n \"title\": \"Los datos enviados no son válidos\",\n \"detail\": \"Revise los campos marcados e inténtelo de nuevo.\"\n },\n \"tooManyRequests\": {\n \"title\": \"Demasiados intentos seguidos\",\n \"detail\": \"Hizo muchas solicitudes en poco tiempo. Espere un momento e inténtelo de nuevo.\"\n },\n \"serverError\": {\n \"title\": \"No pudimos completar su solicitud\",\n \"detail\": \"Ocurrió un problema en el servidor. Inténtelo en un momento; si el problema continúa, comuníquese con soporte.\"\n },\n \"serviceUnavailable\": {\n \"title\": \"El servicio no está disponible\",\n \"detail\": \"El servidor no está respondiendo en este momento. Inténtelo más tarde.\"\n },\n \"generic\": {\n \"title\": \"No pudimos completar su solicitud\",\n \"detail\": \"Ocurrió un problema con su solicitud. Si continúa, comuníquese con soporte.\"\n },\n \"gone\": {\n \"title\": \"Esto ya no está disponible\",\n \"detail\": \"El enlace o el registro venció o ya se usó.\"\n }\n },\n \"validation\": {\n \"default\": \"El valor no es válido.\",\n \"required\": \"Este campo es obligatorio.\",\n \"notType\": \"El valor no tiene el formato esperado.\",\n \"oneOf\": \"Elija una de las opciones.\",\n \"notOneOf\": \"Ese valor no está permitido.\",\n \"email\": \"Escriba un correo válido, por ejemplo nombre@empresa.com.\",\n \"url\": \"Escriba una dirección web válida.\",\n \"uuid\": \"El identificador no es válido.\",\n \"stringLength_one\": \"Escriba exactamente {{count}} carácter.\",\n \"stringLength_other\": \"Escriba exactamente {{count}} caracteres.\",\n \"stringMin_one\": \"Escriba al menos {{count}} carácter.\",\n \"stringMin_other\": \"Escriba al menos {{count}} caracteres.\",\n \"stringMax_one\": \"Escriba como máximo {{count}} carácter.\",\n \"stringMax_other\": \"Escriba como máximo {{count}} caracteres.\",\n \"matches\": \"El formato no es válido.\",\n \"trim\": \"Quite los espacios al inicio y al final.\",\n \"lowercase\": \"Escríbalo en minúsculas.\",\n \"uppercase\": \"Escríbalo en mayúsculas.\",\n \"numberMin\": \"No puede ser menor que {{min}}.\",\n \"numberMax\": \"No puede ser mayor que {{max}}.\",\n \"lessThan\": \"Debe ser menor que {{less}}.\",\n \"moreThan\": \"Debe ser mayor que {{more}}.\",\n \"positive\": \"Debe ser un número positivo.\",\n \"negative\": \"Debe ser un número negativo.\",\n \"integer\": \"Debe ser un número entero.\",\n \"dateMin\": \"La fecha no puede ser anterior a {{min}}.\",\n \"dateMax\": \"La fecha no puede ser posterior a {{max}}.\",\n \"arrayMin_one\": \"Elija al menos {{count}} opción.\",\n \"arrayMin_other\": \"Elija al menos {{count}} opciones.\",\n \"arrayMax_one\": \"Elija como máximo {{count}} opción.\",\n \"arrayMax_other\": \"Elija como máximo {{count}} opciones.\",\n \"arrayLength_one\": \"Elija exactamente {{count}} opción.\",\n \"arrayLength_other\": \"Elija exactamente {{count}} opciones.\"\n },\n \"dictionaries\": {\n \"unknown\": {\n \"label\": \"Sin estatus\",\n \"tooltip\": \"\"\n }\n },\n \"security\": {\n \"passkeys\": {\n \"cancelled\": \"No se usó ninguna llave de acceso. Puede intentarlo de nuevo.\",\n \"alreadyRegistered\": \"Esa llave de acceso ya está registrada en su cuenta.\",\n \"wrongDomain\": \"Las llaves de acceso solo funcionan en la dirección oficial del sistema.\",\n \"failed\": \"El navegador no pudo usar la llave de acceso.\",\n \"unsupported\": \"Este navegador no admite llaves de acceso.\"\n },\n \"twoStep\": {\n \"title\": \"Verificación en dos pasos\",\n \"description\": \"Además de su contraseña, un segundo paso al entrar. Si alguien obtiene su contraseña, no podrá entrar sin él.\",\n \"active\": \"Activa\",\n \"activeSince\": \"Desde {{date}}\",\n \"inactive\": \"Sin activar\",\n \"inactiveTooltip\": \"Su cuenta solo está protegida por la contraseña.\",\n \"passkeysTitle\": \"Llaves de acceso\",\n \"add\": \"Agregar\",\n \"passkeysEmpty\": \"La forma más segura: entra con la huella, el rostro o el PIN de su equipo.\",\n \"passkeyUsed\": \"Usada por última vez {{date}}\",\n \"passkeyCreated\": \"Creada {{date}}\",\n \"removePasskey\": \"Quitar {{name}}\",\n \"removePasskeyTooltip\": \"Quitar esta llave\",\n \"appTitle\": \"App autenticadora\",\n \"appActiveSince\": \"Activa desde {{date}}\",\n \"appDescription\": \"Un código de 6 dígitos que cambia cada 30 segundos.\",\n \"activate\": \"Activar\",\n \"deactivate\": \"Desactivar\",\n \"codesTitle\": \"Códigos de recuperación\",\n \"codesRemaining_one\": \"Le queda {{count}} código sin usar.\",\n \"codesRemaining_other\": \"Le quedan {{count}} códigos sin usar.\",\n \"codesNone\": \"Ya no le quedan códigos: genere otros.\",\n \"regenerate\": \"Generar nuevos\",\n \"passwordLabel\": \"Su contraseña actual\",\n \"passwordRequired\": \"Escriba su contraseña.\",\n \"disableTitle\": \"¿Desactivar la app autenticadora?\",\n \"disableText\": \"Si no tiene una llave de acceso, su cuenta quedará protegida solo por la contraseña y sus códigos de recuperación dejarán de servir.\",\n \"removeTitle\": \"¿Quitar «{{name}}»?\",\n \"removeText\": \"Ya no podrá entrar con esta llave. Bórrela también de su equipo o de su gestor de contraseñas.\",\n \"remove\": \"Quitar\",\n \"regenerateTitle\": \"¿Generar códigos nuevos?\",\n \"regenerateText\": \"Los códigos que tenga ahora dejarán de servir.\",\n \"generate\": \"Generar\",\n \"passkeyDialogTitle\": \"Agregar una llave de acceso\",\n \"passkeyDialogText\": \"Una llave de acceso usa la huella, el rostro o el PIN de este equipo o de su teléfono. No se puede robar con una página falsa y le permite entrar sin escribir su contraseña.\",\n \"passkeyName\": \"Nombre para reconocerla (opcional)\",\n \"passkeyNamePlaceholder\": \"Por ejemplo: MacBook del trabajo\",\n \"createPasskey\": \"Crear llave\",\n \"totpDialogTitle\": \"Activar la app autenticadora\",\n \"totpStep1\": \"1. Abra su app (Google Authenticator, Microsoft Authenticator, 1Password…) y escanee este código.\",\n \"totpQrAlt\": \"Código QR para la app autenticadora\",\n \"totpCantScan\": \"¿No puede escanear? Escriba esta clave en su app:\",\n \"totpStep2\": \"2. Escriba el código de 6 dígitos que muestra la app.\",\n \"codeLabel\": \"Código\",\n \"codeRequired\": \"Escriba el código de 6 dígitos.\",\n \"codeFormat\": \"Son 6 dígitos, como aparecen en su app.\",\n \"passkeyAdded\": \"Llave de acceso agregada\",\n \"passkeyRemoved\": \"Llave de acceso quitada\",\n \"appActivated\": \"App autenticadora activada\",\n \"appDeactivated\": \"App autenticadora desactivada\"\n },\n \"recoveryCodes\": {\n \"title\": \"Guarde sus códigos de recuperación\",\n \"warning\": \"Es la única vez que los verá. Con uno de ellos podrá entrar si pierde su teléfono o su llave de acceso; cada uno sirve una sola vez.\",\n \"copyAll\": \"Copiar todos\",\n \"download\": \"Descargar .txt\",\n \"saved\": \"Ya los guardé en un lugar seguro\",\n \"done\": \"Listo\",\n \"fileTitle\": \"{{appName}} · códigos de recuperación\",\n \"fileAccount\": \"Cuenta: {{account}}\",\n \"fileGenerated\": \"Generados: {{date}}\",\n \"fileHowTo\": \"Cada código sirve UNA sola vez para entrar si no tiene su teléfono o su llave de acceso.\",\n \"fileKeepSafe\": \"Guárdelos en un lugar seguro (un gestor de contraseñas o impresos bajo llave).\",\n \"fileName\": \"codigos-recuperacion-{{date}}.txt\"\n },\n \"secondStep\": {\n \"intro\": \"Su cuenta tiene verificación en dos pasos. Confirme que es usted para terminar de entrar.\",\n \"usePasskey\": \"Usar mi llave de acceso\",\n \"or\": \"o\",\n \"appCodeLabel\": \"Código de su app autenticadora\",\n \"appCodeHelp\": \"Ábrala (Google Authenticator, Microsoft Authenticator, 1Password…) y escriba los 6 dígitos.\",\n \"recoveryCodeLabel\": \"Código de recuperación\",\n \"recoveryCodeHelp\": \"Uno de los códigos que guardó al activar la verificación. Cada uno sirve una sola vez.\",\n \"recoveryCodeRequired\": \"Escriba uno de sus códigos de recuperación.\",\n \"recoveryCodeFormat\": \"Tienen la forma ABCDE-FGHJK.\",\n \"confirm\": \"Confirmar\",\n \"useRecovery\": \"No tengo mi teléfono: usar un código de recuperación\",\n \"useApp\": \"Usar el código de mi app autenticadora\",\n \"otherAccount\": \"Entrar con otra cuenta\",\n \"remainingAttempts_one\": \"Le queda {{count}} intento.\",\n \"remainingAttempts_other\": \"Le quedan {{count}} intentos.\"\n },\n \"sessions\": {\n \"title\": \"Mis sesiones\",\n \"description\": \"Dónde tiene la cuenta abierta. Si no reconoce un equipo, cierre la sesión en los demás y cambie su contraseña.\",\n \"current\": \"Este equipo\",\n \"device\": \"{{browser}} en {{platform}}\",\n \"otherBrowser\": \"Otro navegador\",\n \"otherPlatform\": \"otro sistema\",\n \"since\": \"Desde {{date}}\",\n \"network\": \"red {{network}}\",\n \"onlyThis\": \"Solo tiene la sesión abierta en este equipo.\",\n \"closeOthers\": \"Cerrar la sesión en los demás equipos\",\n \"confirmTitle\": \"¿Cerrar la sesión en los demás equipos?\",\n \"confirmText_one\": \"Se cerrará en {{count}} equipo. Este sigue dentro, y en el otro tendrá que volver a entrar con su contraseña.\",\n \"confirmText_other\": \"Se cerrará en {{count}} equipos. Este sigue dentro, y en los demás tendrá que volver a entrar con su contraseña.\",\n \"confirm\": \"Cerrar las demás\",\n \"closed_one\": \"Se cerró la sesión en {{count}} equipo\",\n \"closed_other\": \"Se cerró la sesión en {{count}} equipos\",\n \"noneClosed\": \"No había sesiones abiertas en otros equipos\"\n },\n \"activity\": {\n \"title\": \"Actividad de la cuenta\",\n \"description\": \"Lo último que pasó con su cuenta. Si ve algo que no fue usted, cambie su contraseña y cierre la sesión en los demás equipos.\",\n \"empty\": \"Todavía no hay actividad.\",\n \"from\": \"{{device}} · red {{network}}\",\n \"kinds\": {\n \"signedIn\": \"Entró\",\n \"signInFailed\": \"Intento de entrar con contraseña equivocada\",\n \"signedOut\": \"Salió\",\n \"passwordChanged\": \"Cambió su contraseña\",\n \"twoStepEnabled\": \"Activó la verificación en dos pasos\",\n \"twoStepDisabled\": \"Desactivó la verificación en dos pasos\",\n \"passkeyAdded\": \"Agregó una llave de acceso\",\n \"passkeyRemoved\": \"Quitó una llave de acceso\",\n \"recoveryCodesGenerated\": \"Generó códigos de recuperación nuevos\",\n \"recoveryCodeUsed\": \"Entró con un código de recuperación\",\n \"sessionsClosed\": \"Cerró la sesión en los demás equipos\"\n }\n }\n },\n \"calendar\": {\n \"details\": {\n \"when\": \"Cuándo\",\n \"timeRange\": \"{{date}}, de {{start}} a {{end}}\",\n \"allDay\": \"{{date}}, todo el día\",\n \"location\": \"Dónde\",\n \"meeting\": \"Reunión en línea\",\n \"join\": \"Unirse\",\n \"attendees\": \"Invitados\",\n \"attendeesCount_one\": \"{{count}} invitado\",\n \"attendeesCount_other\": \"{{count}} invitados\",\n \"response\": {\"accepted\": \"Va\", \"declined\": \"No va\", \"pending\": \"Sin responder\"},\n \"reminder\": \"Recordatorio\",\n \"reminderUnit\": {\n \"atStart\": \"Al empezar\",\n \"minutes_one\": \"{{count}} minuto antes\",\n \"minutes_other\": \"{{count}} minutos antes\",\n \"hours_one\": \"{{count}} hora antes\",\n \"hours_other\": \"{{count}} horas antes\",\n \"days_one\": \"{{count}} día antes\",\n \"days_other\": \"{{count}} días antes\"\n },\n \"notes\": \"Notas\"\n },\n \"previous\": \"Anterior\",\n \"next\": \"Siguiente\",\n \"today\": \"Hoy\",\n \"view\": \"Vista del calendario\",\n \"views\": {\n \"month\": \"Mes\",\n \"week\": \"Semana\",\n \"day\": \"Día\",\n \"list\": \"Lista\"\n }\n },\n \"kanban\": {\n \"dueCompleted\": \"Era para el {{date}}\",\n \"complete\": \"Terminar «{{title}}»\",\n \"reopen\": \"Reabrir «{{title}}»\",\n \"completed\": \"Terminada\",\n \"collapseColumn\": \"Encoger «{{column}}»\",\n \"expandColumn\": \"Abrir «{{column}}»\",\n \"addCard\": \"Agregar una tarjeta en {{column}}\",\n \"addCardPlaceholder\": \"Título de la tarjeta (Enter para agregar)\",\n \"cardActions\": \"Acciones de «{{title}}»\",\n \"columnActions\": \"Acciones de {{column}}\",\n \"dueOverdue\": \"Venció el {{date}}\",\n \"dueToday\": \"Vence hoy\",\n \"dueOn\": \"Vence el {{date}}\",\n \"checklist\": \"{{done}} de {{total}} pendientes hechos\",\n \"comments_one\": \"{{count}} comentario\",\n \"comments_other\": \"{{count}} comentarios\",\n \"attachments_one\": \"{{count}} adjunto\",\n \"attachments_other\": \"{{count}} adjuntos\",\n \"assignees\": \"Responsables: {{names}}\",\n \"instructions\": \"Para mover una tarjeta con el teclado: espacio para levantarla, flechas para moverla y espacio para soltarla. Escape la deja donde estaba.\",\n \"lifted\": \"Levantó «{{title}}», en {{column}}, posición {{position}}.\",\n \"moving\": \"Va a {{column}}, posición {{position}}.\",\n \"dropped\": \"Soltó «{{title}}» en {{column}}, posición {{position}}.\",\n \"cancelled\": \"«{{title}}» se quedó donde estaba.\",\n \"overLimit\": \"Pasó el tope de {{limit}} tarjetas.\",\n \"emptyFiltered\": \"Ninguna tarjeta de aquí coincide con la búsqueda.\",\n \"empty\": \"Sin tarjetas. Arrastre una aquí.\"\n },\n \"inbox\": {\n \"labels\": \"Etiquetas\",\n \"select\": \"Elegir «{{subject}}»\",\n \"messageActions\": \"Acciones de «{{subject}}»\",\n \"threadCount_one\": \"{{count}} mensaje en la conversación\",\n \"threadCount_other\": \"{{count}} mensajes en la conversación\",\n \"cc\": \"Con copia: {{cc}}\",\n \"openAttachment\": \"Abrir {{name}}\",\n \"expandMessage\": \"Abrir el mensaje de {{name}} ({{date}})\",\n \"listLabel\": \"Mensajes\",\n \"folders\": \"Carpetas\",\n \"empty\": \"No hay mensajes aquí.\",\n \"noMessageSelected\": \"Elija un mensaje para leerlo.\",\n \"star\": \"Destacar\",\n \"unstar\": \"Quitar de destacados\",\n \"unread\": \"Sin leer\",\n \"attachments\": \"Archivos adjuntos\",\n \"to\": \"Para: {{to}}\",\n \"back\": \"Regresar\",\n \"unreadCount_one\": \"{{count}} sin leer\",\n \"unreadCount_other\": \"{{count}} sin leer\"\n },\n \"account\": {\n \"reset\": {\n \"title\": \"Elegir una contraseña nueva\",\n \"subtitle\": \"Llegó aquí desde el enlace del correo. Elija la contraseña con la que va a entrar.\",\n \"done\": \"Ya puede entrar con su contraseña nueva.\",\n \"goToSignIn\": \"Ir a iniciar sesión\",\n \"missingToken\": \"Este enlace no sirve: le falta su clave. Pida uno nuevo desde «¿Olvidó su contraseña?».\",\n \"askAgain\": \"Pedir otro enlace\"\n },\n \"profile\": {\n \"tabs\": {\n \"label\": \"Partes de su perfil\",\n \"data\": \"Sus datos\",\n \"security\": \"Seguridad\",\n \"preferences\": \"Preferencias\"\n },\n \"data\": {\n \"title\": \"Sus datos\",\n \"text\": \"Así le ven las demás personas en la app.\",\n \"name\": \"Nombre\",\n \"email\": \"Correo\",\n \"emailHelp\": \"Es el correo con el que entra; para cambiarlo, pídalo a quien administra la app.\",\n \"phone\": \"Teléfono (opcional)\",\n \"phoneInvalid\": \"Escriba solo dígitos (de 7 a 15), con + al inicio si es de otro país.\",\n \"save\": \"Guardar\",\n \"saved\": \"Se guardaron sus datos.\"\n },\n \"password\": {\n \"title\": \"Contraseña\",\n \"text\": \"Use una contraseña que no use en otros sitios. Al cambiarla, la próxima vez entra con la nueva.\"\n },\n \"preferences\": {\n \"title\": \"Apariencia\",\n \"text\": \"Cómo se ve la app para usted: el tamaño de letra, el idioma y claro u oscuro se recuerdan en este navegador.\"\n }\n },\n \"demo\": {\n \"totpCode\": \"Usar el código de ejemplo\",\n \"totpCodeHelp\": \"Solo en la demostración: llena el código que enseñaría su app ahorita, por si no la tiene a la mano.\",\n \"passkeyNote\": \"En la demostración la llave se guarda de verdad en su llavero (iCloud, Google o su gestor de contraseñas), pero esta página la olvida al recargar. Cuando termine de probar, bórrela también de su llavero.\",\n \"securityNote\": \"Servidor de demostración: la verificación funciona con su teléfono de verdad (escanee el QR o cree una llave con su huella), pero vive en la memoria de la pestaña: al recargar se apaga. Salga y vuelva a entrar para ver el segundo paso.\",\n \"server\": {\n \"wrongCredentialsTitle\": \"No pudo entrar\",\n \"wrongCredentials\": \"En la demostración se entra con {{email}} y cualquier contraseña.\",\n \"noSessionTitle\": \"Su sesión terminó\",\n \"noSession\": \"Vuelva a entrar.\",\n \"validationTitle\": \"Revise los datos\",\n \"validationDetail\": \"Hay campos por corregir.\",\n \"passwordRequired\": \"Escriba su contraseña para confirmar.\",\n \"nameRequired\": \"Escriba su nombre.\",\n \"wrongCodeTitle\": \"El código no coincide\",\n \"wrongCode\": \"Revise que sea el código que enseña su app ahorita.\",\n \"expiredTitle\": \"Se acabó el tiempo\",\n \"expired\": \"Pasaron más de 5 minutos: vuelva a escribir su contraseña.\",\n \"lockedTitle\": \"Demasiados intentos\",\n \"locked\": \"Por seguridad hay que empezar de nuevo: vuelva a escribir su contraseña.\",\n \"defaultPasskeyName\": \"Llave {{number}}\",\n \"profileSaved\": \"Se guardaron sus datos.\"\n }\n }\n }\n});\n"],"names":[],"mappings":"AAEA,MAAA,KAAe,OAAO,OAAO;AAAA,EACzB,SAAS;AAAA,IACL,UAAU;AAAA,MACN,eAAe;AAAA,IAC3B;AAAA,IACQ,SAAS;AAAA,MACL,eAAe;AAAA,MACf,iBAAiB;AAAA,MACjB,UAAU;AAAA,MACV,aAAa;AAAA,MACb,iBAAiB;AAAA,IAC7B;AAAA,IACQ,SAAS;AAAA,MACL,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,gBAAgB;AAAA,IAC5B;AAAA,IACQ,SAAS;AAAA,MACL,eAAe;AAAA,MACf,iBAAiB;AAAA,MACjB,YAAY;AAAA,IACxB;AAAA,IACQ,gBAAgB;AAAA,MACZ,WAAW;AAAA,MACX,qBAAqB;AAAA,MACrB,uBAAuB;AAAA,MACvB,SAAS;AAAA,MACT,YAAY;AAAA,IACxB;AAAA,IACQ,WAAW;AAAA,MACP,qBAAqB;AAAA,MACrB,SAAS;AAAA,MACT,WAAW;AAAA,IACvB;AAAA,IACQ,QAAQ;AAAA,MACJ,WAAW;AAAA,MACX,aAAa;AAAA,MACb,cAAc;AAAA,MACd,SAAS;AAAA,IACrB;AAAA,IACQ,aAAa;AAAA,MACT,aAAa;AAAA,MACb,eAAe;AAAA,MACf,eAAe;AAAA,MACf,qBAAqB;AAAA,MACrB,oBAAoB;AAAA,MACpB,sBAAsB;AAAA,MACtB,qBAAqB;AAAA,MACrB,2BAA2B;AAAA,MAC3B,gBAAgB;AAAA,MAChB,SAAS;AAAA,MACT,gBAAgB;AAAA,MAChB,gBAAgB;AAAA,IAC5B;AAAA,IACQ,YAAY;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,aAAa;AAAA,IACzB;AAAA,IACQ,gBAAgB;AAAA,MACZ,OAAO;AAAA,MACP,kBAAkB;AAAA,IAC9B;AAAA,IACQ,UAAU;AAAA,MACN,WAAW;AAAA,MACX,aAAa;AAAA,IACzB;AAAA,IACQ,SAAS;AAAA,MACL,WAAW;AAAA,IACvB;AAAA,IACQ,aAAa;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,WAAW;AAAA,MACX,WAAW;AAAA,MACX,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACrB;AAAA,IACQ,SAAS;AAAA,MACL,WAAW;AAAA,QACP,cAAc;AAAA,QACd,SAAS;AAAA,QACT,UAAU;AAAA,QACV,SAAS;AAAA,MACzB;AAAA,MACY,eAAe;AAAA,MACf,sBAAsB;AAAA,MACtB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,SAAS;AAAA,IACrB;AAAA,EACA;AAAA,EACI,UAAU;AAAA,IACN,WAAW;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,UAAU;AAAA,IACtB;AAAA,IACQ,UAAU;AAAA,MACN,WAAW;AAAA,MACX,UAAU;AAAA,IACtB;AAAA,IACQ,QAAQ;AAAA,MACJ,gBAAgB;AAAA,MAChB,gBAAgB;AAAA,MAChB,SAAS;AAAA,MACT,qBAAqB;AAAA,MACrB,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,iBAAiB;AAAA,MACjB,oBAAoB;AAAA,MACpB,sBAAsB;AAAA,IAClC;AAAA,IACQ,aAAa;AAAA,MACT,SAAS;AAAA,MACT,gBAAgB;AAAA,MAChB,gBAAgB;AAAA,IAC5B;AAAA,IACQ,UAAU;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,UAAU;AAAA,IACtB;AAAA,IACQ,YAAY;AAAA,MACR,YAAY;AAAA,QACR,MAAM;AAAA,QACN,MAAM;AAAA,MACtB;AAAA,MACY,iBAAiB;AAAA,MACjB,UAAU;AAAA,IACtB;AAAA,IACQ,eAAe;AAAA,MACX,SAAS;AAAA,MACT,YAAY;AAAA,QACR,SAAS;AAAA,QACT,QAAQ;AAAA,MACxB;AAAA,IACA;AAAA,IACQ,SAAS;AAAA,MACL,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,WAAW;AAAA,IACvB;AAAA,IACQ,WAAW;AAAA,MACP,SAAS;AAAA,MACT,QAAQ;AAAA,IACpB;AAAA,IACQ,YAAY;AAAA,MACR,WAAW;AAAA,MACX,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,WAAW;AAAA,MACP,UAAU;AAAA,MACV,UAAU;AAAA,MACV,SAAS;AAAA,MACT,WAAW;AAAA,MACX,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,MACzB;AAAA,MACY,cAAc;AAAA,QACV,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,MACzB;AAAA,MACY,kBAAkB;AAAA,QACd,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,MACzB;AAAA,IACA;AAAA,IACQ,UAAU;AAAA,MACN,QAAQ;AAAA,MACR,gBAAgB;AAAA,MAChB,UAAU;AAAA,MACV,kBAAkB;AAAA,MAClB,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,sBAAsB;AAAA,MACtB,oBAAoB;AAAA,MACpB,sBAAsB;AAAA,MACtB,eAAe;AAAA,MACf,oBAAoB;AAAA,MACpB,oBAAoB;AAAA,MACpB,sBAAsB;AAAA,MACtB,eAAe;AAAA,MACf,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,mBAAmB;AAAA,MACnB,QAAQ;AAAA,QACJ,WAAW;AAAA,QACX,aAAa;AAAA,QACb,kBAAkB;AAAA,QAClB,oBAAoB;AAAA,QACpB,gBAAgB;AAAA,QAChB,cAAc;AAAA,QACd,sBAAsB;AAAA,QACtB,qBAAqB;AAAA,QACrB,mBAAmB;AAAA,QACnB,eAAe;AAAA,QACf,eAAe;AAAA,QACf,eAAe;AAAA,QACf,cAAc;AAAA,QACd,aAAa;AAAA,QACb,UAAU;AAAA,QACV,aAAa;AAAA,QACb,aAAa;AAAA,QACb,mBAAmB;AAAA,QACnB,kBAAkB;AAAA,QAClB,mBAAmB;AAAA,QACnB,eAAe;AAAA,MAC/B;AAAA,IACA;AAAA,IACQ,YAAY;AAAA,MACR,WAAW;AAAA,QACP,OAAO;AAAA,UACH,SAAS;AAAA,UACT,QAAQ;AAAA,QAC5B;AAAA,QACgB,QAAQ;AAAA,UACJ,SAAS;AAAA,UACT,QAAQ;AAAA,QAC5B;AAAA,QACgB,OAAO;AAAA,UACH,SAAS;AAAA,UACT,QAAQ;AAAA,QAC5B;AAAA,MACA;AAAA,IACA;AAAA,IACQ,aAAa;AAAA,MACT,SAAS;AAAA,MACT,0BAA0B;AAAA,MAC1B,4BAA4B;AAAA,MAC5B,wBAAwB;AAAA,MACxB,0BAA0B;AAAA,MAC1B,qBAAqB;AAAA,MACrB,uBAAuB;AAAA,MACvB,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,SAAS;AAAA,MACT,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,aAAa;AAAA,MACb,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,UAAU;AAAA,MACV,YAAY;AAAA,IACxB;AAAA,IACQ,UAAU;AAAA,MACN,eAAe;AAAA,MACf,UAAU;AAAA,MACV,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,OAAO;AAAA,IACnB;AAAA,EACA;AAAA,EACI,UAAU;AAAA,IACN,iBAAiB;AAAA,IACjB,cAAc;AAAA,MACV,QAAQ;AAAA,IACpB;AAAA,IACQ,UAAU;AAAA,MACN,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,eAAe;AAAA,MACf,cAAc;AAAA,MACd,YAAY;AAAA,IACxB;AAAA,IACQ,WAAW;AAAA,MACP,QAAQ;AAAA,MACR,aAAa;AAAA,IACzB;AAAA,IACQ,YAAY;AAAA,MACR,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,UAAU;AAAA,IACtB;AAAA,IACQ,YAAY;AAAA,MACR,SAAS;AAAA,MACT,UAAU;AAAA,MACV,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,gBAAgB;AAAA,MACZ,MAAM;AAAA,MACN,MAAM;AAAA,IAClB;AAAA,IACQ,eAAe;AAAA,IACf,WAAW;AAAA,IACX,cAAc;AAAA,EACtB;AAAA,EACI,QAAQ;AAAA,IACJ,UAAU;AAAA,MACN,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,UAAU;AAAA,IACtB;AAAA,IACQ,kBAAkB;AAAA,MACd,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,UAAU;AAAA,MACV,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,gBAAgB;AAAA,IAC5B;AAAA,IACQ,kBAAkB;AAAA,MACd,SAAS;AAAA,MACT,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,eAAe;AAAA,MACf,QAAQ;AAAA,IACpB;AAAA,IACQ,iBAAiB;AAAA,MACb,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,MACb,aAAa;AAAA,MACb,UAAU;AAAA,MACV,UAAU;AAAA,MACV,OAAO;AAAA,MACP,WAAW;AAAA,IACvB;AAAA,EACA;AAAA,EACI,UAAU;AAAA,IACN,eAAe;AAAA,MACX,WAAW;AAAA,MACX,SAAS;AAAA,IACrB;AAAA,EACA;AAAA,EACI,cAAc;AAAA,IACV,SAAS;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ;AAAA,IACpB;AAAA,EACA;AAAA,EACI,aAAa;AAAA,IACT,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,cAAc;AAAA,MACV,SAAS;AAAA,MACT,QAAQ;AAAA,IACpB;AAAA,IACQ,YAAY;AAAA,MACR,SAAS;AAAA,MACT,QAAQ;AAAA,IACpB;AAAA,IACQ,aAAa;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA,IACpB;AAAA,IACQ,eAAe;AAAA,MACX,SAAS;AAAA,MACT,QAAQ;AAAA,IACpB;AAAA,EACA;AAAA,EACI,YAAY;AAAA,IACR,WAAW;AAAA,MACP,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,mBAAmB;AAAA,MACf,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,aAAa;AAAA,MACT,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,YAAY;AAAA,MACR,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,YAAY;AAAA,MACR,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,mBAAmB;AAAA,MACf,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,qBAAqB;AAAA,MACjB,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,cAAc;AAAA,MACV,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,mBAAmB;AAAA,MACf,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,eAAe;AAAA,MACX,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,sBAAsB;AAAA,MAClB,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,WAAW;AAAA,MACP,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,IACQ,QAAQ;AAAA,MACJ,SAAS;AAAA,MACT,UAAU;AAAA,IACtB;AAAA,EACA;AAAA,EACI,cAAc;AAAA,IACV,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,oBAAoB;AAAA,IACpB,sBAAsB;AAAA,IACtB,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,aAAa;AAAA,IACb,aAAa;AAAA,IACb,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,EAC7B;AAAA,EACI,gBAAgB;AAAA,IACZ,WAAW;AAAA,MACP,SAAS;AAAA,MACT,WAAW;AAAA,IACvB;AAAA,EACA;AAAA,EACI,YAAY;AAAA,IACR,YAAY;AAAA,MACR,aAAa;AAAA,MACb,qBAAqB;AAAA,MACrB,eAAe;AAAA,MACf,UAAU;AAAA,MACV,eAAe;AAAA,IAC3B;AAAA,IACQ,WAAW;AAAA,MACP,SAAS;AAAA,MACT,eAAe;AAAA,MACf,UAAU;AAAA,MACV,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,mBAAmB;AAAA,MACnB,iBAAiB;AAAA,MACjB,OAAO;AAAA,MACP,iBAAiB;AAAA,MACjB,eAAe;AAAA,MACf,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,wBAAwB;AAAA,MACxB,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,kBAAkB;AAAA,MAClB,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,cAAc;AAAA,MACd,sBAAsB;AAAA,MACtB,wBAAwB;AAAA,MACxB,aAAa;AAAA,MACb,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,oBAAoB;AAAA,MACpB,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,eAAe;AAAA,MACf,cAAc;AAAA,MACd,UAAU;AAAA,MACV,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,YAAY;AAAA,MACZ,sBAAsB;AAAA,MACtB,qBAAqB;AAAA,MACrB,eAAe;AAAA,MACf,0BAA0B;AAAA,MAC1B,iBAAiB;AAAA,MACjB,mBAAmB;AAAA,MACnB,aAAa;AAAA,MACb,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAChB,kBAAkB;AAAA,IAC9B;AAAA,IACQ,iBAAiB;AAAA,MACb,SAAS;AAAA,MACT,WAAW;AAAA,MACX,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,eAAe;AAAA,MACf,iBAAiB;AAAA,MACjB,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,YAAY;AAAA,IACxB;AAAA,IACQ,cAAc;AAAA,MACV,SAAS;AAAA,MACT,cAAc;AAAA,MACd,MAAM;AAAA,MACN,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,qBAAqB;AAAA,MACrB,oBAAoB;AAAA,MACpB,wBAAwB;AAAA,MACxB,sBAAsB;AAAA,MACtB,WAAW;AAAA,MACX,eAAe;AAAA,MACf,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,yBAAyB;AAAA,MACzB,2BAA2B;AAAA,IACvC;AAAA,IACQ,YAAY;AAAA,MACR,SAAS;AAAA,MACT,eAAe;AAAA,MACf,WAAW;AAAA,MACX,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,SAAS;AAAA,MACT,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,mBAAmB;AAAA,MACnB,qBAAqB;AAAA,MACrB,WAAW;AAAA,MACX,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB,cAAc;AAAA,IAC1B;AAAA,IACQ,YAAY;AAAA,MACR,SAAS;AAAA,MACT,eAAe;AAAA,MACf,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,SAAS;AAAA,QACL,YAAY;AAAA,QACZ,gBAAgB;AAAA,QAChB,aAAa;AAAA,QACb,mBAAmB;AAAA,QACnB,kBAAkB;AAAA,QAClB,mBAAmB;AAAA,QACnB,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,QAClB,0BAA0B;AAAA,QAC1B,oBAAoB;AAAA,QACpB,kBAAkB;AAAA,MAClC;AAAA,IACA;AAAA,EACA;AAAA,EACI,YAAY;AAAA,IACR,WAAW;AAAA,MACP,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,sBAAsB;AAAA,MACtB,wBAAwB;AAAA,MACxB,YAAY,EAAC,YAAY,MAAM,YAAY,SAAS,WAAW,gBAAe;AAAA,MAC9E,YAAY;AAAA,MACZ,gBAAgB;AAAA,QACZ,WAAW;AAAA,QACX,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,aAAa;AAAA,QACb,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,cAAc;AAAA,MAC9B;AAAA,MACY,SAAS;AAAA,IACrB;AAAA,IACQ,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,MACL,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACpB;AAAA,EACA;AAAA,EACI,UAAU;AAAA,IACN,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,WAAW;AAAA,IACX,sBAAsB;AAAA,IACtB,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,IACrB,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,UAAU;AAAA,IACV,UAAU;AAAA,IACV,WAAW;AAAA,IACX,aAAa;AAAA,IACb,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,SAAS;AAAA,EACjB;AAAA,EACI,SAAS;AAAA,IACL,UAAU;AAAA,IACV,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,IACrB,MAAM;AAAA,IACN,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,aAAa;AAAA,IACb,WAAW;AAAA,IACX,SAAS;AAAA,IACT,qBAAqB;AAAA,IACrB,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,UAAU;AAAA,IACV,eAAe;AAAA,IACf,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,EAC7B;AAAA,EACI,WAAW;AAAA,IACP,SAAS;AAAA,MACL,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB,YAAY;AAAA,IACxB;AAAA,IACQ,WAAW;AAAA,MACP,QAAQ;AAAA,QACJ,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,eAAe;AAAA,MAC/B;AAAA,MACY,QAAQ;AAAA,QACJ,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,aAAa;AAAA,QACb,SAAS;AAAA,QACT,gBAAgB;AAAA,QAChB,QAAQ;AAAA,QACR,SAAS;AAAA,MACzB;AAAA,MACY,YAAY;AAAA,QACR,SAAS;AAAA,QACT,QAAQ;AAAA,MACxB;AAAA,MACY,eAAe;AAAA,QACX,SAAS;AAAA,QACT,QAAQ;AAAA,MACxB;AAAA,IACA;AAAA,IACQ,QAAQ;AAAA,MACJ,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,UAAU;AAAA,QACN,yBAAyB;AAAA,QACzB,oBAAoB;AAAA,QACpB,kBAAkB;AAAA,QAClB,aAAa;AAAA,QACb,mBAAmB;AAAA,QACnB,oBAAoB;AAAA,QACpB,oBAAoB;AAAA,QACpB,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,QAClB,aAAa;AAAA,QACb,gBAAgB;AAAA,QAChB,WAAW;AAAA,QACX,eAAe;AAAA,QACf,UAAU;AAAA,QACV,sBAAsB;AAAA,QACtB,gBAAgB;AAAA,MAChC;AAAA,IACA;AAAA,EACA;AACA,CAAC;"}
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import "leaflet/dist/leaflet.css";
4
4
  import { useRef, useMemo, useEffect } from "react";
5
5
  import L from "leaflet";
6
- import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet";
6
+ import { MapContainer, TileLayer, Marker, Popup, useMapEvents, useMap } from "react-leaflet";
7
7
  import Box from "@mui/material/Box";
8
8
  import Typography from "@mui/material/Typography";
9
9
  import { useTheme } from "@mui/material/styles";
@@ -40,7 +40,28 @@ function FlyToSelected({ selectedMarker, markerRefs }) {
40
40
  }, [map, selectedMarker, markerRefs]);
41
41
  return null;
42
42
  }
43
- function MapView({ markers, selectedId = null, onSelect, ariaLabel, height = DEFAULT_HEIGHT_PIXELS, tileLayer = OPEN_STREET_MAP_TILES }) {
43
+ function MapClicks({ onMapClick }) {
44
+ useMapEvents({ click: (event) => onMapClick([event.latlng.lat, event.latlng.lng]) });
45
+ return null;
46
+ }
47
+ function DefaultPopupContent({ marker }) {
48
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
49
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", component: "p", children: marker.title }),
50
+ marker.description && /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", component: "p", sx: { m: 0 }, children: marker.description })
51
+ ] });
52
+ }
53
+ function MapView({
54
+ markers,
55
+ selectedId = null,
56
+ onSelect,
57
+ ariaLabel,
58
+ height = DEFAULT_HEIGHT_PIXELS,
59
+ tileLayer = OPEN_STREET_MAP_TILES,
60
+ renderPopup,
61
+ onMapClick,
62
+ children,
63
+ mapProps
64
+ }) {
44
65
  const theme = useTheme();
45
66
  const palette = paletteOf(theme);
46
67
  const markerRefs = useRef(/* @__PURE__ */ new Map());
@@ -66,8 +87,10 @@ function MapView({ markers, selectedId = null, onSelect, ariaLabel, height = DEF
66
87
  "& .leaflet-control-attribution": { bgcolor: "background.paper", color: "text.secondary", "& a": { color: "inherit" } },
67
88
  // OpenStreetMap solo da mosaicos claros: en oscuro se invierten y se regresa el tono (el agua sigue azul)
68
89
  ...currentTheme.applyStyles("dark", { "& .leaflet-tile-pane": { filter: "invert(1) hue-rotate(180deg) brightness(0.95) contrast(0.9)" } })
69
- }), children: /* @__PURE__ */ jsxs(MapContainer, { center: markers[0]?.position ?? DEFAULT_CENTER, zoom: DEFAULT_ZOOM, scrollWheelZoom: false, children: [
90
+ }), children: /* @__PURE__ */ jsxs(MapContainer, { center: markers[0]?.position ?? DEFAULT_CENTER, zoom: DEFAULT_ZOOM, scrollWheelZoom: false, ...mapProps, children: [
70
91
  /* @__PURE__ */ jsx(TileLayer, { url: tileLayer.url, attribution: tileLayer.attribution }),
92
+ onMapClick && /* @__PURE__ */ jsx(MapClicks, { onMapClick }),
93
+ children,
71
94
  /* @__PURE__ */ jsx(FitToMarkers, { bounds }),
72
95
  /* @__PURE__ */ jsx(FlyToSelected, { selectedMarker, markerRefs }),
73
96
  markers.map((marker) => /* @__PURE__ */ jsx(
@@ -80,10 +103,7 @@ function MapView({ markers, selectedId = null, onSelect, ariaLabel, height = DEF
80
103
  zIndexOffset: marker.id === selectedId ? 1e3 : 0,
81
104
  ref: (leafletMarker) => leafletMarker ? markerRefs.current.set(marker.id, leafletMarker) : markerRefs.current.delete(marker.id),
82
105
  eventHandlers: { click: () => onSelect?.(marker.id) },
83
- children: /* @__PURE__ */ jsxs(Popup, { children: [
84
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", component: "p", children: marker.title }),
85
- marker.description && /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", component: "p", sx: { m: 0 }, children: marker.description })
86
- ] })
106
+ children: /* @__PURE__ */ jsx(Popup, { children: renderPopup ? renderPopup(marker) : /* @__PURE__ */ jsx(DefaultPopupContent, { marker }) })
87
107
  },
88
108
  marker.id
89
109
  ))
@@ -1 +1 @@
1
- {"version":3,"file":"MapView.js","sources":["../../src/maps/MapView.jsx"],"sourcesContent":["// Un mapa con marcadores (@stackcraftmx/calcita/maps): Leaflet con los mosaicos de OpenStreetMap. Cada marcador es un\n// alfiler del color del tema (mapMarkers.js); el elegido va más grande, el mapa vuela a él y abre su globo. Al cambiar los\n// marcadores, el mapa se acomoda para que se vean todos.\n//\n// <MapView markers={[{id: 'gdl', position: [20.67, -103.35], title: 'Sucursal Guadalajara', tone: 'success'}]}\n// selectedId={selectedId} onSelect={setSelectedId} ariaLabel=\"Mapa de sucursales\" />\n//\n// En oscuro los mosaicos se oscurecen con un filtro (OpenStreetMap solo los da claros). Los mosaicos públicos de\n// OpenStreetMap son para poco tráfico y piden su crédito: para mucho uso, un proveedor propio en tileLayer.\n//\n// OJO, Next.js: Leaflet usa window al cargarse; en Next se importa con next/dynamic y ssr: false.\n//\n// Genérico del paquete: function (regla de la casa).\nimport 'leaflet/dist/leaflet.css';\nimport {useEffect, useMemo, useRef} from 'react';\nimport L from 'leaflet';\nimport {MapContainer, Marker, Popup, TileLayer, useMap} from 'react-leaflet';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\n\nimport {MAP_PIN_SIZE, boundsOfMarkers, mapPinHtmlOf} from '@/maps/mapMarkers.js';\nimport {paletteOf} from '@/theme/components/paletteOf.js';\n\n/** Los mosaicos de OpenStreetMap con su crédito (lo pide su política de uso). */\nexport const OPEN_STREET_MAP_TILES = Object.freeze({\n url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',\n attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a>',\n});\n\n// México completo cuando no hay marcadores\nconst DEFAULT_CENTER = Object.freeze([23.6, -102.5]);\nconst DEFAULT_ZOOM = 5;\n// al volar a un marcador elegido, al menos este acercamiento\nconst SELECTED_ZOOM = 13;\n// al acomodar el mapa a los marcadores: el margen (px) y que no se acerque de más con uno solo\nconst FIT_PADDING_PIXELS = 48;\nconst FIT_MAX_ZOOM = 13;\nconst DEFAULT_HEIGHT_PIXELS = 420;\n\n/**\n * Acomoda el mapa para que se vean todos los marcadores cada vez que cambian.\n *\n * @param {{bounds: [[number, number], [number, number]]|null}} props\n */\nfunction FitToMarkers({bounds}) {\n const map = useMap();\n const boundsKey = JSON.stringify(bounds);\n useEffect(() => {\n if (bounds) {\n map.fitBounds(bounds, {padding: [FIT_PADDING_PIXELS, FIT_PADDING_PIXELS], maxZoom: FIT_MAX_ZOOM});\n }\n // OJO: depende del texto del recuadro y no del arreglo: el arreglo es nuevo en cada pintada y el mapa se movería solo\n }, [map, boundsKey]);\n return null;\n}\n\n/**\n * Vuela al marcador elegido y abre su globo.\n *\n * @param {{selectedMarker: object|undefined, markerRefs: object}} props\n */\nfunction FlyToSelected({selectedMarker, markerRefs}) {\n const map = useMap();\n useEffect(() => {\n if (!selectedMarker) {\n return;\n }\n map.flyTo(selectedMarker.position, Math.max(map.getZoom(), SELECTED_ZOOM));\n markerRefs.current.get(selectedMarker.id)?.openPopup();\n }, [map, selectedMarker, markerRefs]);\n return null;\n}\n\n/**\n * @param {object} props\n * @param {Array<{id: string, position: [number, number], title: string, description?: string, tone?: string}>} props.markers\n * tone: un color del tema (primary, success, warning, error, info…); por omisión primary\n * @param {string|null} [props.selectedId]\n * @param {(markerId: string) => void} [props.onSelect] al picar un marcador\n * @param {string} props.ariaLabel ya traducido: qué enseña el mapa\n * @param {number} [props.height] px\n * @param {{url: string, attribution: string}} [props.tileLayer] por omisión OpenStreetMap\n */\nexport default function MapView({markers, selectedId = null, onSelect, ariaLabel, height = DEFAULT_HEIGHT_PIXELS, tileLayer = OPEN_STREET_MAP_TILES}) {\n const theme = useTheme();\n const palette = paletteOf(theme);\n const markerRefs = useRef(new Map());\n const bounds = useMemo(() => boundsOfMarkers(markers), [markers]);\n const selectedMarker = markers.find((marker) => marker.id === selectedId);\n\n // el ícono de cada marcador: se arma de nuevo solo si cambia su color o si se elige\n const iconOf = (marker) => {\n const isSelected = marker.id === selectedId;\n const size = isSelected ? MAP_PIN_SIZE.selected : MAP_PIN_SIZE.normal;\n const color = (palette[marker.tone] ?? palette.primary).main;\n return L.divIcon({html: mapPinHtmlOf(color, isSelected), className: '', iconSize: [size, size], iconAnchor: [size / 2, size], popupAnchor: [0, -size]});\n };\n\n return (\n <Box role=\"region\" aria-label={ariaLabel} sx={(currentTheme) => ({\n height, borderRadius: 3, overflow: 'hidden', position: 'relative', zIndex: 0,\n '& .leaflet-container': {height: '100%', width: '100%', fontFamily: 'inherit', bgcolor: 'action.hover'},\n '& .leaflet-bar': {border: 0, borderRadius: '10px', overflow: 'hidden', boxShadow: currentTheme.shadows[2]},\n '& .leaflet-bar a': {bgcolor: 'background.paper', color: 'text.primary', borderBottomColor: 'divider'},\n '& .leaflet-popup-content-wrapper, & .leaflet-popup-tip': {bgcolor: 'background.paper', color: 'text.primary'},\n '& .leaflet-popup-content-wrapper': {borderRadius: '12px'},\n '& .leaflet-control-attribution': {bgcolor: 'background.paper', color: 'text.secondary', '& a': {color: 'inherit'}},\n // OpenStreetMap solo da mosaicos claros: en oscuro se invierten y se regresa el tono (el agua sigue azul)\n ...currentTheme.applyStyles('dark', {'& .leaflet-tile-pane': {filter: 'invert(1) hue-rotate(180deg) brightness(0.95) contrast(0.9)'}}),\n })}>\n <MapContainer center={markers[0]?.position ?? DEFAULT_CENTER} zoom={DEFAULT_ZOOM} scrollWheelZoom={false}>\n <TileLayer url={tileLayer.url} attribution={tileLayer.attribution} />\n <FitToMarkers bounds={bounds} />\n <FlyToSelected selectedMarker={selectedMarker} markerRefs={markerRefs} />\n {markers.map((marker) => (\n <Marker key={marker.id} position={marker.position} icon={iconOf(marker)} title={marker.title} alt={marker.title}\n zIndexOffset={marker.id === selectedId ? 1000 : 0}\n ref={(leafletMarker) => (leafletMarker ? markerRefs.current.set(marker.id, leafletMarker) : markerRefs.current.delete(marker.id))}\n eventHandlers={{click: () => onSelect?.(marker.id)}}>\n <Popup>\n <Typography variant=\"subtitle2\" component=\"p\">{marker.title}</Typography>\n {marker.description && <Typography variant=\"body2\" color=\"text.secondary\" component=\"p\" sx={{m: 0}}>{marker.description}</Typography>}\n </Popup>\n </Marker>\n ))}\n </MapContainer>\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MapView.js","sources":["../../src/maps/MapView.jsx"],"sourcesContent":["// Un mapa con marcadores (@stackcraftmx/calcita/maps): Leaflet con los mosaicos de OpenStreetMap. Cada marcador es un\n// alfiler del color del tema (mapMarkers.js); el elegido va más grande, el mapa vuela a él y abre su globo. Al cambiar los\n// marcadores, el mapa se acomoda para que se vean todos.\n//\n// <MapView markers={[{id: 'gdl', position: [20.67, -103.35], title: 'Sucursal Guadalajara', tone: 'success'}]}\n// selectedId={selectedId} onSelect={setSelectedId} ariaLabel=\"Mapa de sucursales\" />\n//\n// En oscuro los mosaicos se oscurecen con un filtro (OpenStreetMap solo los da claros). Los mosaicos públicos de\n// OpenStreetMap son para poco tráfico y piden su crédito: para mucho uso, un proveedor propio en tileLayer.\n//\n// Se extiende sin tocar el framework: cualquier contenido en el globo (renderPopup: un estado, un botón, una foto, «Cómo\n// llegar»), capas propias de react-leaflet adentro (children: zonas de entrega, un radio), el clic en el mapa (onMapClick:\n// agregar un punto) y lo demás de MapContainer de Leaflet (mapProps).\n//\n// OJO, Next.js: Leaflet usa window al cargarse; en Next se importa con next/dynamic y ssr: false.\n//\n// Genérico del paquete: function (regla de la casa).\nimport 'leaflet/dist/leaflet.css';\nimport {useEffect, useMemo, useRef} from 'react';\nimport L from 'leaflet';\nimport {MapContainer, Marker, Popup, TileLayer, useMap, useMapEvents} from 'react-leaflet';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\n\nimport {MAP_PIN_SIZE, boundsOfMarkers, mapPinHtmlOf} from '@/maps/mapMarkers.js';\nimport {paletteOf} from '@/theme/components/paletteOf.js';\n\n/** Los mosaicos de OpenStreetMap con su crédito (lo pide su política de uso). */\nexport const OPEN_STREET_MAP_TILES = Object.freeze({\n url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',\n attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a>',\n});\n\n// México completo cuando no hay marcadores\nconst DEFAULT_CENTER = Object.freeze([23.6, -102.5]);\nconst DEFAULT_ZOOM = 5;\n// al volar a un marcador elegido, al menos este acercamiento\nconst SELECTED_ZOOM = 13;\n// al acomodar el mapa a los marcadores: el margen (px) y que no se acerque de más con uno solo\nconst FIT_PADDING_PIXELS = 48;\nconst FIT_MAX_ZOOM = 13;\nconst DEFAULT_HEIGHT_PIXELS = 420;\n\n/**\n * Acomoda el mapa para que se vean todos los marcadores cada vez que cambian.\n *\n * @param {{bounds: [[number, number], [number, number]]|null}} props\n */\nfunction FitToMarkers({bounds}) {\n const map = useMap();\n const boundsKey = JSON.stringify(bounds);\n useEffect(() => {\n if (bounds) {\n map.fitBounds(bounds, {padding: [FIT_PADDING_PIXELS, FIT_PADDING_PIXELS], maxZoom: FIT_MAX_ZOOM});\n }\n // OJO: depende del texto del recuadro y no del arreglo: el arreglo es nuevo en cada pintada y el mapa se movería solo\n }, [map, boundsKey]);\n return null;\n}\n\n/**\n * Vuela al marcador elegido y abre su globo.\n *\n * @param {{selectedMarker: object|undefined, markerRefs: object}} props\n */\nfunction FlyToSelected({selectedMarker, markerRefs}) {\n const map = useMap();\n useEffect(() => {\n if (!selectedMarker) {\n return;\n }\n map.flyTo(selectedMarker.position, Math.max(map.getZoom(), SELECTED_ZOOM));\n markerRefs.current.get(selectedMarker.id)?.openPopup();\n }, [map, selectedMarker, markerRefs]);\n return null;\n}\n\n/**\n * Avisa cada clic en el mapa (fuera de un marcador) con su posición.\n *\n * @param {{onMapClick: (position: [number, number]) => void}} props\n */\nfunction MapClicks({onMapClick}) {\n useMapEvents({click: (event) => onMapClick([event.latlng.lat, event.latlng.lng])});\n return null;\n}\n\n/**\n * Lo que dice el globo de un marcador cuando la pantalla no da el suyo: su título y su descripción.\n *\n * @param {{marker: {title: string, description?: string}}} props\n */\nfunction DefaultPopupContent({marker}) {\n return (\n <>\n <Typography variant=\"subtitle2\" component=\"p\">{marker.title}</Typography>\n {marker.description && <Typography variant=\"body2\" color=\"text.secondary\" component=\"p\" sx={{m: 0}}>{marker.description}</Typography>}\n </>\n );\n}\n\n/**\n * @param {object} props\n * @param {Array<{id: string, position: [number, number], title: string, description?: string, tone?: string} & Record<string, any>>} props.markers\n * tone: un color del tema (primary, success, warning, error, info…); por omisión primary\n * @param {string|null} [props.selectedId]\n * @param {(markerId: string) => void} [props.onSelect] al picar un marcador\n * @param {string} props.ariaLabel ya traducido: qué enseña el mapa\n * @param {number} [props.height] px\n * @param {{url: string, attribution: string}} [props.tileLayer] por omisión OpenStreetMap\n * @param {(marker: any) => import('react').ReactNode} [props.renderPopup] lo que dice el globo de un marcador (recibe el\n * marcador tal cual, con sus campos propios); por omisión su título y su descripción\n * @param {(position: [number, number]) => void} [props.onMapClick] al picar el mapa fuera de un marcador\n * @param {import('react').ReactNode} [props.children] capas propias de react-leaflet (Circle, Polygon, GeoJSON…)\n * @param {object} [props.mapProps] lo demás de MapContainer de react-leaflet (scrollWheelZoom, minZoom…)\n */\nexport default function MapView({\n markers, selectedId = null, onSelect, ariaLabel, height = DEFAULT_HEIGHT_PIXELS, tileLayer = OPEN_STREET_MAP_TILES, renderPopup, onMapClick,\n children, mapProps,\n}) {\n const theme = useTheme();\n const palette = paletteOf(theme);\n const markerRefs = useRef(new Map());\n const bounds = useMemo(() => boundsOfMarkers(markers), [markers]);\n const selectedMarker = markers.find((marker) => marker.id === selectedId);\n\n // el ícono de cada marcador: se arma de nuevo solo si cambia su color o si se elige\n const iconOf = (marker) => {\n const isSelected = marker.id === selectedId;\n const size = isSelected ? MAP_PIN_SIZE.selected : MAP_PIN_SIZE.normal;\n const color = (palette[marker.tone] ?? palette.primary).main;\n return L.divIcon({html: mapPinHtmlOf(color, isSelected), className: '', iconSize: [size, size], iconAnchor: [size / 2, size], popupAnchor: [0, -size]});\n };\n\n return (\n <Box role=\"region\" aria-label={ariaLabel} sx={(currentTheme) => ({\n height, borderRadius: 3, overflow: 'hidden', position: 'relative', zIndex: 0,\n '& .leaflet-container': {height: '100%', width: '100%', fontFamily: 'inherit', bgcolor: 'action.hover'},\n '& .leaflet-bar': {border: 0, borderRadius: '10px', overflow: 'hidden', boxShadow: currentTheme.shadows[2]},\n '& .leaflet-bar a': {bgcolor: 'background.paper', color: 'text.primary', borderBottomColor: 'divider'},\n '& .leaflet-popup-content-wrapper, & .leaflet-popup-tip': {bgcolor: 'background.paper', color: 'text.primary'},\n '& .leaflet-popup-content-wrapper': {borderRadius: '12px'},\n '& .leaflet-control-attribution': {bgcolor: 'background.paper', color: 'text.secondary', '& a': {color: 'inherit'}},\n // OpenStreetMap solo da mosaicos claros: en oscuro se invierten y se regresa el tono (el agua sigue azul)\n ...currentTheme.applyStyles('dark', {'& .leaflet-tile-pane': {filter: 'invert(1) hue-rotate(180deg) brightness(0.95) contrast(0.9)'}}),\n })}>\n <MapContainer center={markers[0]?.position ?? DEFAULT_CENTER} zoom={DEFAULT_ZOOM} scrollWheelZoom={false} {...mapProps}>\n <TileLayer url={tileLayer.url} attribution={tileLayer.attribution} />\n {onMapClick && <MapClicks onMapClick={onMapClick} />}\n {children}\n <FitToMarkers bounds={bounds} />\n <FlyToSelected selectedMarker={selectedMarker} markerRefs={markerRefs} />\n {markers.map((marker) => (\n <Marker key={marker.id} position={marker.position} icon={iconOf(marker)} title={marker.title} alt={marker.title}\n zIndexOffset={marker.id === selectedId ? 1000 : 0}\n ref={(leafletMarker) => (leafletMarker ? markerRefs.current.set(marker.id, leafletMarker) : markerRefs.current.delete(marker.id))}\n eventHandlers={{click: () => onSelect?.(marker.id)}}>\n <Popup>\n {renderPopup ? renderPopup(marker) : <DefaultPopupContent marker={marker} />}\n </Popup>\n </Marker>\n ))}\n </MapContainer>\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -2,7 +2,18 @@ const LAST_ORDER = 1e3;
2
2
  const HOME_PATH = "/";
3
3
  function composeMenu(menuModules, { canAccess = () => true, context = {} } = {}) {
4
4
  const modules = Array.isArray(menuModules) ? menuModules : Object.values(menuModules);
5
- return modules.filter((menuModule) => canAccess(menuModule.access)).sort((first, second) => (first.order ?? LAST_ORDER) - (second.order ?? LAST_ORDER)).flatMap((menuModule) => menuModule.buildMenuItems?.(context) ?? [menuModule.buildMenuItem?.(context)]).filter((menuItem) => Boolean(menuItem) && canAccess(menuItem.access)).map((menuItem) => withAllowedChildren(menuItem, canAccess)).filter((menuItem) => Boolean(menuItem.to) || menuItem.children?.length > 0);
5
+ return modules.filter((menuModule) => canAccess(menuModule.access)).sort((first, second) => (first.order ?? LAST_ORDER) - (second.order ?? LAST_ORDER)).flatMap((menuModule) => menuModule.buildMenuItems?.(context) ?? [menuModule.buildMenuItem?.(context)]).filter((menuItem) => Boolean(menuItem) && canAccess(menuItem.access)).map((menuItem) => withAllowedChildren(menuItem, canAccess)).filter((menuItem) => Boolean(menuItem.to) || Boolean(menuItem.href) || menuItem.children?.length > 0);
6
+ }
7
+ function menuItemsWithIconsOf(menuItems) {
8
+ return menuItems.map((menuItem) => {
9
+ const Icon = menuItem.Icon ?? (menuItem.icon ? iconComponentOf(menuItem.icon) : void 0);
10
+ const children = Array.isArray(menuItem.children) ? menuItemsWithIconsOf(menuItem.children) : menuItem.children;
11
+ return { ...menuItem, Icon, children };
12
+ });
13
+ }
14
+ function iconComponentOf(icon) {
15
+ const MenuIcon = () => icon;
16
+ return MenuIcon;
6
17
  }
7
18
  function withAllowedChildren(menuItem, canAccess) {
8
19
  if (!Array.isArray(menuItem.children)) {
@@ -86,6 +97,7 @@ export {
86
97
  menuBadgeOf,
87
98
  menuBreadcrumbsOf,
88
99
  menuItemKeyOf,
100
+ menuItemsWithIconsOf,
89
101
  mobileTabsOf,
90
102
  parentMenuItemOf,
91
103
  screenMenuItemsOf
@@ -1 +1 @@
1
- {"version":3,"file":"appMenu.js","sources":["../../src/navigation/appMenu.js"],"sourcesContent":["// El menú de la app: COMPUESTO, no escrito.\n//\n// Cada módulo declara su entrada en su propia carpeta (modules/<módulo>/menu.js) y aquí solo se juntan, se filtran por\n// permiso y se ordenan. Módulo nuevo con menú = cero ediciones en una lista central, y nadie pisa la entrada del vecino al\n// resolver un merge.\n//\n// Forma de un menu.js:\n// export const order = 10; // posición en el menú (los que no dicen, al final)\n// export const access = {ability: 'orders.view'}; // la regla de quién lo ve (accessRules.js); sin ella, todos\n// export function buildMenuItem(context) { // o buildMenuItems (en plural) para dos o más entradas\n// return {\n// to: '/orders', label: 'Pedidos', // o labelKey: 'orders:menu.label' (se traduce al pintarse)\n// description: 'Todos los pedidos…', // o descriptionKey: para las tarjetas de un inicio\n// section: 'Operación', // o sectionKey: el título de la sección del menú lateral\n// Icon: ReceiptLongOutlinedIcon,\n// badge: 3, // opcional: el numerito o la palabra a la derecha («Nuevo»)\n// mobileTab: true, // opcional: va en la barra de pestañas del teléfono (mobileTabsOf)\n// badgeKey: 'pendingPayment', // opcional: su conteo llega del servidor (AppLayout menuBadges)\n// matchPrefix: '/orders', // opcional: desde dónde se marca activo (por omisión, to)\n// access: {...}, // opcional: una entrada con otra regla que su módulo\n// children: [{to: '/orders/returns', label: 'Devoluciones', access: {...}}], // opcional: un submenú\n// };\n// }\n// buildMenuItem puede regresar null (una pantalla apagada por configuración): no sale. Un submenú se filtra igual que las\n// entradas, y si se queda sin hijas (y no lleva a ninguna pantalla propia) no sale.\n//\n// El proyecto le pasa los módulos con el glob de Vite (o una lista, en otro empaquetador):\n// const menuModules = import.meta.glob('@/modules/*/menu.js', {eager: true});\n// const menuItems = composeMenu(menuModules, {canAccess, context: {session}});\n//\n// Funciones puras, sin React: function (regla de la casa). Se prueban con node --test (appMenu.test.js).\n\n// la posición de un módulo que no dice su order: al final\nconst LAST_ORDER = 1000;\n\n// la dirección del inicio: la primera miga\nconst HOME_PATH = '/';\n\n/**\n * Las entradas del menú que le tocan a esta sesión, ordenadas.\n *\n * @param {Record<string, any>|any[]} menuModules lo que regresa import.meta.glob(..., {eager: true}) o una lista\n * @param {object} [options]\n * @param {(access: object|undefined) => boolean} [options.canAccess] la regla de permisos del proyecto; sin ella, todos\n * @param {object} [options.context] lo que reciben buildMenuItem(s): la sesión, la configuración…\n * @returns {object[]} las entradas, cada una con to y su texto\n */\nexport function composeMenu(menuModules, {canAccess = () => true, context = {}} = {}) {\n const modules = Array.isArray(menuModules) ? menuModules : Object.values(menuModules);\n return modules\n .filter((menuModule) => canAccess(menuModule.access))\n .sort((first, second) => (first.order ?? LAST_ORDER) - (second.order ?? LAST_ORDER))\n .flatMap((menuModule) => menuModule.buildMenuItems?.(context) ?? [menuModule.buildMenuItem?.(context)])\n .filter((menuItem) => Boolean(menuItem) && canAccess(menuItem.access))\n .map((menuItem) => withAllowedChildren(menuItem, canAccess))\n .filter((menuItem) => Boolean(menuItem.to) || menuItem.children?.length > 0);\n}\n\n/**\n * La entrada con solo las hijas que la sesión puede ver (una entrada sin submenú se queda igual).\n *\n * @param {object} menuItem\n * @param {(access: object|undefined) => boolean} canAccess\n * @returns {object}\n */\nfunction withAllowedChildren(menuItem, canAccess) {\n if (!Array.isArray(menuItem.children)) {\n return menuItem;\n }\n return {...menuItem, children: menuItem.children.filter((child) => Boolean(child) && canAccess(child.access))};\n}\n\n/**\n * Las entradas que llevan a una pantalla, con las de los submenús (las de arriba primero).\n *\n * @param {object[]} menuItems lo que regresa composeMenu\n * @returns {object[]}\n */\nexport function screenMenuItemsOf(menuItems) {\n return menuItems.flatMap((menuItem) => [menuItem, ...(menuItem.children ?? [])]).filter((menuItem) => Boolean(menuItem.to));\n}\n\n/**\n * ¿Está la entrada dentro de un submenú?\n *\n * @param {object[]} menuItems\n * @param {object} menuItem\n * @returns {boolean}\n */\nfunction isChildMenuItem(menuItems, menuItem) {\n return menuItems.some((candidate) => candidate.children?.includes(menuItem));\n}\n\n/**\n * La entrada del menú de la pantalla abierta: la más específica gana (/orders/15 marca «Pedidos», no «Inicio»).\n *\n * @param {Array<{to: string, matchPrefix?: string}>} menuItems lo que regresa composeMenu\n * @param {string} pathname location.pathname\n * @returns {object|null}\n */\nexport function activeMenuItemOf(menuItems, pathname) {\n const matchingItems = screenMenuItemsOf(menuItems).filter((menuItem) => {\n const prefix = menuItem.matchPrefix ?? menuItem.to;\n // el inicio solo se marca en el inicio: todas las direcciones empiezan con '/'\n const isHome = prefix === HOME_PATH;\n return pathname === prefix || (!isHome && pathname.startsWith(`${prefix}/`));\n });\n matchingItems.sort((first, second) => {\n const lengthDifference = (second.matchPrefix ?? second.to).length - (first.matchPrefix ?? first.to).length;\n if (lengthDifference !== 0) {\n return lengthDifference;\n }\n // con el mismo camino (un submenú cuya primera hija lleva a donde mismo que él), gana la hija: es la pantalla\n return Number(isChildMenuItem(menuItems, second)) - Number(isChildMenuItem(menuItems, first));\n });\n return matchingItems[0] ?? null;\n}\n\n/**\n * El submenú que contiene a una entrada (para abrirlo y para las migas); null si la entrada está arriba.\n *\n * @param {object[]} menuItems lo que regresa composeMenu\n * @param {object|null} menuItem\n * @returns {object|null}\n */\nexport function parentMenuItemOf(menuItems, menuItem) {\n return menuItems.find((candidate) => candidate.children?.includes(menuItem)) ?? null;\n}\n\n/**\n * Las migas de la pantalla abierta, sacadas del menú: el inicio, el submenú (si lo hay) y la entrada activa.\n * En el inicio, o en una pantalla que no está en el menú, regresa solo lo que encuentra (una o ninguna).\n *\n * @param {object[]} menuItems lo que regresa composeMenu\n * @param {string} pathname location.pathname\n * @returns {object[]} entradas del menú, en orden\n */\nexport function menuBreadcrumbsOf(menuItems, pathname) {\n const activeItem = activeMenuItemOf(menuItems, pathname);\n if (!activeItem) {\n return [];\n }\n const homeItem = menuItems.find((menuItem) => menuItem.to === HOME_PATH) ?? null;\n const parentItem = parentMenuItemOf(menuItems, activeItem);\n return [homeItem, parentItem, activeItem].filter((menuItem, index, crumbs) => Boolean(menuItem) && crumbs.indexOf(menuItem) === index);\n}\n\n/**\n * El badge de una entrada del menú: el conteo que llegó del servidor por su badgeKey (lo pendiente de esa pantalla), la\n * suma de sus hijas si es un submenú (como las carpetas del iPhone), o el badge fijo de su menu.js («Nuevo»). Un conteo\n * en cero no se enseña: no hay nada pendiente.\n *\n * menuBadgeOf({badgeKey: 'pendingPayment'}, {pendingPayment: 13}) // {content: 13, isCount: true}\n *\n * @param {object} menuItem una entrada de composeMenu\n * @param {Record<string, number>} [menuBadges] los conteos por badgeKey (el proyecto los pide a su servidor)\n * @returns {{content: number|string, isCount: boolean}|null}\n */\nexport function menuBadgeOf(menuItem, menuBadges = {}) {\n const countOf = (item) => {\n const count = item.badgeKey ? Number(menuBadges[item.badgeKey] ?? 0) : 0;\n return Number.isFinite(count) && count > 0 ? count : 0;\n };\n const ownCount = countOf(menuItem);\n const childrenCount = Array.isArray(menuItem.children) ? menuItem.children.reduce((total, child) => total + countOf(child), 0) : 0;\n const total = ownCount + childrenCount;\n if (total > 0) {\n return {content: total, isCount: true};\n }\n if (menuItem.badge !== undefined && menuItem.badge !== null && menuItem.badge !== '') {\n return {content: menuItem.badge, isCount: typeof menuItem.badge === 'number'};\n }\n return null;\n}\n\n// cuántas pestañas van en la barra del teléfono si no se dice otra cosa, como las de Apple: hasta 5; si hay más entradas,\n// 4 y «Más» con el resto. La barra (MobileTabBar) pasa las que de verdad caben a lo ancho\nconst MOBILE_TAB_LIMIT = 5;\n/** Cuántas puede fijar la persona en la barra: las que caben junto a «Más». */\nexport const PINNABLE_TAB_LIMIT = MOBILE_TAB_LIMIT - 1;\n\n/**\n * Con qué se reconoce una entrada del menú para recordarla (las pestañas que fijó la persona): su ruta, o su llave de\n * texto si no navega. No el texto traducido: cambia con el idioma.\n *\n * @param {{to?: string, labelKey?: string, label?: string}} menuItem\n * @returns {string}\n */\nexport function menuItemKeyOf(menuItem) {\n return menuItem.to ?? menuItem.labelKey ?? menuItem.label;\n}\n\n/**\n * Las pestañas de la barra del teléfono y lo que va en «Más». Van las que fijó la persona (pinnedKeys, en el orden del\n * menú); si no fijó ninguna, las que el menu.js marca con mobileTab: true; si ninguna lo dice, las primeras. Si todas\n * las entradas caben (tabLimit), van todas y no hay «Más»; si no, tabLimit - 1 y «Más» con el resto.\n *\n * @param {object[]} menuItems lo que regresa composeMenu\n * @param {string[]|null} [pinnedKeys] las que eligió la persona (menuItemKeyOf); las que ya no existen o ya no puede ver\n * no cuentan\n * @param {number} [tabLimit] cuántas caben en la barra contando «Más» (por omisión 5; la barra mide su ancho)\n * @returns {{tabs: object[], more: object[]}}\n */\nexport function mobileTabsOf(menuItems, pinnedKeys = null, tabLimit = MOBILE_TAB_LIMIT) {\n if (menuItems.length <= tabLimit) {\n return {tabs: menuItems, more: []};\n }\n const pinned = menuItems.filter((menuItem) => (pinnedKeys ?? []).includes(menuItemKeyOf(menuItem)));\n const marked = menuItems.filter((menuItem) => menuItem.mobileTab);\n let candidates = menuItems;\n if (pinned.length > 0) {\n candidates = pinned;\n } else if (marked.length > 0) {\n candidates = marked;\n }\n // una de las que caben es para «Más»\n const tabs = candidates.slice(0, Math.max(tabLimit - 1, 1));\n return {tabs, more: menuItems.filter((menuItem) => !tabs.includes(menuItem))};\n}\n"],"names":["total"],"mappings":"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"}
1
+ {"version":3,"file":"appMenu.js","sources":["../../src/navigation/appMenu.js"],"sourcesContent":["// El menú de la app: COMPUESTO, no escrito.\n//\n// Cada módulo declara su entrada en su propia carpeta (modules/<módulo>/menu.js) y aquí solo se juntan, se filtran por\n// permiso y se ordenan. Módulo nuevo con menú = cero ediciones en una lista central, y nadie pisa la entrada del vecino al\n// resolver un merge.\n//\n// Forma de un menu.js:\n// export const order = 10; // posición en el menú (los que no dicen, al final)\n// export const access = {ability: 'orders.view'}; // la regla de quién lo ve (accessRules.js); sin ella, todos\n// export function buildMenuItem(context) { // o buildMenuItems (en plural) para dos o más entradas\n// return {\n// to: '/orders', label: 'Pedidos', // o labelKey: 'orders:menu.label' (se traduce al pintarse)\n// description: 'Todos los pedidos…', // o descriptionKey: para las tarjetas de un inicio\n// section: 'Operación', // o sectionKey: el título de la sección del menú lateral\n// Icon: ReceiptLongOutlinedIcon, // o icon: <Iconify icon=\"mdi:receipt\" /> (un ícono ya pintado)\n// href: 'https://sistema-anterior/inicio', // en lugar de to: una liga a otro sistema (carga la página completa)\n// badge: 3, // opcional: el numerito o la palabra a la derecha («Nuevo»)\n// mobileTab: true, // opcional: va en la barra de pestañas del teléfono (mobileTabsOf)\n// badgeKey: 'pendingPayment', // opcional: su conteo llega del servidor (AppLayout menuBadges)\n// matchPrefix: '/orders', // opcional: desde dónde se marca activo (por omisión, to)\n// access: {...}, // opcional: una entrada con otra regla que su módulo\n// children: [{to: '/orders/returns', label: 'Devoluciones', access: {...}}], // opcional: un submenú\n// };\n// }\n// buildMenuItem puede regresar null (una pantalla apagada por configuración): no sale. Un submenú se filtra igual que las\n// entradas, y si se queda sin hijas (y no lleva a ninguna pantalla propia) no sale.\n//\n// El proyecto le pasa los módulos con el glob de Vite (o una lista, en otro empaquetador):\n// const menuModules = import.meta.glob('@/modules/*/menu.js', {eager: true});\n// const menuItems = composeMenu(menuModules, {canAccess, context: {session}});\n//\n// Funciones puras, sin React: function (regla de la casa). Se prueban con node --test (appMenu.test.js).\n\n// la posición de un módulo que no dice su order: al final\nconst LAST_ORDER = 1000;\n\n// la dirección del inicio: la primera miga\nconst HOME_PATH = '/';\n\n/**\n * Las entradas del menú que le tocan a esta sesión, ordenadas.\n *\n * @param {Record<string, any>|any[]} menuModules lo que regresa import.meta.glob(..., {eager: true}) o una lista\n * @param {object} [options]\n * @param {(access: object|undefined) => boolean} [options.canAccess] la regla de permisos del proyecto; sin ella, todos\n * @param {object} [options.context] lo que reciben buildMenuItem(s): la sesión, la configuración…\n * @returns {object[]} las entradas, cada una con to y su texto\n */\nexport function composeMenu(menuModules, {canAccess = () => true, context = {}} = {}) {\n const modules = Array.isArray(menuModules) ? menuModules : Object.values(menuModules);\n return modules\n .filter((menuModule) => canAccess(menuModule.access))\n .sort((first, second) => (first.order ?? LAST_ORDER) - (second.order ?? LAST_ORDER))\n .flatMap((menuModule) => menuModule.buildMenuItems?.(context) ?? [menuModule.buildMenuItem?.(context)])\n .filter((menuItem) => Boolean(menuItem) && canAccess(menuItem.access))\n .map((menuItem) => withAllowedChildren(menuItem, canAccess))\n .filter((menuItem) => Boolean(menuItem.to) || Boolean(menuItem.href) || menuItem.children?.length > 0);\n}\n\n/**\n * Las entradas con su ícono como componente (Icon), que es lo que pintan las piezas del menú: el proyecto puede dar el\n * componente o un ícono ya pintado (icon, por ejemplo <Iconify icon=\"mdi:bank\" /> de un sistema que ya usaba iconify).\n *\n * @param {object[]} menuItems\n * @returns {object[]}\n */\nexport function menuItemsWithIconsOf(menuItems) {\n return menuItems.map((menuItem) => {\n const Icon = menuItem.Icon ?? (menuItem.icon ? iconComponentOf(menuItem.icon) : undefined);\n const children = Array.isArray(menuItem.children) ? menuItemsWithIconsOf(menuItem.children) : menuItem.children;\n return {...menuItem, Icon, children};\n });\n}\n\n/** Un componente que pinta el ícono ya hecho (no recibe el tamaño: el proyecto lo pinta a su gusto). */\nfunction iconComponentOf(icon) {\n const MenuIcon = () => icon;\n return MenuIcon;\n}\n\n/**\n * La entrada con solo las hijas que la sesión puede ver (una entrada sin submenú se queda igual).\n *\n * @param {object} menuItem\n * @param {(access: object|undefined) => boolean} canAccess\n * @returns {object}\n */\nfunction withAllowedChildren(menuItem, canAccess) {\n if (!Array.isArray(menuItem.children)) {\n return menuItem;\n }\n return {...menuItem, children: menuItem.children.filter((child) => Boolean(child) && canAccess(child.access))};\n}\n\n/**\n * Las entradas que llevan a una pantalla, con las de los submenús (las de arriba primero).\n *\n * @param {object[]} menuItems lo que regresa composeMenu\n * @returns {object[]}\n */\nexport function screenMenuItemsOf(menuItems) {\n return menuItems.flatMap((menuItem) => [menuItem, ...(menuItem.children ?? [])]).filter((menuItem) => Boolean(menuItem.to));\n}\n\n/**\n * ¿Está la entrada dentro de un submenú?\n *\n * @param {object[]} menuItems\n * @param {object} menuItem\n * @returns {boolean}\n */\nfunction isChildMenuItem(menuItems, menuItem) {\n return menuItems.some((candidate) => candidate.children?.includes(menuItem));\n}\n\n/**\n * La entrada del menú de la pantalla abierta: la más específica gana (/orders/15 marca «Pedidos», no «Inicio»).\n *\n * @param {Array<{to: string, matchPrefix?: string}>} menuItems lo que regresa composeMenu\n * @param {string} pathname location.pathname\n * @returns {object|null}\n */\nexport function activeMenuItemOf(menuItems, pathname) {\n const matchingItems = screenMenuItemsOf(menuItems).filter((menuItem) => {\n const prefix = menuItem.matchPrefix ?? menuItem.to;\n // el inicio solo se marca en el inicio: todas las direcciones empiezan con '/'\n const isHome = prefix === HOME_PATH;\n return pathname === prefix || (!isHome && pathname.startsWith(`${prefix}/`));\n });\n matchingItems.sort((first, second) => {\n const lengthDifference = (second.matchPrefix ?? second.to).length - (first.matchPrefix ?? first.to).length;\n if (lengthDifference !== 0) {\n return lengthDifference;\n }\n // con el mismo camino (un submenú cuya primera hija lleva a donde mismo que él), gana la hija: es la pantalla\n return Number(isChildMenuItem(menuItems, second)) - Number(isChildMenuItem(menuItems, first));\n });\n return matchingItems[0] ?? null;\n}\n\n/**\n * El submenú que contiene a una entrada (para abrirlo y para las migas); null si la entrada está arriba.\n *\n * @param {object[]} menuItems lo que regresa composeMenu\n * @param {object|null} menuItem\n * @returns {object|null}\n */\nexport function parentMenuItemOf(menuItems, menuItem) {\n return menuItems.find((candidate) => candidate.children?.includes(menuItem)) ?? null;\n}\n\n/**\n * Las migas de la pantalla abierta, sacadas del menú: el inicio, el submenú (si lo hay) y la entrada activa.\n * En el inicio, o en una pantalla que no está en el menú, regresa solo lo que encuentra (una o ninguna).\n *\n * @param {object[]} menuItems lo que regresa composeMenu\n * @param {string} pathname location.pathname\n * @returns {object[]} entradas del menú, en orden\n */\nexport function menuBreadcrumbsOf(menuItems, pathname) {\n const activeItem = activeMenuItemOf(menuItems, pathname);\n if (!activeItem) {\n return [];\n }\n const homeItem = menuItems.find((menuItem) => menuItem.to === HOME_PATH) ?? null;\n const parentItem = parentMenuItemOf(menuItems, activeItem);\n return [homeItem, parentItem, activeItem].filter((menuItem, index, crumbs) => Boolean(menuItem) && crumbs.indexOf(menuItem) === index);\n}\n\n/**\n * El badge de una entrada del menú: el conteo que llegó del servidor por su badgeKey (lo pendiente de esa pantalla), la\n * suma de sus hijas si es un submenú (como las carpetas del iPhone), o el badge fijo de su menu.js («Nuevo»). Un conteo\n * en cero no se enseña: no hay nada pendiente.\n *\n * menuBadgeOf({badgeKey: 'pendingPayment'}, {pendingPayment: 13}) // {content: 13, isCount: true}\n *\n * @param {object} menuItem una entrada de composeMenu\n * @param {Record<string, number>} [menuBadges] los conteos por badgeKey (el proyecto los pide a su servidor)\n * @returns {{content: number|string, isCount: boolean}|null}\n */\nexport function menuBadgeOf(menuItem, menuBadges = {}) {\n const countOf = (item) => {\n const count = item.badgeKey ? Number(menuBadges[item.badgeKey] ?? 0) : 0;\n return Number.isFinite(count) && count > 0 ? count : 0;\n };\n const ownCount = countOf(menuItem);\n const childrenCount = Array.isArray(menuItem.children) ? menuItem.children.reduce((total, child) => total + countOf(child), 0) : 0;\n const total = ownCount + childrenCount;\n if (total > 0) {\n return {content: total, isCount: true};\n }\n if (menuItem.badge !== undefined && menuItem.badge !== null && menuItem.badge !== '') {\n return {content: menuItem.badge, isCount: typeof menuItem.badge === 'number'};\n }\n return null;\n}\n\n// cuántas pestañas van en la barra del teléfono si no se dice otra cosa, como las de Apple: hasta 5; si hay más entradas,\n// 4 y «Más» con el resto. La barra (MobileTabBar) pasa las que de verdad caben a lo ancho\nconst MOBILE_TAB_LIMIT = 5;\n/** Cuántas puede fijar la persona en la barra: las que caben junto a «Más». */\nexport const PINNABLE_TAB_LIMIT = MOBILE_TAB_LIMIT - 1;\n\n/**\n * Con qué se reconoce una entrada del menú para recordarla (las pestañas que fijó la persona): su ruta, o su llave de\n * texto si no navega. No el texto traducido: cambia con el idioma.\n *\n * @param {{to?: string, labelKey?: string, label?: string}} menuItem\n * @returns {string}\n */\nexport function menuItemKeyOf(menuItem) {\n return menuItem.to ?? menuItem.labelKey ?? menuItem.label;\n}\n\n/**\n * Las pestañas de la barra del teléfono y lo que va en «Más». Van las que fijó la persona (pinnedKeys, en el orden del\n * menú); si no fijó ninguna, las que el menu.js marca con mobileTab: true; si ninguna lo dice, las primeras. Si todas\n * las entradas caben (tabLimit), van todas y no hay «Más»; si no, tabLimit - 1 y «Más» con el resto.\n *\n * @param {object[]} menuItems lo que regresa composeMenu\n * @param {string[]|null} [pinnedKeys] las que eligió la persona (menuItemKeyOf); las que ya no existen o ya no puede ver\n * no cuentan\n * @param {number} [tabLimit] cuántas caben en la barra contando «Más» (por omisión 5; la barra mide su ancho)\n * @returns {{tabs: object[], more: object[]}}\n */\nexport function mobileTabsOf(menuItems, pinnedKeys = null, tabLimit = MOBILE_TAB_LIMIT) {\n if (menuItems.length <= tabLimit) {\n return {tabs: menuItems, more: []};\n }\n const pinned = menuItems.filter((menuItem) => (pinnedKeys ?? []).includes(menuItemKeyOf(menuItem)));\n const marked = menuItems.filter((menuItem) => menuItem.mobileTab);\n let candidates = menuItems;\n if (pinned.length > 0) {\n candidates = pinned;\n } else if (marked.length > 0) {\n candidates = marked;\n }\n // una de las que caben es para «Más»\n const tabs = candidates.slice(0, Math.max(tabLimit - 1, 1));\n return {tabs, more: menuItems.filter((menuItem) => !tabs.includes(menuItem))};\n}\n"],"names":["total"],"mappings":"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"}
@@ -17,10 +17,27 @@ function currentLevel() {
17
17
  }
18
18
  return defaultLevel;
19
19
  }
20
+ const sinks = [];
21
+ function addLogSink(sink) {
22
+ sinks.push(sink);
23
+ return () => {
24
+ const position = sinks.indexOf(sink);
25
+ if (position !== -1) {
26
+ sinks.splice(position, 1);
27
+ }
28
+ };
29
+ }
20
30
  function emit(level, namespace, message, context) {
21
31
  if (LEVELS[level] > currentLevel()) {
22
32
  return;
23
33
  }
34
+ const entry = { level, namespace, message, ...context !== void 0 && { context }, timestamp: Date.now() };
35
+ for (const sink of sinks) {
36
+ try {
37
+ sink(entry);
38
+ } catch {
39
+ }
40
+ }
24
41
  const write = level === "debug" ? console.log : console[level] ?? console.log;
25
42
  if (context === void 0) {
26
43
  write(`[${namespace}] ${message}`);
@@ -37,6 +54,7 @@ function createLogger(namespace) {
37
54
  };
38
55
  }
39
56
  export {
57
+ addLogSink,
40
58
  createLogger,
41
59
  setLogLevel
42
60
  };
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sources":["../../src/observability/logger.js"],"sourcesContent":["// Logger con espacio de nombres sobre console.\n//\n// Por qué un envoltorio y no una librería: corre en el navegador, son pocas líneas legibles y, cuando haga falta un\n// reporte externo (Sentry o el que sea), se enchufa en emit() sin tocar cada llamada.\n//\n// Reglas:\n// - datos estructurados: log.warn('mensaje', {status, requestId}); nunca concatenar cadenas.\n// - IMPORTANTE: sin datos personales. Nada de correos, nombres, contraseñas ni contenido de archivos: solo ids,\n// códigos, status y el request_id (misma regla que el backend: logs sin datos personales).\n// - por omisión solo salen error y warn. La aplicación pide todo en desarrollo con setLogLevel('debug') (el paquete no\n// sabe si la aplicación corre en desarrollo: import.meta.env es de quien construye). Para ver más en una instalación\n// ya desplegada:\n// localStorage.setItem('calcita:logLevel', 'debug')\n//\n// Funciones puras, no renderizan: function (regla de la casa).\n\nconst LEVELS = {error: 0, warn: 1, info: 2, debug: 3};\n\nconst LOG_LEVEL_STORAGE_KEY = 'calcita:logLevel';\n\n// el nivel cuando nadie lo pidió en localStorage; lo cambia la aplicación con setLogLevel\nlet defaultLevel = LEVELS.warn;\n\n/**\n * El nivel de lo que sale en consola: 'error', 'warn', 'info' o 'debug'. Lo llama la aplicación al arrancar, por ejemplo\n * setLogLevel(import.meta.env.DEV ? 'debug' : 'warn').\n *\n * @param {'error'|'warn'|'info'|'debug'} level\n * @returns {void}\n */\nexport function setLogLevel(level) {\n if (!(level in LEVELS)) {\n throw new Error(`setLogLevel espera error, warn, info o debug y recibió «${level}»`);\n }\n defaultLevel = LEVELS[level];\n}\n\nfunction currentLevel() {\n try {\n const override = window.localStorage?.getItem(LOG_LEVEL_STORAGE_KEY);\n if (override && override in LEVELS) {\n return LEVELS[override];\n }\n } catch {\n // ventana privada o almacenamiento bloqueado: usamos el nivel por defecto\n }\n return defaultLevel;\n}\n\nfunction emit(level, namespace, message, context) {\n if (LEVELS[level] > currentLevel()) {\n return;\n }\n\n // OJO: debug sale por console.log a propósito; console.debug cae en \"Verbose\" y DevTools lo esconde\n const write = level === 'debug' ? console.log : (console[level] ?? console.log);\n if (context === undefined) {\n write(`[${namespace}] ${message}`);\n } else {\n write(`[${namespace}] ${message}`, context);\n }\n}\n\n/**\n * Logger que antepone [namespace] a cada mensaje.\n *\n * @param {string} namespace p. ej. 'api', 'session'\n */\nexport function createLogger(namespace) {\n return {\n error: (message, context) => emit('error', namespace, message, context),\n warn: (message, context) => emit('warn', namespace, message, context),\n info: (message, context) => emit('info', namespace, message, context),\n debug: (message, context) => emit('debug', namespace, message, context),\n };\n}\n"],"names":[],"mappings":"AAgBA,MAAM,SAAS,EAAC,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAA;AAEnD,MAAM,wBAAwB;AAG9B,IAAI,eAAe,OAAO;AASnB,SAAS,YAAY,OAAO;AAC/B,MAAI,EAAE,SAAS,SAAS;AACpB,UAAM,IAAI,MAAM,2DAA2D,KAAK,GAAG;AAAA,EACvF;AACA,iBAAe,OAAO,KAAK;AAC/B;AAEA,SAAS,eAAe;AACpB,MAAI;AACA,UAAM,WAAW,OAAO,cAAc,QAAQ,qBAAqB;AACnE,QAAI,YAAY,YAAY,QAAQ;AAChC,aAAO,OAAO,QAAQ;AAAA,IAC1B;AAAA,EACJ,QAAQ;AAAA,EAER;AACA,SAAO;AACX;AAEA,SAAS,KAAK,OAAO,WAAW,SAAS,SAAS;AAC9C,MAAI,OAAO,KAAK,IAAI,gBAAgB;AAChC;AAAA,EACJ;AAGA,QAAM,QAAQ,UAAU,UAAU,QAAQ,MAAO,QAAQ,KAAK,KAAK,QAAQ;AAC3E,MAAI,YAAY,QAAW;AACvB,UAAM,IAAI,SAAS,KAAK,OAAO,EAAE;AAAA,EACrC,OAAO;AACH,UAAM,IAAI,SAAS,KAAK,OAAO,IAAI,OAAO;AAAA,EAC9C;AACJ;AAOO,SAAS,aAAa,WAAW;AACpC,SAAO;AAAA,IACH,OAAO,CAAC,SAAS,YAAY,KAAK,SAAS,WAAW,SAAS,OAAO;AAAA,IACtE,MAAM,CAAC,SAAS,YAAY,KAAK,QAAQ,WAAW,SAAS,OAAO;AAAA,IACpE,MAAM,CAAC,SAAS,YAAY,KAAK,QAAQ,WAAW,SAAS,OAAO;AAAA,IACpE,OAAO,CAAC,SAAS,YAAY,KAAK,SAAS,WAAW,SAAS,OAAO;AAAA,EAAA;AAE9E;"}
1
+ {"version":3,"file":"logger.js","sources":["../../src/observability/logger.js"],"sourcesContent":["// Logger con espacio de nombres sobre console.\n//\n// Por qué un envoltorio y no una librería: corre en el navegador, son pocas líneas legibles y un reporte externo (Sentry,\n// Datadog, un endpoint propio) se enchufa con addLogSink sin tocar cada llamada:\n// addLogSink((entry) => Sentry.logger[entry.level](`[${entry.namespace}] ${entry.message}`, entry.context));\n// Cada salida recibe lo mismo que sale en consola (respeta el nivel); una salida que truena no tumba la app.\n//\n// Reglas:\n// - datos estructurados: log.warn('mensaje', {status, requestId}); nunca concatenar cadenas.\n// - IMPORTANTE: sin datos personales. Nada de correos, nombres, contraseñas ni contenido de archivos: solo ids,\n// códigos, status y el request_id (misma regla que el backend: logs sin datos personales).\n// - por omisión solo salen error y warn. La aplicación pide todo en desarrollo con setLogLevel('debug') (el paquete no\n// sabe si la aplicación corre en desarrollo: import.meta.env es de quien construye). Para ver más en una instalación\n// ya desplegada:\n// localStorage.setItem('calcita:logLevel', 'debug')\n//\n// Funciones puras, no renderizan: function (regla de la casa).\n\nconst LEVELS = {error: 0, warn: 1, info: 2, debug: 3};\n\nconst LOG_LEVEL_STORAGE_KEY = 'calcita:logLevel';\n\n// el nivel cuando nadie lo pidió en localStorage; lo cambia la aplicación con setLogLevel\nlet defaultLevel = LEVELS.warn;\n\n/**\n * El nivel de lo que sale en consola: 'error', 'warn', 'info' o 'debug'. Lo llama la aplicación al arrancar, por ejemplo\n * setLogLevel(import.meta.env.DEV ? 'debug' : 'warn').\n *\n * @param {'error'|'warn'|'info'|'debug'} level\n * @returns {void}\n */\nexport function setLogLevel(level) {\n if (!(level in LEVELS)) {\n throw new Error(`setLogLevel espera error, warn, info o debug y recibió «${level}»`);\n }\n defaultLevel = LEVELS[level];\n}\n\nfunction currentLevel() {\n try {\n const override = window.localStorage?.getItem(LOG_LEVEL_STORAGE_KEY);\n if (override && override in LEVELS) {\n return LEVELS[override];\n }\n } catch {\n // ventana privada o almacenamiento bloqueado: usamos el nivel por defecto\n }\n return defaultLevel;\n}\n\n// las salidas propias del proyecto (addLogSink), además de la consola\nconst sinks = [];\n\n/**\n * Una salida propia para lo que se anota (un reporte externo): recibe cada anotación que sale en consola, con su nivel.\n *\n * @param {(entry: {level: 'error'|'warn'|'info'|'debug', namespace: string, message: string, context?: object, timestamp: number}) => void} sink\n * @returns {() => void} para quitarla\n */\nexport function addLogSink(sink) {\n sinks.push(sink);\n return () => {\n const position = sinks.indexOf(sink);\n if (position !== -1) {\n sinks.splice(position, 1);\n }\n };\n}\n\nfunction emit(level, namespace, message, context) {\n if (LEVELS[level] > currentLevel()) {\n return;\n }\n const entry = {level, namespace, message, ...(context !== undefined && {context}), timestamp: Date.now()};\n for (const sink of sinks) {\n try {\n sink(entry);\n } catch {\n // una salida con un error no debe tumbar la app ni la anotación en consola\n }\n }\n\n // OJO: debug sale por console.log a propósito; console.debug cae en \"Verbose\" y DevTools lo esconde\n const write = level === 'debug' ? console.log : (console[level] ?? console.log);\n if (context === undefined) {\n write(`[${namespace}] ${message}`);\n } else {\n write(`[${namespace}] ${message}`, context);\n }\n}\n\n/**\n * Logger que antepone [namespace] a cada mensaje.\n *\n * @param {string} namespace p. ej. 'api', 'session'\n */\nexport function createLogger(namespace) {\n return {\n error: (message, context) => emit('error', namespace, message, context),\n warn: (message, context) => emit('warn', namespace, message, context),\n info: (message, context) => emit('info', namespace, message, context),\n debug: (message, context) => emit('debug', namespace, message, context),\n };\n}\n"],"names":[],"mappings":"AAkBA,MAAM,SAAS,EAAC,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAA;AAEnD,MAAM,wBAAwB;AAG9B,IAAI,eAAe,OAAO;AASnB,SAAS,YAAY,OAAO;AAC/B,MAAI,EAAE,SAAS,SAAS;AACpB,UAAM,IAAI,MAAM,2DAA2D,KAAK,GAAG;AAAA,EACvF;AACA,iBAAe,OAAO,KAAK;AAC/B;AAEA,SAAS,eAAe;AACpB,MAAI;AACA,UAAM,WAAW,OAAO,cAAc,QAAQ,qBAAqB;AACnE,QAAI,YAAY,YAAY,QAAQ;AAChC,aAAO,OAAO,QAAQ;AAAA,IAC1B;AAAA,EACJ,QAAQ;AAAA,EAER;AACA,SAAO;AACX;AAGA,MAAM,QAAQ,CAAA;AAQP,SAAS,WAAW,MAAM;AAC7B,QAAM,KAAK,IAAI;AACf,SAAO,MAAM;AACT,UAAM,WAAW,MAAM,QAAQ,IAAI;AACnC,QAAI,aAAa,IAAI;AACjB,YAAM,OAAO,UAAU,CAAC;AAAA,IAC5B;AAAA,EACJ;AACJ;AAEA,SAAS,KAAK,OAAO,WAAW,SAAS,SAAS;AAC9C,MAAI,OAAO,KAAK,IAAI,gBAAgB;AAChC;AAAA,EACJ;AACA,QAAM,QAAQ,EAAC,OAAO,WAAW,SAAS,GAAI,YAAY,UAAa,EAAC,QAAA,GAAW,WAAW,KAAK,MAAI;AACvG,aAAW,QAAQ,OAAO;AACtB,QAAI;AACA,WAAK,KAAK;AAAA,IACd,QAAQ;AAAA,IAER;AAAA,EACJ;AAGA,QAAM,QAAQ,UAAU,UAAU,QAAQ,MAAO,QAAQ,KAAK,KAAK,QAAQ;AAC3E,MAAI,YAAY,QAAW;AACvB,UAAM,IAAI,SAAS,KAAK,OAAO,EAAE;AAAA,EACrC,OAAO;AACH,UAAM,IAAI,SAAS,KAAK,OAAO,IAAI,OAAO;AAAA,EAC9C;AACJ;AAOO,SAAS,aAAa,WAAW;AACpC,SAAO;AAAA,IACH,OAAO,CAAC,SAAS,YAAY,KAAK,SAAS,WAAW,SAAS,OAAO;AAAA,IACtE,MAAM,CAAC,SAAS,YAAY,KAAK,QAAQ,WAAW,SAAS,OAAO;AAAA,IACpE,MAAM,CAAC,SAAS,YAAY,KAAK,QAAQ,WAAW,SAAS,OAAO;AAAA,IACpE,OAAO,CAAC,SAAS,YAAY,KAAK,SAAS,WAAW,SAAS,OAAO;AAAA,EAAA;AAE9E;"}