@stackcraftmx/calcita 0.47.0 → 0.51.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 (488) hide show
  1. package/README.md +173 -12
  2. package/cli/calcita.js +15 -0
  3. package/cli/checkTexts.js +209 -0
  4. package/cli/packageVersions.js +6 -1
  5. package/cli/projectFiles.js +4 -2
  6. package/cli/templates/project/README.md.tpl +21 -1
  7. package/cli/templates/project/eslint.config.js.tpl +27 -0
  8. package/cli/templates/project/src/app/appTexts.js.tpl +2 -1
  9. package/cli/templates/project/src/core/publicPaths.js.tpl +10 -2
  10. package/cli/templates/project/vite.config.js.tpl +8 -1
  11. package/dist/DataTable/AuditTrailDialog.js +3 -2
  12. package/dist/DataTable/AuditTrailDialog.js.map +1 -1
  13. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +3 -2
  14. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
  15. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +3 -2
  16. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  17. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +3 -2
  18. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
  19. package/dist/DataTable/EmptyState.js +3 -2
  20. package/dist/DataTable/EmptyState.js.map +1 -1
  21. package/dist/DataTable/FilterChips.js +3 -2
  22. package/dist/DataTable/FilterChips.js.map +1 -1
  23. package/dist/DataTable/GoToPage.js +3 -2
  24. package/dist/DataTable/GoToPage.js.map +1 -1
  25. package/dist/DataTable/ServerTable.js +95 -28
  26. package/dist/DataTable/ServerTable.js.map +1 -1
  27. package/dist/DataTable/ServerTableFilterDrawer.js +8 -3
  28. package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -1
  29. package/dist/DataTable/StackedCell.js +60 -0
  30. package/dist/DataTable/StackedCell.js.map +1 -0
  31. package/dist/DataTable/TableCardsView.js +88 -0
  32. package/dist/DataTable/TableCardsView.js.map +1 -0
  33. package/dist/DataTable/columnWidths.js +30 -0
  34. package/dist/DataTable/columnWidths.js.map +1 -0
  35. package/dist/DataTable/columns.js +47 -1
  36. package/dist/DataTable/columns.js.map +1 -1
  37. package/dist/DataTable/fixedColumns.js +24 -0
  38. package/dist/DataTable/fixedColumns.js.map +1 -0
  39. package/dist/DataTable/useTableView.js +29 -0
  40. package/dist/DataTable/useTableView.js.map +1 -0
  41. package/dist/access/PermissionMatrix.js +3 -2
  42. package/dist/access/PermissionMatrix.js.map +1 -1
  43. package/dist/account/AccountProvider.js +1 -1
  44. package/dist/account/AccountSecuritySection.js +3 -2
  45. package/dist/account/AccountSecuritySection.js.map +1 -1
  46. package/dist/account/DemoTotpCodeButton.js +3 -2
  47. package/dist/account/DemoTotpCodeButton.js.map +1 -1
  48. package/dist/account/ProfileScreen.js +3 -2
  49. package/dist/account/ProfileScreen.js.map +1 -1
  50. package/dist/account/SignInScreen.js +3 -2
  51. package/dist/account/SignInScreen.js.map +1 -1
  52. package/dist/appShell/AccountMenu.js +3 -2
  53. package/dist/appShell/AccountMenu.js.map +1 -1
  54. package/dist/appShell/AppLayout.js +3 -2
  55. package/dist/appShell/AppLayout.js.map +1 -1
  56. package/dist/appShell/AppearanceSettings.js +3 -2
  57. package/dist/appShell/AppearanceSettings.js.map +1 -1
  58. package/dist/appShell/AuthLayout.js +3 -2
  59. package/dist/appShell/AuthLayout.js.map +1 -1
  60. package/dist/appShell/BarActionButton.js +11 -4
  61. package/dist/appShell/BarActionButton.js.map +1 -1
  62. package/dist/appShell/BarActions.js +3 -2
  63. package/dist/appShell/BarActions.js.map +1 -1
  64. package/dist/appShell/BarCapsule.js +3 -2
  65. package/dist/appShell/BarCapsule.js.map +1 -1
  66. package/dist/appShell/BarSearch.js +3 -2
  67. package/dist/appShell/BarSearch.js.map +1 -1
  68. package/dist/appShell/HeaderTools.js +3 -2
  69. package/dist/appShell/HeaderTools.js.map +1 -1
  70. package/dist/appShell/MobileTabBar.js +3 -2
  71. package/dist/appShell/MobileTabBar.js.map +1 -1
  72. package/dist/appShell/SettingsMenu.js +3 -2
  73. package/dist/appShell/SettingsMenu.js.map +1 -1
  74. package/dist/appShell/SideMenu.js +3 -2
  75. package/dist/appShell/SideMenu.js.map +1 -1
  76. package/dist/appShell/SideMenuEntry.js +3 -2
  77. package/dist/appShell/SideMenuEntry.js.map +1 -1
  78. package/dist/appShell/SubmenuActionsMenu.js +3 -2
  79. package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
  80. package/dist/appShell/TabBarEditor.js +3 -2
  81. package/dist/appShell/TabBarEditor.js.map +1 -1
  82. package/dist/appShell/TopMenu.js +3 -2
  83. package/dist/appShell/TopMenu.js.map +1 -1
  84. package/dist/appShell/Wordmark.js +3 -2
  85. package/dist/appShell/Wordmark.js.map +1 -1
  86. package/dist/auth/PasswordChecklist.js +3 -2
  87. package/dist/auth/PasswordChecklist.js.map +1 -1
  88. package/dist/calendar/EventCalendar.js +3 -2
  89. package/dist/calendar/EventCalendar.js.map +1 -1
  90. package/dist/calendar/EventDetails.js +3 -2
  91. package/dist/calendar/EventDetails.js.map +1 -1
  92. package/dist/datePickers/DateInput.js +28 -22
  93. package/dist/datePickers/DateInput.js.map +1 -1
  94. package/dist/datePickers/DateTimeInput.js +33 -27
  95. package/dist/datePickers/DateTimeInput.js.map +1 -1
  96. package/dist/datePickers/MonthInput.js +27 -21
  97. package/dist/datePickers/MonthInput.js.map +1 -1
  98. package/dist/datePickers/TimeInput.js +26 -20
  99. package/dist/datePickers/TimeInput.js.map +1 -1
  100. package/dist/datePickers/usePickerField.js +5 -4
  101. package/dist/datePickers/usePickerField.js.map +1 -1
  102. package/dist/downloads/DownloadManager.js +3 -2
  103. package/dist/downloads/DownloadManager.js.map +1 -1
  104. package/dist/feedback/ConfirmDialog.js +3 -2
  105. package/dist/feedback/ConfirmDialog.js.map +1 -1
  106. package/dist/feedback/ModalWaiting.js +3 -2
  107. package/dist/feedback/ModalWaiting.js.map +1 -1
  108. package/dist/feedback/NewBuildNotice.js +3 -2
  109. package/dist/feedback/NewBuildNotice.js.map +1 -1
  110. package/dist/filepond/FileDropzone.js +3 -2
  111. package/dist/filepond/FileDropzone.js.map +1 -1
  112. package/dist/forms/FormProblemAlert.js +3 -2
  113. package/dist/forms/FormProblemAlert.js.map +1 -1
  114. package/dist/forms/SubmitButton.js +7 -2
  115. package/dist/forms/SubmitButton.js.map +1 -1
  116. package/dist/forms/auth/SignInForm.js +3 -2
  117. package/dist/forms/auth/SignInForm.js.map +1 -1
  118. package/dist/forms/fieldRules.js +30 -0
  119. package/dist/forms/fieldRules.js.map +1 -0
  120. package/dist/forms/security/SecondStepForm.js +3 -2
  121. package/dist/forms/security/SecondStepForm.js.map +1 -1
  122. package/dist/forms/security/TwoStepVerificationCard.js +3 -2
  123. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  124. package/dist/forms/useValidationText.js +18 -0
  125. package/dist/forms/useValidationText.js.map +1 -0
  126. package/dist/forms/validation.js +35 -31
  127. package/dist/forms/validation.js.map +1 -1
  128. package/dist/forms/validationMessages.js +64 -7
  129. package/dist/forms/validationMessages.js.map +1 -1
  130. package/dist/forms.js +3 -0
  131. package/dist/forms.js.map +1 -1
  132. package/dist/hookForm/FormProblemAlert.js +3 -2
  133. package/dist/hookForm/FormProblemAlert.js.map +1 -1
  134. package/dist/hookForm/SubmitButton.js +7 -2
  135. package/dist/hookForm/SubmitButton.js.map +1 -1
  136. package/dist/inbox/InboxLayout.js +3 -2
  137. package/dist/inbox/InboxLayout.js.map +1 -1
  138. package/dist/inbox/MessageList.js +3 -2
  139. package/dist/inbox/MessageList.js.map +1 -1
  140. package/dist/inbox/MessageView.js +3 -2
  141. package/dist/inbox/MessageView.js.map +1 -1
  142. package/dist/index.js +186 -163
  143. package/dist/index.js.map +1 -1
  144. package/dist/inputs/CatalogInput.js +32 -25
  145. package/dist/inputs/CatalogInput.js.map +1 -1
  146. package/dist/inputs/CheckboxGroupInput.js +8 -5
  147. package/dist/inputs/CheckboxGroupInput.js.map +1 -1
  148. package/dist/inputs/ColorInput.js +7 -4
  149. package/dist/inputs/ColorInput.js.map +1 -1
  150. package/dist/inputs/FieldFrame.js +10 -4
  151. package/dist/inputs/FieldFrame.js.map +1 -1
  152. package/dist/inputs/FormSection.js +1 -1
  153. package/dist/inputs/MaskedInput.js +1 -0
  154. package/dist/inputs/MaskedInput.js.map +1 -1
  155. package/dist/inputs/NumberInput.js +29 -24
  156. package/dist/inputs/NumberInput.js.map +1 -1
  157. package/dist/inputs/RepeatingFields.js +8 -5
  158. package/dist/inputs/RepeatingFields.js.map +1 -1
  159. package/dist/inputs/SelectInput.js +8 -5
  160. package/dist/inputs/SelectInput.js.map +1 -1
  161. package/dist/inputs/TextInput.js +5 -3
  162. package/dist/inputs/TextInput.js.map +1 -1
  163. package/dist/inputs/TransferListInput.js +8 -5
  164. package/dist/inputs/TransferListInput.js.map +1 -1
  165. package/dist/inputs/WizardLayout.js +3 -2
  166. package/dist/inputs/WizardLayout.js.map +1 -1
  167. package/dist/kanban/KanbanBoard.js +3 -2
  168. package/dist/kanban/KanbanBoard.js.map +1 -1
  169. package/dist/legal/CookieNotice.js +3 -2
  170. package/dist/legal/CookieNotice.js.map +1 -1
  171. package/dist/legal/TermsAcceptanceDialog.js +3 -2
  172. package/dist/legal/TermsAcceptanceDialog.js.map +1 -1
  173. package/dist/locales/en.js +24 -0
  174. package/dist/locales/en.js.map +1 -1
  175. package/dist/locales/es.js +24 -0
  176. package/dist/locales/es.js.map +1 -1
  177. package/dist/maps/MapView.js +3 -2
  178. package/dist/maps/MapView.js.map +1 -1
  179. package/dist/mexico/CfdiSummary.js +3 -2
  180. package/dist/mexico/CfdiSummary.js.map +1 -1
  181. package/dist/mexico/EfirmaFields.js +3 -2
  182. package/dist/mexico/EfirmaFields.js.map +1 -1
  183. package/dist/mexico/mexicoIdentifiers.js +5 -0
  184. package/dist/mexico/mexicoIdentifiers.js.map +1 -1
  185. package/dist/mexico/mexicoRules.js +9 -4
  186. package/dist/mexico/mexicoRules.js.map +1 -1
  187. package/dist/mexico.js +4 -2
  188. package/dist/pwa/InstallAppSetting.js +3 -2
  189. package/dist/pwa/InstallAppSetting.js.map +1 -1
  190. package/dist/pwa/InstallInstructionsDialog.js +3 -2
  191. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  192. package/dist/pwa/InstallPromptBanner.js +3 -2
  193. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  194. package/dist/react-hook-form.js +3 -0
  195. package/dist/react-hook-form.js.map +1 -1
  196. package/dist/richText/RichTextEditor.js +7 -4
  197. package/dist/richText/RichTextEditor.js.map +1 -1
  198. package/dist/richText/RichTextView.js +3 -2
  199. package/dist/richText/RichTextView.js.map +1 -1
  200. package/dist/security/ActivityLog.js +3 -2
  201. package/dist/security/ActivityLog.js.map +1 -1
  202. package/dist/security/RecoveryCodesDialog.js +3 -2
  203. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  204. package/dist/security/SessionsCard.js +3 -2
  205. package/dist/security/SessionsCard.js.map +1 -1
  206. package/dist/table.js +21 -2
  207. package/dist/table.js.map +1 -1
  208. package/dist/theme/themePieces.js +70 -4
  209. package/dist/theme/themePieces.js.map +1 -1
  210. package/dist/toolkit/ToolkitProvider.js +8 -1
  211. package/dist/toolkit/ToolkitProvider.js.map +1 -1
  212. package/dist/toolkit/browserNavigation.js +19 -4
  213. package/dist/toolkit/browserNavigation.js.map +1 -1
  214. package/dist/toolkit/combineTexts.js +32 -0
  215. package/dist/toolkit/combineTexts.js.map +1 -0
  216. package/dist/toolkit/hashNavigation.js +17 -3
  217. package/dist/toolkit/hashNavigation.js.map +1 -1
  218. package/dist/toolkit/navigationPaths.js +15 -0
  219. package/dist/toolkit/navigationPaths.js.map +1 -1
  220. package/dist/toolkit/pathIndex.js +66 -0
  221. package/dist/toolkit/pathIndex.js.map +1 -0
  222. package/dist/toolkit/toolkitSettings.js +19 -1
  223. package/dist/toolkit/toolkitSettings.js.map +1 -1
  224. package/dist/types/DataTable/AuditTrailDialog.d.ts +6 -17
  225. package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +6 -5
  226. package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +6 -9
  227. package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +6 -14
  228. package/dist/types/DataTable/EmptyState.d.ts +6 -9
  229. package/dist/types/DataTable/FilterChips.d.ts +6 -11
  230. package/dist/types/DataTable/GoToPage.d.ts +6 -9
  231. package/dist/types/DataTable/ServerTable.d.ts +16 -80
  232. package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +6 -13
  233. package/dist/types/DataTable/StackedCell.d.ts +12 -0
  234. package/dist/types/DataTable/TableCardsView.d.ts +27 -0
  235. package/dist/types/DataTable/columnWidths.d.ts +21 -0
  236. package/dist/types/DataTable/columns.d.ts +111 -0
  237. package/dist/types/DataTable/fixedColumns.d.ts +29 -0
  238. package/dist/types/DataTable/index.d.ts +6 -1
  239. package/dist/types/DataTable/useTableView.d.ts +22 -0
  240. package/dist/types/access/PermissionMatrix.d.ts +6 -10
  241. package/dist/types/account/AccountSecuritySection.d.ts +6 -7
  242. package/dist/types/account/DemoTotpCodeButton.d.ts +6 -5
  243. package/dist/types/account/ProfileScreen.d.ts +6 -18
  244. package/dist/types/account/SignInScreen.d.ts +6 -13
  245. package/dist/types/appShell/AccountMenu.d.ts +6 -11
  246. package/dist/types/appShell/AppLayout.d.ts +3 -40
  247. package/dist/types/appShell/AppearanceSettings.d.ts +6 -12
  248. package/dist/types/appShell/AuthLayout.d.ts +3 -11
  249. package/dist/types/appShell/BarActionButton.d.ts +6 -10
  250. package/dist/types/appShell/BarActions.d.ts +6 -15
  251. package/dist/types/appShell/BarCapsule.d.ts +6 -9
  252. package/dist/types/appShell/BarSearch.d.ts +6 -15
  253. package/dist/types/appShell/HeaderTools.d.ts +6 -16
  254. package/dist/types/appShell/MobileTabBar.d.ts +6 -13
  255. package/dist/types/appShell/SettingsMenu.d.ts +6 -10
  256. package/dist/types/appShell/SideMenu.d.ts +6 -20
  257. package/dist/types/appShell/SideMenuEntry.d.ts +6 -11
  258. package/dist/types/appShell/SubmenuActionsMenu.d.ts +6 -12
  259. package/dist/types/appShell/TabBarEditor.d.ts +6 -9
  260. package/dist/types/appShell/TopMenu.d.ts +6 -5
  261. package/dist/types/appShell/Wordmark.d.ts +6 -7
  262. package/dist/types/auth/PasswordChecklist.d.ts +6 -9
  263. package/dist/types/calendar/EventCalendar.d.ts +6 -23
  264. package/dist/types/calendar/EventDetails.d.ts +6 -10
  265. package/dist/types/datePickers/DateInput.d.ts +7 -22
  266. package/dist/types/datePickers/DateTimeInput.d.ts +7 -27
  267. package/dist/types/datePickers/MonthInput.d.ts +7 -21
  268. package/dist/types/datePickers/TimeInput.d.ts +7 -21
  269. package/dist/types/datePickers/usePickerField.d.ts +5 -1
  270. package/dist/types/downloads/DownloadManager.d.ts +6 -6
  271. package/dist/types/feedback/ConfirmDialog.d.ts +6 -18
  272. package/dist/types/feedback/ModalWaiting.d.ts +6 -9
  273. package/dist/types/feedback/NewBuildNotice.d.ts +6 -10
  274. package/dist/types/filepond/FileDropzone.d.ts +6 -29
  275. package/dist/types/forms/FormProblemAlert.d.ts +6 -5
  276. package/dist/types/forms/SubmitButton.d.ts +6 -6
  277. package/dist/types/forms/auth/SignInForm.d.ts +6 -11
  278. package/dist/types/forms/fieldRules.d.ts +44 -0
  279. package/dist/types/forms/index.d.ts +1 -0
  280. package/dist/types/forms/security/SecondStepForm.d.ts +6 -14
  281. package/dist/types/forms/security/TwoStepVerificationCard.d.ts +6 -21
  282. package/dist/types/forms/useValidationText.d.ts +12 -0
  283. package/dist/types/forms/validation.d.ts +117 -174
  284. package/dist/types/forms/validationMessages.d.ts +54 -6
  285. package/dist/types/forms.d.ts +1 -1
  286. package/dist/types/hookForm/FormProblemAlert.d.ts +6 -5
  287. package/dist/types/hookForm/SubmitButton.d.ts +6 -13
  288. package/dist/types/inbox/InboxLayout.d.ts +6 -17
  289. package/dist/types/inbox/MessageList.d.ts +6 -18
  290. package/dist/types/inbox/MessageView.d.ts +6 -14
  291. package/dist/types/index.d.ts +8 -1
  292. package/dist/types/inputs/CatalogInput.d.ts +7 -24
  293. package/dist/types/inputs/CheckboxGroupInput.d.ts +7 -18
  294. package/dist/types/inputs/ColorInput.d.ts +7 -16
  295. package/dist/types/inputs/FieldFrame.d.ts +6 -10
  296. package/dist/types/inputs/MaskedInput.d.ts +3 -0
  297. package/dist/types/inputs/NumberInput.d.ts +7 -20
  298. package/dist/types/inputs/RepeatingFields.d.ts +7 -28
  299. package/dist/types/inputs/SelectInput.d.ts +7 -20
  300. package/dist/types/inputs/TextInput.d.ts +3 -0
  301. package/dist/types/inputs/TransferListInput.d.ts +7 -20
  302. package/dist/types/inputs/WizardLayout.d.ts +6 -19
  303. package/dist/types/kanban/KanbanBoard.d.ts +6 -25
  304. package/dist/types/legal/CookieNotice.d.ts +6 -16
  305. package/dist/types/legal/TermsAcceptanceDialog.d.ts +6 -18
  306. package/dist/types/locales/en.d.ts +24 -0
  307. package/dist/types/locales/es.d.ts +24 -0
  308. package/dist/types/maps/MapView.d.ts +6 -17
  309. package/dist/types/mexico/CfdiSummary.d.ts +6 -11
  310. package/dist/types/mexico/EfirmaFields.d.ts +6 -16
  311. package/dist/types/mexico/mexicoIdentifiers.d.ts +7 -0
  312. package/dist/types/mexico/mexicoRules.d.ts +19 -3
  313. package/dist/types/mexico.d.ts +2 -2
  314. package/dist/types/pwa/InstallAppSetting.d.ts +6 -6
  315. package/dist/types/pwa/InstallInstructionsDialog.d.ts +6 -9
  316. package/dist/types/pwa/InstallPromptBanner.d.ts +6 -11
  317. package/dist/types/react-hook-form.d.ts +1 -0
  318. package/dist/types/richText/RichTextEditor.d.ts +7 -22
  319. package/dist/types/richText/RichTextView.d.ts +6 -9
  320. package/dist/types/security/ActivityLog.d.ts +6 -9
  321. package/dist/types/security/RecoveryCodesDialog.d.ts +6 -11
  322. package/dist/types/security/SessionsCard.d.ts +6 -11
  323. package/dist/types/table.d.ts +3 -1
  324. package/dist/types/theme/themePieces.d.ts +55 -3
  325. package/dist/types/toolkit/ToolkitProvider.d.ts +13 -1
  326. package/dist/types/toolkit/browserNavigation.d.ts +7 -3
  327. package/dist/types/toolkit/combineTexts.d.ts +6 -0
  328. package/dist/types/toolkit/hashNavigation.d.ts +4 -2
  329. package/dist/types/toolkit/navigationPaths.d.ts +35 -0
  330. package/dist/types/toolkit/pathIndex.d.ts +27 -0
  331. package/dist/types/toolkit/toolkitSettings.d.ts +7 -4
  332. package/dist/types/ui/actions/ActionsMenu.d.ts +9 -11
  333. package/dist/types/ui/actions/CopyButton.d.ts +6 -12
  334. package/dist/types/ui/actions/DownloadMenu.d.ts +6 -29
  335. package/dist/types/ui/actions/SecretField.d.ts +6 -11
  336. package/dist/types/ui/actions/SelectionActions.d.ts +6 -13
  337. package/dist/types/ui/cards/RecordCard.d.ts +6 -31
  338. package/dist/types/ui/cards/RecordCardSkeleton.d.ts +6 -11
  339. package/dist/types/ui/countdown/Countdown.d.ts +6 -15
  340. package/dist/types/ui/documents/PdfViewerDialog.d.ts +6 -13
  341. package/dist/types/ui/filters/DateRangeFilter.d.ts +6 -10
  342. package/dist/types/ui/filters/FilterDrawer.d.ts +6 -17
  343. package/dist/types/ui/filters/PeriodRangeSelector.d.ts +6 -13
  344. package/dist/types/ui/filters/QuickFilters.d.ts +6 -12
  345. package/dist/types/ui/help/HelpButton.d.ts +6 -10
  346. package/dist/types/ui/help/HelpCarouselDialog.d.ts +6 -14
  347. package/dist/types/ui/labels/MarkedText.d.ts +10 -0
  348. package/dist/types/ui/labels/Tag.d.ts +6 -12
  349. package/dist/types/ui/labels/markedTextPieces.d.ts +22 -0
  350. package/dist/types/ui/layout/CollapsibleSection.d.ts +6 -12
  351. package/dist/types/ui/layout/ContentDialog.d.ts +6 -10
  352. package/dist/types/ui/layout/DefinitionList.d.ts +6 -9
  353. package/dist/types/ui/layout/DetailHeader.d.ts +6 -10
  354. package/dist/types/ui/layout/ErrorPage.d.ts +6 -17
  355. package/dist/types/ui/layout/FullPageLoader.d.ts +6 -6
  356. package/dist/types/ui/layout/GlassPanel.d.ts +6 -8
  357. package/dist/types/ui/layout/PageHeader.d.ts +6 -10
  358. package/dist/types/ui/layout/PageHeading.d.ts +6 -11
  359. package/dist/types/ui/layout/ProfileHeader.d.ts +6 -11
  360. package/dist/types/ui/layout/ScrollableRow.d.ts +6 -13
  361. package/dist/types/ui/layout/SectionCard.d.ts +6 -8
  362. package/dist/types/ui/metrics/BarList.d.ts +6 -12
  363. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +6 -19
  364. package/dist/types/ui/metrics/ChartTooltip.d.ts +6 -6
  365. package/dist/types/ui/metrics/ColumnChart.d.ts +6 -25
  366. package/dist/types/ui/metrics/LineChart.d.ts +6 -20
  367. package/dist/types/ui/metrics/MetricCard.d.ts +6 -17
  368. package/dist/types/ui/metrics/TableToggle.d.ts +6 -6
  369. package/dist/types/ui/metrics/chartParts.d.ts +15 -30
  370. package/dist/types/ui/navigation/BackButton.d.ts +6 -9
  371. package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +6 -9
  372. package/dist/types/ui/navigation/FontSizeToggle.d.ts +6 -8
  373. package/dist/types/ui/navigation/LanguageToggle.d.ts +6 -8
  374. package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +6 -7
  375. package/dist/types/ui/navigation/RouteTabs.d.ts +7 -12
  376. package/dist/types/ui/timeline/GroupedTimeline.d.ts +6 -23
  377. package/dist/types/upload/AttachmentsInput.d.ts +11 -1
  378. package/dist/types/upload/FileDropzone.d.ts +6 -21
  379. package/dist/types/upload/FileInput.d.ts +7 -24
  380. package/dist/types/upload/FileUploadInput.d.ts +7 -31
  381. package/dist/types/upload/ImageInput.d.ts +7 -25
  382. package/dist/types/upload/TemplateDownloadButton.d.ts +6 -18
  383. package/dist/ui/actions/ActionsMenu.js +14 -5
  384. package/dist/ui/actions/ActionsMenu.js.map +1 -1
  385. package/dist/ui/actions/CopyButton.js +8 -3
  386. package/dist/ui/actions/CopyButton.js.map +1 -1
  387. package/dist/ui/actions/DownloadMenu.js +3 -2
  388. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  389. package/dist/ui/actions/SecretField.js +8 -3
  390. package/dist/ui/actions/SecretField.js.map +1 -1
  391. package/dist/ui/actions/SelectionActions.js +3 -2
  392. package/dist/ui/actions/SelectionActions.js.map +1 -1
  393. package/dist/ui/cards/RecordCard.js +4 -3
  394. package/dist/ui/cards/RecordCard.js.map +1 -1
  395. package/dist/ui/cards/RecordCardSkeleton.js +8 -3
  396. package/dist/ui/cards/RecordCardSkeleton.js.map +1 -1
  397. package/dist/ui/countdown/Countdown.js +3 -2
  398. package/dist/ui/countdown/Countdown.js.map +1 -1
  399. package/dist/ui/documents/PdfViewerDialog.js +3 -2
  400. package/dist/ui/documents/PdfViewerDialog.js.map +1 -1
  401. package/dist/ui/filters/DateRangeFilter.js +3 -2
  402. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  403. package/dist/ui/filters/FilterDrawer.js +3 -2
  404. package/dist/ui/filters/FilterDrawer.js.map +1 -1
  405. package/dist/ui/filters/PeriodRangeSelector.js +3 -2
  406. package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
  407. package/dist/ui/filters/QuickFilters.js +3 -2
  408. package/dist/ui/filters/QuickFilters.js.map +1 -1
  409. package/dist/ui/help/HelpButton.js +8 -3
  410. package/dist/ui/help/HelpButton.js.map +1 -1
  411. package/dist/ui/help/HelpCarouselDialog.js +3 -2
  412. package/dist/ui/help/HelpCarouselDialog.js.map +1 -1
  413. package/dist/ui/labels/MarkedText.js +31 -0
  414. package/dist/ui/labels/MarkedText.js.map +1 -0
  415. package/dist/ui/labels/Tag.js +3 -2
  416. package/dist/ui/labels/Tag.js.map +1 -1
  417. package/dist/ui/labels/markedTextPieces.js +64 -0
  418. package/dist/ui/labels/markedTextPieces.js.map +1 -0
  419. package/dist/ui/layout/CollapsibleSection.js +3 -2
  420. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  421. package/dist/ui/layout/ContentDialog.js +3 -2
  422. package/dist/ui/layout/ContentDialog.js.map +1 -1
  423. package/dist/ui/layout/DefinitionList.js +3 -2
  424. package/dist/ui/layout/DefinitionList.js.map +1 -1
  425. package/dist/ui/layout/DetailHeader.js +3 -2
  426. package/dist/ui/layout/DetailHeader.js.map +1 -1
  427. package/dist/ui/layout/ErrorPage.js +3 -2
  428. package/dist/ui/layout/ErrorPage.js.map +1 -1
  429. package/dist/ui/layout/FullPageLoader.js +3 -2
  430. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  431. package/dist/ui/layout/GlassPanel.js +3 -2
  432. package/dist/ui/layout/GlassPanel.js.map +1 -1
  433. package/dist/ui/layout/PageHeader.js +3 -2
  434. package/dist/ui/layout/PageHeader.js.map +1 -1
  435. package/dist/ui/layout/PageHeading.js +3 -2
  436. package/dist/ui/layout/PageHeading.js.map +1 -1
  437. package/dist/ui/layout/ProfileHeader.js +3 -2
  438. package/dist/ui/layout/ProfileHeader.js.map +1 -1
  439. package/dist/ui/layout/ScrollableRow.js +3 -2
  440. package/dist/ui/layout/ScrollableRow.js.map +1 -1
  441. package/dist/ui/layout/SectionCard.js +3 -2
  442. package/dist/ui/layout/SectionCard.js.map +1 -1
  443. package/dist/ui/metrics/BarList.js +3 -2
  444. package/dist/ui/metrics/BarList.js.map +1 -1
  445. package/dist/ui/metrics/CalendarHeatmap.js +3 -2
  446. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  447. package/dist/ui/metrics/ChartTooltip.js +3 -2
  448. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  449. package/dist/ui/metrics/ColumnChart.js +3 -2
  450. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  451. package/dist/ui/metrics/LineChart.js +3 -2
  452. package/dist/ui/metrics/LineChart.js.map +1 -1
  453. package/dist/ui/metrics/MetricCard.js +3 -2
  454. package/dist/ui/metrics/MetricCard.js.map +1 -1
  455. package/dist/ui/metrics/TableToggle.js +3 -2
  456. package/dist/ui/metrics/TableToggle.js.map +1 -1
  457. package/dist/ui/metrics/chartParts.js +7 -4
  458. package/dist/ui/metrics/chartParts.js.map +1 -1
  459. package/dist/ui/navigation/BackButton.js +7 -2
  460. package/dist/ui/navigation/BackButton.js.map +1 -1
  461. package/dist/ui/navigation/ColorSchemeToggle.js +3 -2
  462. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  463. package/dist/ui/navigation/FontSizeToggle.js +3 -2
  464. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  465. package/dist/ui/navigation/LanguageToggle.js +3 -2
  466. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  467. package/dist/ui/navigation/PageBreadcrumbs.js +3 -2
  468. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  469. package/dist/ui/navigation/RouteTabs.js +8 -4
  470. package/dist/ui/navigation/RouteTabs.js.map +1 -1
  471. package/dist/ui/timeline/GroupedTimeline.js +3 -2
  472. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  473. package/dist/upload/AttachmentsInput.js +9 -6
  474. package/dist/upload/AttachmentsInput.js.map +1 -1
  475. package/dist/upload/FileChip.js +2 -1
  476. package/dist/upload/FileChip.js.map +1 -1
  477. package/dist/upload/FileDropzone.js +5 -4
  478. package/dist/upload/FileDropzone.js.map +1 -1
  479. package/dist/upload/FileInput.js +9 -6
  480. package/dist/upload/FileInput.js.map +1 -1
  481. package/dist/upload/FileUploadInput.js +9 -6
  482. package/dist/upload/FileUploadInput.js.map +1 -1
  483. package/dist/upload/ImageInput.js +8 -5
  484. package/dist/upload/ImageInput.js.map +1 -1
  485. package/dist/upload/TemplateDownloadButton.js +3 -2
  486. package/dist/upload/TemplateDownloadButton.js.map +1 -1
  487. package/package.json +1 -1
  488. package/cli/templates/project/src/app/combineTexts.js.tpl +0 -31
package/README.md CHANGED
@@ -53,6 +53,11 @@ El proyecto trae pruebas de pantalla (`pnpm test`, Vitest con jsdom y Testing Li
53
53
  dirección con el servidor de mentira (`openScreen` en `src/testing`) y pica como una persona. Vienen las del inicio, la de
54
54
  entrar y las del módulo de ejemplo, y `make:module` arma la de cada módulo nuevo.
55
55
 
56
+ Y dos revisiones para antes de subir: `pnpm lint` (ESLint: una variable que no existe, «t is not defined», se caza al
57
+ escribir) y `pnpm check:texts` (`calcita check:texts`: que cada `t('llave')` escrita tal cual exista en todos los idiomas y
58
+ que los idiomas tengan las mismas llaves). Sirve también en un proyecto que no salió del CLI: lee `src/**/*Texts.js` y
59
+ `src/**/texts.js`.
60
+
56
61
  ## Arrancar en un proyecto que ya existe
57
62
 
58
63
  ```bash
@@ -97,12 +102,42 @@ Sin nada más, todo sale en español, con la zona horaria del navegador, montos
97
102
  | `canAccess` | quién ve qué (menú, rutas, botones): la regla del proyecto, normalmente `createAccessChecker(...)` | solo lo que no tiene regla |
98
103
  | `buildId` | la compilación del front que corre en la pestaña, para el aviso de versión nueva | ninguna (sin aviso) |
99
104
  | `fileTypes` | tipos de archivo propios para las descargas (su ícono y su color), además de Excel, CSV y PDF | los de la casa |
105
+ | `onMissingText` | a quién avisarle de un texto que no existe (la pantalla enseña la llave): mandarlo a Sentry, tronar una prueba | un aviso en la consola, una vez por idioma y llave |
106
+ | `readValidationMessage` | cómo leer los mensajes de validación de un sistema que ya existe (ver «Mensajes de error propios») | `{key}`, `{msg}`, `{message}` y texto |
100
107
 
101
108
  Un `ToolkitProvider` dentro de otro (una parte de la app en otro idioma, una vista previa) toma del de afuera lo que no le
102
109
  digan, y lo de adentro usa el suyo: el más cercano. No le cambia nada al resto de la app. Dentro de un componente,
103
110
  `useFormatters()` da los formatos (`formatDate`, `formatCurrency`…) con el idioma, la zona y la moneda del proveedor más
104
111
  cercano; los sueltos usan los del proveedor de la raíz.
105
112
 
113
+ **Fuera de un componente** (una función que arma columnas, un diccionario, un aviso): `translateToolkit(namespace, llave,
114
+ valores)` y los formatos sueltos (`formatDate`, `formatCurrency`, `formatNumber`…) usan el idioma, la zona y la moneda del
115
+ proveedor de la raíz, ya actualizados en la misma pintada en que cambian. Así una función auxiliar no tiene que recibir `t`
116
+ ni los formatos por parámetro:
117
+
118
+ ```js
119
+ import {formatDate, translateToolkit} from '@stackcraftmx/calcita';
120
+
121
+ export const columnsOf = () => [
122
+ textColumn({field: 'registered_at', label: translateToolkit('clients', 'columns.registered'), valueFormatter: (value) => formatDate(value)}),
123
+ ];
124
+ ```
125
+
126
+ Dos cuidados: con dos proveedores (dos islas en otro idioma) los sueltos usan el de la raíz; y si la pantalla guarda el
127
+ resultado (`useMemo(() => columnsOf(), [])`), ponga el idioma en las dependencias o se queda en el de antes.
128
+
129
+ Un texto que no existe se pinta con su llave (`clients:columns.registered`) y Calcita lo avisa en la consola una vez; con
130
+ `onMissingText` se le avisa al proyecto. Un texto opcional se pide con `defaultValue` (`t('tips.extra', {defaultValue: ''})`)
131
+ y no avisa.
132
+
133
+ Para un texto con una palabra resaltada, una sola llave con marcas y `MarkedText` (como `<Trans>` de react-i18next, sin
134
+ i18next). No pinta HTML: solo las marcas que se nombran; lo demás sale como texto.
135
+
136
+ ```jsx
137
+ // es: {home: {headline: 'Optimiza tu negocio con <highlight>financiamiento</highlight> inteligente'}}
138
+ <MarkedText text={t('home.headline')} marks={{highlight: <Box component="strong" sx={{color: 'primary.main'}} />}} />
139
+ ```
140
+
106
141
  Con react-router y i18next:
107
142
 
108
143
  ```jsx
@@ -310,6 +345,23 @@ tema del proyecto y al final el `sx` que pase la pantalla. Los números se leen
310
345
  Sirve con `createToolkitTheme` y con cualquier tema de MUI (el que ya tenía un sistema, tal cual): así un sistema puede
311
346
  dejar Calcita con su propia cara, parte por parte.
312
347
 
348
+ Además, como en MUI, cada parte con nombre se puede **cambiar por otro componente** (`slots`) o **recibir props de más**
349
+ (`slotProps`), con el nombre de la parte de la tabla de abajo; desde la pantalla o para toda la app desde el tema:
350
+
351
+ ```jsx
352
+ <RecordCard slots={{avatar: StoreAvatar}} slotProps={{titles: {component: 'header'}}} … />
353
+ createTheme({components: {CalcitaRecordCard: {defaultProps: {slotProps: {root: {elevation: 3}}}}}});
354
+ ```
355
+
356
+ `className`, `style` y `sx` de `slotProps` se suman a los de Calcita; una pieza dentro de otra igual no toma los `slots` de la
357
+ de afuera. `TextInput` es la excepción: su `slotProps` es el del `TextField` de MUI (`input`, `htmlInput`…). Una pieza propia
358
+ del proyecto se arma con lo mismo: `piecePart`, `usePieceProps` y `withPieceSlots`.
359
+
360
+ El `sx` de la pantalla se **combina regla por regla** con el de la pieza, no la reemplaza: en la tabla,
361
+ `gridSx={{'& .MuiDataGrid-cell': {alignItems: 'flex-start'}}}` cambia solo la alineación y la celda conserva lo demás.
362
+ Para hacer lo mismo en un componente propio: `sx={[{p: 2}, ...sxListOf(sx)]}` (una lista de sx, como pide MUI; nunca
363
+ `{p: 2, ...sx}`, que pierde la llave entera cuando las dos traen la misma).
364
+
313
365
  <details>
314
366
  <summary>Las piezas, con su nombre en el tema y sus partes</summary>
315
367
 
@@ -324,11 +376,14 @@ dejar Calcita con su propia cara, parte por parte.
324
376
  | `CalcitaAttachmentsInput` | `root`, `actions`, `button`, `list`, `fileChip`, `limits` |
325
377
  | `CalcitaAuditTrailDialog` | `description` |
326
378
  | `CalcitaAuthLayout` | `root`, `tools`, `body`, `brand`, `logo`, `brandName`, `card`, `description`, `content`, `footer` |
379
+ | `CalcitaBackButton` | `root` |
380
+ | `CalcitaBarActionButton` | `button`, `iconButton`, `menuItem` |
327
381
  | `CalcitaBarActions` | `group` |
328
382
  | `CalcitaBarCapsule` | `root` |
329
383
  | `CalcitaBarList` | `row`, `chart` |
330
384
  | `CalcitaBarSearch` | `field` |
331
385
  | `CalcitaCalendarHeatmap` | `header`, `toggle`, `tableArea`, `dateCell`, `valueCell`, `descriptionCell`, `gridArea`, `grid`, `monthLabel`, `week`, `dayLabel`, `cell`, `legend`, `legendLess`, `legendCell`, `legendMore` |
386
+ | `CalcitaCatalogInput` | `field` |
332
387
  | `CalcitaCfdiSummary` | `root`, `header`, `amounts`, `concepts` |
333
388
  | `CalcitaChart` | `tableArea`, `legend`, `legendItem`, `legendLine`, `legendSwatch`, `reference`, `referenceLine` |
334
389
  | `CalcitaChartTooltip` | `root` |
@@ -340,23 +395,27 @@ dejar Calcita con su propia cara, parte por parte.
340
395
  | `CalcitaConfirmDialog` | `problem`, `actions` |
341
396
  | `CalcitaContentDialog` | `header`, `title`, `closeButton`, `body` |
342
397
  | `CalcitaCookieNotice` | `root`, `text`, `actions` |
398
+ | `CalcitaCopyButton` | `button` |
343
399
  | `CalcitaCountdown` | `root` |
400
+ | `CalcitaDateInput` | `picker` |
344
401
  | `CalcitaDateRangeFilter` | `chip`, `panel`, `actions` |
402
+ | `CalcitaDateTimeInput` | `picker` |
345
403
  | `CalcitaDefinitionList` | `root`, `entry`, `label`, `value` |
346
404
  | `CalcitaDemoTotpCode` | `root`, `code` |
347
405
  | `CalcitaDetailHeader` | `root`, `titleArea`, `title`, `badges`, `actions` |
348
406
  | `CalcitaDownloadManager` | `doneIcon`, `failedIcon`, `canceledIcon`, `cancelButton`, `progress`, `cancelIcon`, `item`, `itemText`, `itemStatus`, `itemActions`, `panel`, `header`, `title`, `list` |
349
407
  | `CalcitaDownloadMenu` | `button` |
350
- | `CalcitaEmptyState` | `root`, `icon` |
351
408
  | `CalcitaEfirmaFields` | `root`, `certificate`, `verify` |
409
+ | `CalcitaEmptyState` | `root`, `icon` |
352
410
  | `CalcitaErrorPage` | `root`, `actions` |
353
411
  | `CalcitaEventCalendar` | `toolbar`, `navigation`, `title`, `calendar` |
354
412
  | `CalcitaEventDetails` | `row`, `rowContent`, `titleRow`, `toneDot`, `linkRow`, `link`, `guestsTitle`, `guest`, `guestAvatar`, `guestName`, `notes`, `actions` |
413
+ | `CalcitaFieldFrame` | `root`, `label` |
355
414
  | `CalcitaFileDropzone` | `root`, `fileArea`, `fileChip`, `hint`, `actions`, `limits`, `extra` |
356
415
  | `CalcitaFileInput` | `root`, `button`, `fileChip`, `empty` |
357
416
  | `CalcitaFileUploadInput` | `root`, `fileName`, `status`, `button`, `helper` |
358
417
  | `CalcitaFilterChips` | `root`, `header`, `clearButton` |
359
- | `CalcitaFilterDrawer` | `header`, `body`, `footer` (el panel: clase `CalcitaFilterDrawer-paper`) |
418
+ | `CalcitaFilterDrawer` | `header`, `body`, `footer` |
360
419
  | `CalcitaFontSizeToggle` | `button` |
361
420
  | `CalcitaFormProblemAlert` | `root` |
362
421
  | `CalcitaFormRow` | `root`, `label`, `control` |
@@ -367,6 +426,7 @@ dejar Calcita con su propia cara, parte por parte.
367
426
  | `CalcitaGroupedTimeline` | `group`, `groupHeader`, `groupButton`, `groupTitle`, `expandIcon`, `timeline`, `opposite`, `connector`, `content`, `entryTitle` |
368
427
  | `CalcitaHeaderFilter` | `button`, `badge`, `panel` |
369
428
  | `CalcitaHeaderTools` | `divider`, `root` |
429
+ | `CalcitaHelpButton` | `root` |
370
430
  | `CalcitaHelpCarouselDialog` | `media`, `content`, `dots`, `actions` |
371
431
  | `CalcitaIconBadge` | `root`, `icon`, `label` |
372
432
  | `CalcitaImageInput` | `root`, `preview`, `image`, `placeholder`, `actions` |
@@ -383,32 +443,37 @@ dejar Calcita con su propia cara, parte por parte.
383
443
  | `CalcitaMetricCard` | `root`, `action`, `header`, `icon`, `label`, `value`, `caption`, `extra`, `trend` |
384
444
  | `CalcitaMobileTabBar` | `tab`, `tabIconPlaceholder`, `tabLabel`, `root`, `bar`, `minimized`, `account`, `sheetHeader` |
385
445
  | `CalcitaModalWaiting` | `root`, `title`, `body`, `text` |
446
+ | `CalcitaMonthInput` | `picker` |
386
447
  | `CalcitaMultiSelectFilter` | `list`, `option` |
387
448
  | `CalcitaNewBuildNotice` | `floating`, `icon`, `message`, `reloadButton`, `banner` |
449
+ | `CalcitaNumberInput` | `field` |
388
450
  | `CalcitaPageBreadcrumbs` | `root`, `link`, `current` |
389
451
  | `CalcitaPageHeader` | `root`, `subtitle`, `action` |
390
452
  | `CalcitaPageHeading` | `root`, `breadcrumbs`, `titleRow`, `title`, `actions`, `description` |
391
453
  | `CalcitaPasswordChecklist` | `title`, `list`, `item`, `hidden` |
454
+ | `CalcitaPdfViewerDialog` | `actions`, `frame` |
392
455
  | `CalcitaPeriodRangeSelector` | `field`, `select`, `root` |
393
456
  | `CalcitaPermissionMatrix` | `permission`, `checkbox`, `group`, `groupHeader`, `groupTitle`, `grid` |
394
457
  | `CalcitaPondDropzone` | `root`, `summary`, `limits`, `actions`, `extra` |
395
458
  | `CalcitaProfileHeader` | `root`, `cover`, `body`, `avatar`, `text`, `name`, `chips`, `actions` |
396
459
  | `CalcitaProfileScreen` | `sectionTitle`, `form`, `preferences` |
397
460
  | `CalcitaQuickFilters` | `root`, `caption`, `chip`, `chipLabel`, `chipCount` |
398
- | `CalcitaPdfViewerDialog` | `actions`, `frame` |
399
461
  | `CalcitaRecordCard` | `root`, `header`, `avatar`, `titles`, `status`, `body`, `group`, `fact`, `highlight`, `footer`, `details` |
462
+ | `CalcitaRecordCardSkeleton` | `root` |
400
463
  | `CalcitaRecoveryCodesDialog` | `codes`, `actions` |
401
- | `CalcitaRouteTabs` | `root`, `tab`, `badge` |
402
- | `CalcitaScrollableRow` | `root` |
403
464
  | `CalcitaRepeatingFields` | `root`, `row`, `rowHeader`, `rowContent`, `addButton`, `helper` |
404
465
  | `CalcitaRichTextEditor` | `root`, `toolbar`, `content`, `footer` |
405
466
  | `CalcitaRichTextView` | `root` |
467
+ | `CalcitaRouteTabs` | `root`, `tab`, `badge` |
468
+ | `CalcitaScrollableRow` | `root` |
406
469
  | `CalcitaSecondStepForm` | `actions` |
470
+ | `CalcitaSecretField` | `field` |
407
471
  | `CalcitaSectionCard` | `root`, `header`, `title` |
408
472
  | `CalcitaSegmentedControl` | `root` |
409
473
  | `CalcitaSelectInput` | `option`, `optionText` |
410
474
  | `CalcitaSelectionActions` | `bar`, `count`, `clear`, `phoneCount`, `phoneActions` |
411
- | `CalcitaServerTable` | `toolbar`, `summary`, `customHeader`, `count`, `countRow`, `selectionNotice`, `error`, `card`, `grid`, `pagination` |
475
+ | `CalcitaServerTable` | `cards`, `cardsPagination`, `viewToggle`, `toolbar`, `summary`, `customHeader`, `count`, `countRow`, `selectionNotice`, `error`, `card`, `grid`, `pagination` |
476
+ | `CalcitaServerTableFilterDrawer` | `button` |
412
477
  | `CalcitaSessionsCard` | `item`, `itemIcon`, `itemHeader`, `actions` |
413
478
  | `CalcitaSettingsMenu` | `divider` |
414
479
  | `CalcitaSideMenu` | `scrollArrow`, `root`, `brand`, `brandLogo`, `brandText`, `brandName`, `brandCaption`, `railTop`, `listArea`, `list`, `section`, `sectionTitle`, `sectionTitleText`, `footer`, `railBottom` |
@@ -416,19 +481,22 @@ dejar Calcita con su propia cara, parte por parte.
416
481
  | `CalcitaSignInForm` | `footer` |
417
482
  | `CalcitaSignInScreen` | `alert` |
418
483
  | `CalcitaSortableFilterHeader` | `title`, `root`, `sortButton`, `sortIndicator`, `clearButton` |
484
+ | `CalcitaStackedCell` | `root`, `line`, `label`, `valueRow`, `primary`, `secondary`, `note` |
419
485
  | `CalcitaStatusChip` | `root`, `dot`, `label`, `chip` |
420
486
  | `CalcitaSubmenuActionsMenu` | `badge` |
487
+ | `CalcitaSubmitButton` | `root` |
421
488
  | `CalcitaTabBarEditor` | `root`, `header`, `title`, `list`, `itemIcon`, `footer` |
422
489
  | `CalcitaTableToggle` | `root`, `button` |
423
490
  | `CalcitaTag` | `root` |
424
491
  | `CalcitaTemplateDownloadButton` | `root` |
425
492
  | `CalcitaTermsAcceptanceDialog` | `content`, `frame`, `footer` |
426
493
  | `CalcitaTextInput` | `field` |
494
+ | `CalcitaTimeInput` | `picker` |
427
495
  | `CalcitaTopMenu` | `link`, `root` |
428
496
  | `CalcitaTransferListInput` | `root`, `list`, `actions` |
429
497
  | `CalcitaTwoStepVerification` | `qr`, `secret`, `secretCode`, `header`, `headerTitle`, `headerText`, `menuIcon` |
430
498
  | `CalcitaWizardLayout` | `stepper`, `phoneSteps`, `actions` |
431
- | `CalcitaWordmark` | `root` |
499
+ | `CalcitaWordmark` | `root`, `mode` |
432
500
 
433
501
  </details>
434
502
 
@@ -532,11 +600,16 @@ estatus, insignias, botón para abrirlo y menú «⋯»; en el cuerpo sus datos
532
600
  principal grande; abajo lo que va a todo lo ancho y «Ver detalle», que despliega más. El orden es el que se da y nada se
533
601
  reacomoda solo: para un orden obligatorio, `trailing` pone el estatus al final y `columns` fija el ancho de cada columna.
534
602
  `RecordCardSkeleton` es su esqueleto. El menú «⋯» (`ActionsMenu`) acepta secciones, separadores, ligas, renglones propios y
535
- submenús (`children`), que se abren dentro del mismo menú con «Regresar».
536
-
537
- Para las secciones de un registro, `RouteTabs`: pestañas donde cada una es una dirección (se comparte la liga a una pestaña y
538
- «atrás» regresa a la anterior); por ruta (cada pestaña su página) o en la consulta (`mode="query"`, `?tab=pagos`, con
539
- `useActiveRouteTab` para pintar el panel), con ícono, conteo y permiso por pestaña. Junto al título de un registro van su
603
+ submenús (`children`), que se abren dentro del mismo menú con «Regresar». Un renglón con `to` es una pantalla de la app: va
604
+ con la navegación del proyecto, así el clic normal no recarga la app y Cmd/Ctrl+clic o el botón de en medio la abren en otra
605
+ pestaña (`href` es para lo de afuera). `onClick` recibe el evento del clic.
606
+
607
+ Para las secciones de un registro, `RouteTabs`: pestañas donde cada una es una dirección (se comparte la liga a una
608
+ pestaña); por ruta (cada pestaña su página, y «atrás» regresa a la anterior) o en la consulta (`mode="query"`, `?tab=pagos`,
609
+ con `useActiveRouteTab` para pintar el panel), con ícono, conteo y permiso por pestaña. Las de la consulta son vistas de la
610
+ misma ficha: por omisión reemplazan la entrada del historial, así «atrás» y el `BackButton` salen de la ficha en vez de pasear
611
+ por sus pestañas (`replace={false}` lo apaga; en las de ruta, `replace` lo prende). Las tres navegaciones de Calcita (la del
612
+ navegador, la de `#` y la de Next) y react-router saben reemplazar. Junto al título de un registro van su
540
613
  estatus (`StatusChip`) y sus insignias (`IconBadge`): un ícono y un texto corto con el
541
614
  color de lo que significan («Envío asegurado», «Pago con tarjeta»), con borde, tenue o sin caja. Mejor desde un diccionario:
542
615
  la entrada trae su ícono (`dictionaryEntryOf(value, keyPrefix, color, {icon})`) y la insignia saca de ahí texto,
@@ -554,6 +627,42 @@ El marco avisa cuando la persona encoge el menú (`onMenuCollapse`), abre el men
554
627
  sus pestañas (`onMobileTabsChange`), para guardar esas preferencias en el servidor del proyecto si se quiere; sus textos se
555
628
  cambian con `texts`.
556
629
 
630
+ ## La tabla de servidor (`/table`)
631
+
632
+ `ServerTable` pagina, filtra y ordena en el servidor (`fetchPage` recibe `{filters, sortOrders, page, pageSize}` y regresa
633
+ `{rows, total, facets}`); con `prefix` su estado va en la dirección. Lo que piden casi todas las listas de administración
634
+ viene listo:
635
+
636
+ ```jsx
637
+ import {ACTIONS_COLUMN_FIELD, ServerTable, actionsColumn, stackedColumn, statusColumn} from '@stackcraftmx/calcita/table';
638
+
639
+ const columns = [
640
+ actionsColumn({label: 'Acciones', actionsOf: (row) => [{key: 'open', label: 'Abrir', to: `/clientes/${row.id}`}]}),
641
+ stackedColumn({field: 'business_name', label: 'Empresa', entries: (row) => [row.business_name, row.rfc, row.regime]}),
642
+ stackedColumn({field: 'created_at', label: 'Fechas', entries: (row) => [ // con etiqueta
643
+ {label: 'Alta', value: formatDate(row.created_at)}, {label: 'Último cambio', value: formatDate(row.updated_at)},
644
+ ]}),
645
+ statusColumn({field: 'status', label: 'Estado', dictionary: REGISTRATION_STATUS, t}), // el ancho, medido
646
+ ];
647
+
648
+ <ServerTable prefix="clientes" fetchPage={fetchClients} columns={columns} ariaLabel="Clientes"
649
+ fixedColumn={[ACTIONS_COLUMN_FIELD, 'business_name']} // fijas al deslizar
650
+ renderCard={(row) => <RecordCard title={row.business_name} … />} /> // también en tarjetas
651
+ ```
652
+
653
+ - `stackedColumn`: los datos que se leen de corrido en una celda, uno debajo del otro, los que hagan falta (el primero en
654
+ negritas, los demás en gris). Cada dato va solo, si el encabezado ya dice qué son, o con su etiqueta arriba
655
+ (`{label, value, note}`) si el encabezado nombra al grupo. Un dato vacío no se pinta, salvo el principal o uno con
656
+ etiqueta, que enseñan «—» (`texts={{cells: {empty: '- - -'}}}` lo cambia). Los renglones toman su alto solos;
657
+ `getRowHeight` también acepta `'auto'`. `StackedCell` es la misma celda para un `renderCell` propio.
658
+ - `statusColumn`: el estatus desde su diccionario, con filtro de facetas y, con `t`, el ancho de su etiqueta más larga.
659
+ - `actionsColumn`: el «⋯» de cada renglón en su columna, de ancho fijo.
660
+ - `fixedColumn` acepta una lista: cada una se pega donde termina la anterior (las que no son la última llevan `width`).
661
+ - `renderCard`: la misma lista en tarjetas, con la misma búsqueda, filtros, orden y páginas; un botón cambia de vista y con
662
+ `prefix` la vista va en la dirección (`?clientes_view=cards`). `view`, `defaultView`, `onViewChange` y `showViewToggle`
663
+ para decidirlo desde la pantalla.
664
+ - `gridSx` se combina con los estilos de la tabla regla por regla; `gridProps` pasa lo demás al DataGrid.
665
+
557
666
  ## Eventos y textos de cada componente
558
667
 
559
668
  Cada componente de la casa avisa hacia afuera todo lo que pasa adentro (`onSelect`, `onChange`, `onViewChange`,
@@ -644,6 +753,29 @@ location /ventas/ {
644
753
  }
645
754
  ```
646
755
 
756
+ Si el prefijo lo decide el servidor al servir la página (Laravel con `config('app.url')`, un proxy con su ruta), se construye
757
+ UNA vez con archivos relativos (`ASSET_URL=./`) y la página trae `<base href="/ventas/">`: `documentBasePath()` lo lee al
758
+ abrirla (`null` si no hay `<base>`) y el proyecto de `calcita new` ya lo usa para el router y `publicUrlOf`.
759
+
760
+ ## Direcciones con nombre y textos que se juntan solos
761
+
762
+ Cuando una pantalla lleva a la de otro módulo (el detalle de un cliente regresa a la lista de donde salió), cada módulo
763
+ publica sus direcciones en su archivo y un índice las junta; se piden por nombre, sin importar el otro módulo:
764
+
765
+ ```js
766
+ // src/app/appPaths.js — el glob tiene que ser un texto literal: Vite lo resuelve al compilar
767
+ export const {pathOf} = createPathIndex(import.meta.glob('@/modules/**/paths.js', {eager: true}));
768
+
769
+ pathOf('admin:profile:detail', {groupId: 2, userId: 7675, tab: 'documentos'}) // '/admin/user/detail/2/7675?tab=documentos'
770
+ ```
771
+
772
+ Lo que la ruta pide (`:userId`) se sustituye y lo que sobra va como consulta; un nombre que no existe o una parte que falta
773
+ truena y dice qué sí hay. Las direcciones del API de cada módulo, igual: `createPathIndex(glob, {exportName: 'uris'})`.
774
+ `fillPath(ruta, partes)` llena una suelta.
775
+
776
+ Los textos de cada módulo se juntan con `combineTexts`: por idioma y namespace, los objetos de adentro se suman
777
+ (`validation.fields` de dos módulos) y un texto repetido entre módulos truena en vez de pisarse.
778
+
647
779
  ## La petición de la casa
648
780
 
649
781
  Todo lo que habla con el servidor pasa por un cliente, armado una vez por proyecto con `fetch` del navegador (sin axios):
@@ -872,6 +1004,35 @@ import {FormRow, FormSection} from '@stackcraftmx/calcita';
872
1004
  Los campos van directo dentro de la `FormSection` (sin Grid ni Stack en medio): la línea entre renglones se dibuja entre
873
1005
  hermanos.
874
1006
 
1007
+ ### Mensajes de error propios (sin tocar el esquema)
1008
+
1009
+ Los mensajes de `buildYupLocale()` son llaves que se traducen al pintarse, y cada uno lleva **su regla y su campo**. Cuando
1010
+ el negocio pide un mensaje muy específico no hace falta tocar el esquema ni los mensajes de Calcita. El primero que exista
1011
+ gana:
1012
+
1013
+ 1. el de la pantalla, en el campo: `<FormTextField name="rfc" messages={{matches: 'El RFC lleva 12 o 13 caracteres'}} />`;
1014
+ 2. el de los textos del proyecto para ese campo y esa regla, en todas las pantallas:
1015
+ `{es: {validation: {fields: {rfc: {matches: 'El RFC lleva 12 o 13 caracteres'}}}}}` (dentro de una lista,
1016
+ `contacts[2].email` busca `contacts.email`);
1017
+ 3. el general de Calcita (o el del esquema: `.required({key: 'clientes:rfcObligatorio'})`).
1018
+
1019
+ Los textos pueden decir el nombre del campo: `{{label}}` (el de `.label('RFC')` del esquema o, si no, la etiqueta del campo).
1020
+
1021
+ **Un sistema que ya existe** con su propio `setLocale` de yup: Calcita entiende sola `{key, values}`, `{msg, values}` (la de
1022
+ muchos sistemas con i18next), `{message: 'texto'}`, el error entero de react-hook-form y un texto ya escrito. Para otra
1023
+ forma, una función en el proveedor; los esquemas viejos entran sin reescribirse:
1024
+
1025
+ ```jsx
1026
+ <ToolkitProvider readValidationMessage={(error) => (error?.codigo ? {key: `legado:${error.codigo}`, values: error.datos} : undefined)}>
1027
+ ```
1028
+
1029
+ Una forma que no entiende no se pinta como «[object Object]»: sale el mensaje general y se avisa en la consola. Un campo
1030
+ propio del proyecto usa lo mismo con `useValidationText({label, messages})`.
1031
+
1032
+ Reglas listas, con su mensaje en el idioma activo: `sameAsFieldRule('password', {otherLabel: 'la contraseña'})` (confirmar
1033
+ una contraseña, una CLABE o un correo), `percentRule({max: 30})` y, en `/mexico`, `postalCodeRule()` junto a `rfcRule()`,
1034
+ `curpRule()` y `clabeRule()`.
1035
+
875
1036
  ### Campos con máscara
876
1037
 
877
1038
  `MaskedInput` (y `FormMaskedField` en `/forms` y `/react-hook-form`) es el campo de texto de la casa con una máscara: mientras
package/cli/calcita.js CHANGED
@@ -14,6 +14,7 @@ import {argv, cwd, exit, stdin, stdout} from 'node:process';
14
14
  import {parseArgs} from 'node:util';
15
15
 
16
16
  import {askQuestions} from './askQuestions.js';
17
+ import {checkTexts} from './checkTexts.js';
17
18
  import {makeComponent} from './makeComponent.js';
18
19
  import {makeForm} from './makeForm.js';
19
20
  import {makeModule} from './makeModule.js';
@@ -33,6 +34,8 @@ const HELP = `Calcita: proyectos y esqueletos con la forma de los ejemplos.
33
34
  calcita make:form <módulo> <Formulario> un formulario con sus reglas y sus textos
34
35
  calcita make:component <Componente> un componente en src/core (lo usan varios módulos)
35
36
  --module <módulo> … o solo de un módulo
37
+ calcita check:texts revisa que cada t('llave') exista en todos los idiomas, y que los idiomas tengan
38
+ las mismas llaves (lo que si no se descubre en la pantalla)
36
39
 
37
40
  Opciones:
38
41
  -y, --yes sin preguntas: lo recomendado, más lo que diga cada opción
@@ -125,6 +128,17 @@ async function runMakeComponent(componentName) {
125
128
  stdout.write('\nListo.\n');
126
129
  }
127
130
 
131
+ async function runCheckTexts() {
132
+ const {problems, checkedKeys, textFiles, languages} = await checkTexts({projectRoot: cwd()});
133
+ stdout.write(`Revisé ${checkedKeys} llaves escritas en el código contra ${textFiles.length} archivos de textos (${languages.join(', ') || 'sin idiomas'}).\n`);
134
+ if (problems.length === 0) {
135
+ stdout.write('Todo en su lugar.\n');
136
+ return;
137
+ }
138
+ stdout.write(`\n${problems.map((problem) => ` ✗ ${problem}`).join('\n')}\n`);
139
+ throw new CliError(`${problems.length} problema(s) con los textos.`);
140
+ }
141
+
128
142
  const COMMANDS = Object.freeze({
129
143
  'new': runNew,
130
144
  'init': () => runNew('.'),
@@ -132,6 +146,7 @@ const COMMANDS = Object.freeze({
132
146
  'make:page': runMakePage,
133
147
  'make:form': runMakeForm,
134
148
  'make:component': runMakeComponent,
149
+ 'check:texts': runCheckTexts,
135
150
  });
136
151
 
137
152
  try {
@@ -0,0 +1,209 @@
1
+ // calcita check:texts — revisa los textos del proyecto sin abrir la app:
2
+ // 1. que cada t('llave') escrita tal cual en el código exista en todos los idiomas (con su namespace: el de
3
+ // useToolkitTranslation('orders') del archivo, o el que trae la llave, 'common:actions.save');
4
+ // 2. que cada idioma tenga las mismas llaves que los demás (lo que falta en inglés se pintaría en español… o como llave).
5
+ // Sin esto, una llave que no existe se descubre cuando alguien la ve en la pantalla («profile:tabs.documents»).
6
+ //
7
+ // Lo que NO revisa: las llaves armadas con una variable (t(`status.${value}`)) ni las que llevan defaultValue (son opcionales).
8
+ // Para esas está el aviso de Calcita en la consola (onMissingText de ToolkitProvider).
9
+ //
10
+ // Lee los archivos de textos del proyecto (src/**/*Texts.js y src/**/texts.js: cada uno exporta {es: {...}, en: {...}}) con
11
+ // node, así que no deben importar otros archivos; el que los junta (con import.meta.glob) se salta. Los namespaces de Calcita
12
+ // (common, table, validation…) salen de su dist/.
13
+ //
14
+ // Solo node, sin dependencias (el CLI corre antes de que el proyecto tenga nada instalado): function (regla de la casa).
15
+ // Pruebas en checkTexts.test.js.
16
+ import {readdir, readFile} from 'node:fs/promises';
17
+ import {join, relative} from 'node:path';
18
+ import {pathToFileURL} from 'node:url';
19
+
20
+ const SOURCE_EXTENSIONS = /\.(jsx?|tsx?)$/;
21
+ const TEST_FILE = /\.test\.(jsx?|tsx?)$/;
22
+ const TEXT_FILE = /(?:^|\/)(?:texts|[A-Za-z0-9]+Texts)\.js$/;
23
+ const IGNORED_FOLDERS = new Set(['node_modules', 'dist', 'coverage', '.git']);
24
+ // el namespace de un archivo: useToolkitTranslation('orders') o useToolkitTranslation(['orders', …])
25
+ const NAMESPACE_CALL = /useToolkitTranslation\(\s*\[?\s*(['"])([\w-]+)\1/g;
26
+ // una llave escrita tal cual: t('list.title') o t("common:actions.save"); con lo que sigue, para ver si trae defaultValue
27
+ const KEY_CALL = /\bt\(\s*(['"])([^'"`$\n]+?)\1([^)]{0,200})/g;
28
+ const PLURAL_SUFFIXES = ['', '_zero', '_one', '_two', '_few', '_many', '_other'];
29
+
30
+ /**
31
+ * Los archivos de una carpeta y sus subcarpetas, sin node_modules ni dist.
32
+ *
33
+ * @param {string} folder
34
+ * @returns {Promise<string[]>}
35
+ */
36
+ async function filesOf(folder) {
37
+ const entries = await readdir(folder, {withFileTypes: true}).catch(() => []);
38
+ const nested = await Promise.all(entries.map(async (entry) => {
39
+ const path = join(folder, entry.name);
40
+ if (entry.isDirectory()) {
41
+ return IGNORED_FOLDERS.has(entry.name) ? [] : filesOf(path);
42
+ }
43
+ return [path];
44
+ }));
45
+ return nested.flat();
46
+ }
47
+
48
+ /**
49
+ * Suma los textos de un archivo a los de todos (por idioma y namespace, objetos por dentro). Aquí no truena con un repetido:
50
+ * lo anota, para decirlo junto con lo demás.
51
+ */
52
+ function addTexts(combined, fileTexts, duplicates, fileName) {
53
+ const mergeInto = (target, added, place) => {
54
+ for (const [key, value] of Object.entries(added)) {
55
+ const isObject = value && typeof value === 'object';
56
+ const keyPath = [...place.path, key];
57
+ if (isObject && target[key] && typeof target[key] === 'object') {
58
+ mergeInto(target[key], value, {...place, path: keyPath});
59
+ } else if (Object.hasOwn(target, key)) {
60
+ duplicates.push(`«${place.namespace}:${keyPath.join('.')}» en ${place.language} (otra vez en ${fileName})`);
61
+ } else {
62
+ target[key] = isObject ? structuredClone(value) : value;
63
+ }
64
+ }
65
+ };
66
+ for (const [language, namespaces] of Object.entries(fileTexts)) {
67
+ combined[language] ??= {};
68
+ for (const [namespace, keys] of Object.entries(namespaces ?? {})) {
69
+ combined[language][namespace] ??= {};
70
+ mergeInto(combined[language][namespace], keys, {language, namespace, path: []});
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * ¿Parece un objeto de textos? ({es: {namespace: {...}}, en: {...}}: llaves de idioma con objetos adentro)
77
+ *
78
+ * @param {unknown} value
79
+ * @returns {boolean}
80
+ */
81
+ function looksLikeTexts(value) {
82
+ return Boolean(value) && typeof value === 'object' && Object.keys(value).length > 0
83
+ && Object.entries(value).every(([language, namespaces]) => /^[a-z]{2}(-[A-Z]{2})?$/.test(language) && namespaces && typeof namespaces === 'object');
84
+ }
85
+
86
+ /** Lo que hay en un camino con puntos ('list.title') dentro de un objeto. */
87
+ function valueAt(object, dottedKey) {
88
+ return dottedKey.split('.').reduce((current, step) => (current && typeof current === 'object' ? current[step] : undefined), object);
89
+ }
90
+
91
+ /** ¿Existe la llave (o su plural) en ese namespace? */
92
+ function hasKey(namespaceTexts, key) {
93
+ return PLURAL_SUFFIXES.some((suffix) => typeof valueAt(namespaceTexts, `${key}${suffix}`) === 'string');
94
+ }
95
+
96
+ /** Todas las llaves con texto de un objeto, con puntos (para comparar idiomas); los plurales cuentan como su llave base. */
97
+ function leafKeysOf(object, prefix = '') {
98
+ return Object.entries(object ?? {}).flatMap(([key, value]) => {
99
+ const path = prefix ? `${prefix}.${key}` : key;
100
+ if (value && typeof value === 'object') {
101
+ return leafKeysOf(value, path);
102
+ }
103
+ return [path.replace(/_(zero|one|two|few|many|other)$/, '')];
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Los textos de Calcita (common, table, validation…), del dist/ del paquete que corre este CLI. Sin dist/ (el repo del paquete
109
+ * sin construir), ninguno: las llaves de esos namespaces no se revisan.
110
+ *
111
+ * @returns {Promise<Record<string, object>>}
112
+ */
113
+ async function packageTextsOf() {
114
+ const texts = {};
115
+ for (const language of ['es', 'en']) {
116
+ try {
117
+ const module = await import(new URL(`../dist/locales/${language}.js`, import.meta.url).href);
118
+ texts[language] = module.default ?? {};
119
+ } catch {
120
+ return {};
121
+ }
122
+ }
123
+ return texts;
124
+ }
125
+
126
+ /**
127
+ * @param {object} options
128
+ * @param {string} options.projectRoot la carpeta del proyecto
129
+ * @param {string} [options.sourceFolder] dónde está el código (por omisión src)
130
+ * @param {Record<string, object>} [options.packageTexts] los textos de Calcita (por omisión, los de su dist/)
131
+ * @returns {Promise<{problems: string[], checkedKeys: number, textFiles: string[], languages: string[]}>}
132
+ */
133
+ export async function checkTexts({projectRoot, sourceFolder = 'src', packageTexts}) {
134
+ const sourceRoot = join(projectRoot, sourceFolder);
135
+ const files = await filesOf(sourceRoot);
136
+ const problems = [];
137
+
138
+ // 1. los textos del proyecto
139
+ const projectTexts = {};
140
+ const duplicates = [];
141
+ const textFiles = files.filter((file) => TEXT_FILE.test(file.replaceAll('\\', '/')));
142
+ const readTextFiles = [];
143
+ for (const file of textFiles) {
144
+ const fileName = relative(projectRoot, file);
145
+ // el que JUNTA los textos (appTexts.js, con el glob de Vite) no es un archivo de textos: se salta
146
+ if ((await readFile(file, 'utf8')).includes('import.meta.glob')) {
147
+ continue;
148
+ }
149
+ readTextFiles.push(fileName);
150
+ try {
151
+ const module = await import(pathToFileURL(file).href);
152
+ const exported = Object.values(module).filter(looksLikeTexts);
153
+ if (exported.length === 0) {
154
+ problems.push(`${fileName}: no exporta textos con la forma {es: {...}, en: {...}}`);
155
+ }
156
+ exported.forEach((fileTexts) => addTexts(projectTexts, fileTexts, duplicates, fileName));
157
+ } catch (error) {
158
+ problems.push(`${fileName}: no se pudo leer (${error.message.split('\n')[0]}); un archivo de textos no debe importar otros archivos`);
159
+ }
160
+ }
161
+ problems.push(...duplicates.map((duplicate) => `texto repetido: ${duplicate}`));
162
+
163
+ const languages = Object.keys(projectTexts);
164
+ // 2. que cada idioma tenga las mismas llaves
165
+ for (const language of languages) {
166
+ for (const otherLanguage of languages.filter((other) => other !== language)) {
167
+ for (const namespace of Object.keys(projectTexts[language])) {
168
+ const otherKeys = new Set(leafKeysOf(projectTexts[otherLanguage][namespace]));
169
+ for (const key of new Set(leafKeysOf(projectTexts[language][namespace]))) {
170
+ if (!otherKeys.has(key)) {
171
+ problems.push(`«${namespace}:${key}» está en ${language} y falta en ${otherLanguage}`);
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ // 3. cada t('llave') escrita tal cual
179
+ const calcitaTexts = packageTexts ?? await packageTextsOf();
180
+ const textsOf = (language, namespace) => projectTexts[language]?.[namespace] ?? calcitaTexts[language]?.[namespace];
181
+ let checkedKeys = 0;
182
+ for (const file of files.filter((path) => SOURCE_EXTENSIONS.test(path) && !TEST_FILE.test(path))) {
183
+ const code = await readFile(file, 'utf8');
184
+ const fileNamespaces = [...code.matchAll(NAMESPACE_CALL)].map((match) => match[2]);
185
+ for (const match of code.matchAll(KEY_CALL)) {
186
+ const [, , key, rest] = match;
187
+ // con defaultValue la llave es opcional; una llave con espacios es un texto, no una llave
188
+ if (/defaultValue/.test(rest) || /\s/.test(key)) {
189
+ continue;
190
+ }
191
+ const line = code.slice(0, match.index).split('\n').length;
192
+ const place = `${relative(projectRoot, file)}:${line}`;
193
+ const [explicitNamespace, plainKey] = key.includes(':') ? key.split(':', 2) : [null, key];
194
+ const candidates = explicitNamespace ? [explicitNamespace] : fileNamespaces;
195
+ if (candidates.length === 0) {
196
+ continue;
197
+ }
198
+ checkedKeys += 1;
199
+ for (const language of languages.length > 0 ? languages : ['es']) {
200
+ const found = candidates.some((namespace) => hasKey(textsOf(language, namespace), plainKey));
201
+ if (!found) {
202
+ problems.push(`${place} «${explicitNamespace ? key : `${candidates[0]}:${key}`}» no existe en ${language}`);
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ return {problems, checkedKeys, textFiles: readTextFiles, languages};
209
+ }
@@ -7,7 +7,7 @@ import {readFile} from 'node:fs/promises';
7
7
  // Lo que el paquete no usa, así que no está en sus devDependencies: i18next (su adaptador recibe la instancia del proyecto;
8
8
  // versión con más de dos semanas publicada al escogerla, pnpm espera un día antes de instalar una recién salida), qrcode
9
9
  // (el QR de la cuenta de demostración; la misma versión que usan los ejemplos) y las pruebas de pantalla del proyecto
10
- // (Vitest con jsdom y Testing Library), en las versiones que ya se probaron juntas con el mismo Vite 7 y React 19
10
+ // (Vitest con jsdom y Testing Library), en las versiones que ya se probaron juntas con el mismo Vite 7 y React 19, y ESLint
11
11
  const VERSIONS_NOT_IN_PACKAGE = Object.freeze({
12
12
  i18next: '26.4.2',
13
13
  qrcode: '1.5.4',
@@ -16,6 +16,11 @@ const VERSIONS_NOT_IN_PACKAGE = Object.freeze({
16
16
  '@testing-library/dom': '10.4.2',
17
17
  '@testing-library/react': '16.3.3',
18
18
  '@testing-library/user-event': '14.6.7',
19
+ // ESLint del proyecto nuevo (pnpm lint): versiones con más de dos semanas publicadas al escogerlas (03/11/2026)
20
+ eslint: '10.13.0',
21
+ '@eslint/js': '10.0.1',
22
+ 'eslint-plugin-react-hooks': '7.1.1',
23
+ globals: '17.13.0',
19
24
  });
20
25
 
21
26
  /**
@@ -15,8 +15,10 @@ const BASE_DEPENDENCIES = Object.freeze([
15
15
  const HOOK_FORM_DEPENDENCIES = Object.freeze(['@hookform/resolvers', 'react-hook-form']);
16
16
  // la cuenta: las llaves de acceso (las pide @stackcraftmx/calcita/account) y el QR de la demostración
17
17
  const ACCOUNT_DEPENDENCIES = Object.freeze(['@simplewebauthn/browser', 'qrcode']);
18
- // las pruebas de pantalla (pnpm test): Vitest con jsdom como navegador y Testing Library para picar como una persona
18
+ // las pruebas de pantalla (pnpm test): Vitest con jsdom como navegador y Testing Library para picar como una persona; y ESLint
19
+ // (pnpm lint), que caza al escribir lo que si no se ve en la pantalla del cliente (una variable que no existe: «t is not defined»)
19
20
  const DEV_DEPENDENCIES = Object.freeze([
21
+ '@eslint/js', 'eslint', 'eslint-plugin-react-hooks', 'globals',
20
22
  '@testing-library/dom', '@testing-library/react', '@testing-library/user-event', '@vitejs/plugin-react', 'jsdom', 'vite', 'vitest',
21
23
  ]);
22
24
 
@@ -43,7 +45,7 @@ export function packageJsonOf({packageName, answers, calcitaPackage}) {
43
45
  version: '0.1.0',
44
46
  private: true,
45
47
  type: 'module',
46
- scripts: {dev: 'vite', build: 'vite build', preview: 'vite preview', test: 'vitest run'},
48
+ scripts: {dev: 'vite', build: 'vite build', preview: 'vite preview', test: 'vitest run', lint: 'eslint .', 'check:texts': 'calcita check:texts'},
47
49
  dependencies: sortedByKey({'@stackcraftmx/calcita': `^${calcitaPackage.version}`, ...versionsOf(dependencyNames, calcitaPackage)}),
48
50
  devDependencies: sortedByKey(versionsOf([...DEV_DEPENDENCIES], calcitaPackage)),
49
51
  engines: {node: '>=22'},