@stackcraftmx/calcita 0.32.1 → 0.47.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 (338) hide show
  1. package/README.md +263 -2
  2. package/cli/packageVersions.js +12 -3
  3. package/cli/projectFiles.js +5 -2
  4. package/cli/templates/module/__Name__Page.test.jsx.tpl +24 -0
  5. package/cli/templates/project/README.md.tpl +14 -1
  6. package/cli/templates/project/src/app/appRouter.jsx.tpl +5 -2
  7. package/cli/templates/project/src/core/accountConnector.js.tpl +4 -2
  8. package/cli/templates/project/src/modules/auth/SignInPage.test.jsx.tpl +20 -0
  9. package/cli/templates/project/src/modules/home/HomePage.test.jsx.tpl +16 -0
  10. package/cli/templates/project/src/testing/browserGaps.js.tpl +70 -0
  11. package/cli/templates/project/src/testing/openScreen.jsx.tpl +58 -0
  12. package/cli/templates/project/src/testing/setup.js.tpl +25 -0
  13. package/cli/templates/project/vite.config.js.tpl +12 -0
  14. package/dist/DataTable/GoToPage.js +69 -13
  15. package/dist/DataTable/GoToPage.js.map +1 -1
  16. package/dist/DataTable/ServerTable.js +2 -0
  17. package/dist/DataTable/ServerTable.js.map +1 -1
  18. package/dist/DataTable/ServerTableFilterDrawer.js +52 -0
  19. package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -0
  20. package/dist/DataTable/filterContext.js.map +1 -1
  21. package/dist/DataTable/goToPageOptions.js +17 -0
  22. package/dist/DataTable/goToPageOptions.js.map +1 -0
  23. package/dist/date-pickers.js +28 -2
  24. package/dist/date-pickers.js.map +1 -1
  25. package/dist/datePickers/DateInput.js +15 -34
  26. package/dist/datePickers/DateInput.js.map +1 -1
  27. package/dist/datePickers/DateTimeInput.js +75 -0
  28. package/dist/datePickers/DateTimeInput.js.map +1 -0
  29. package/dist/datePickers/FormDateTimeField.js +12 -0
  30. package/dist/datePickers/FormDateTimeField.js.map +1 -0
  31. package/dist/datePickers/FormMonthField.js +12 -0
  32. package/dist/datePickers/FormMonthField.js.map +1 -0
  33. package/dist/datePickers/FormTimeField.js +12 -0
  34. package/dist/datePickers/FormTimeField.js.map +1 -0
  35. package/dist/datePickers/MonthInput.js +58 -0
  36. package/dist/datePickers/MonthInput.js.map +1 -0
  37. package/dist/datePickers/PickerFrame.js +27 -0
  38. package/dist/datePickers/PickerFrame.js.map +1 -0
  39. package/dist/datePickers/TimeInput.js +55 -0
  40. package/dist/datePickers/TimeInput.js.map +1 -0
  41. package/dist/datePickers/dateTimeRules.js +24 -0
  42. package/dist/datePickers/dateTimeRules.js.map +1 -0
  43. package/dist/datePickers/pickerValues.js +106 -0
  44. package/dist/datePickers/pickerValues.js.map +1 -0
  45. package/dist/datePickers/useCurrentMinute.js +28 -0
  46. package/dist/datePickers/useCurrentMinute.js.map +1 -0
  47. package/dist/datePickers/usePickerField.js +22 -0
  48. package/dist/datePickers/usePickerField.js.map +1 -0
  49. package/dist/dictionaries/dictionaries.js +3 -1
  50. package/dist/dictionaries/dictionaries.js.map +1 -1
  51. package/dist/forms/FormAttachmentsField.js +12 -0
  52. package/dist/forms/FormAttachmentsField.js.map +1 -0
  53. package/dist/forms/FormCheckboxGroupField.js +12 -0
  54. package/dist/forms/FormCheckboxGroupField.js.map +1 -0
  55. package/dist/forms/FormFileField.js +12 -0
  56. package/dist/forms/FormFileField.js.map +1 -0
  57. package/dist/forms/FormFileUploadField.js +12 -0
  58. package/dist/forms/FormFileUploadField.js.map +1 -0
  59. package/dist/forms/FormImageField.js +12 -0
  60. package/dist/forms/FormImageField.js.map +1 -0
  61. package/dist/forms/FormMaskedField.js +12 -0
  62. package/dist/forms/FormMaskedField.js.map +1 -0
  63. package/dist/forms/FormRepeatingFields.js +54 -0
  64. package/dist/forms/FormRepeatingFields.js.map +1 -0
  65. package/dist/forms/FormTransferListField.js +12 -0
  66. package/dist/forms/FormTransferListField.js.map +1 -0
  67. package/dist/forms/applyServerProblem.js +10 -3
  68. package/dist/forms/applyServerProblem.js.map +1 -1
  69. package/dist/forms/listRules.js +111 -0
  70. package/dist/forms/listRules.js.map +1 -0
  71. package/dist/forms/serverProblemFields.js +17 -0
  72. package/dist/forms/serverProblemFields.js.map +1 -1
  73. package/dist/forms.js +51 -29
  74. package/dist/forms.js.map +1 -1
  75. package/dist/hookForm/FormAttachmentsField.js +12 -0
  76. package/dist/hookForm/FormAttachmentsField.js.map +1 -0
  77. package/dist/hookForm/FormCheckboxGroupField.js +12 -0
  78. package/dist/hookForm/FormCheckboxGroupField.js.map +1 -0
  79. package/dist/hookForm/FormFileField.js +12 -0
  80. package/dist/hookForm/FormFileField.js.map +1 -0
  81. package/dist/hookForm/FormFileUploadField.js +12 -0
  82. package/dist/hookForm/FormFileUploadField.js.map +1 -0
  83. package/dist/hookForm/FormImageField.js +12 -0
  84. package/dist/hookForm/FormImageField.js.map +1 -0
  85. package/dist/hookForm/FormMaskedField.js +12 -0
  86. package/dist/hookForm/FormMaskedField.js.map +1 -0
  87. package/dist/hookForm/FormRepeatingFields.js +44 -0
  88. package/dist/hookForm/FormRepeatingFields.js.map +1 -0
  89. package/dist/hookForm/FormTransferListField.js +12 -0
  90. package/dist/hookForm/FormTransferListField.js.map +1 -0
  91. package/dist/hookForm/applyServerProblem.js +8 -3
  92. package/dist/hookForm/applyServerProblem.js.map +1 -1
  93. package/dist/index.js +188 -100
  94. package/dist/index.js.map +1 -1
  95. package/dist/inputs/CatalogInput.js +74 -7
  96. package/dist/inputs/CatalogInput.js.map +1 -1
  97. package/dist/inputs/CheckboxGroupInput.js +100 -0
  98. package/dist/inputs/CheckboxGroupInput.js.map +1 -0
  99. package/dist/inputs/MaskedInput.js +86 -0
  100. package/dist/inputs/MaskedInput.js.map +1 -0
  101. package/dist/inputs/NumberInput.js +12 -1
  102. package/dist/inputs/NumberInput.js.map +1 -1
  103. package/dist/inputs/RepeatingFields.js +177 -0
  104. package/dist/inputs/RepeatingFields.js.map +1 -0
  105. package/dist/inputs/SelectInput.js +43 -5
  106. package/dist/inputs/SelectInput.js.map +1 -1
  107. package/dist/inputs/TransferListInput.js +215 -0
  108. package/dist/inputs/TransferListInput.js.map +1 -0
  109. package/dist/inputs/inputMasks.js +84 -0
  110. package/dist/inputs/inputMasks.js.map +1 -0
  111. package/dist/inputs/selectAllState.js +20 -0
  112. package/dist/inputs/selectAllState.js.map +1 -0
  113. package/dist/inputs/useRowKeys.js +26 -0
  114. package/dist/inputs/useRowKeys.js.map +1 -0
  115. package/dist/legal/CookieNotice.js +79 -0
  116. package/dist/legal/CookieNotice.js.map +1 -0
  117. package/dist/legal/TermsAcceptanceDialog.js +129 -0
  118. package/dist/legal/TermsAcceptanceDialog.js.map +1 -0
  119. package/dist/legal/consentStorage.js +18 -0
  120. package/dist/legal/consentStorage.js.map +1 -0
  121. package/dist/legal/useCookieConsent.js +17 -0
  122. package/dist/legal/useCookieConsent.js.map +1 -0
  123. package/dist/locales/en.js +233 -1
  124. package/dist/locales/en.js.map +1 -1
  125. package/dist/locales/es.js +233 -1
  126. package/dist/locales/es.js.map +1 -1
  127. package/dist/mexico/CfdiSummary.js +81 -0
  128. package/dist/mexico/CfdiSummary.js.map +1 -0
  129. package/dist/mexico/EfirmaFields.js +167 -0
  130. package/dist/mexico/EfirmaFields.js.map +1 -0
  131. package/dist/mexico/certificateReader.js +79 -0
  132. package/dist/mexico/certificateReader.js.map +1 -0
  133. package/dist/mexico/cfdiReader.js +81 -0
  134. package/dist/mexico/cfdiReader.js.map +1 -0
  135. package/dist/mexico/derReader.js +81 -0
  136. package/dist/mexico/derReader.js.map +1 -0
  137. package/dist/mexico/efirmaKey.js +51 -0
  138. package/dist/mexico/efirmaKey.js.map +1 -0
  139. package/dist/mexico/mexicoIdentifiers.js +86 -0
  140. package/dist/mexico/mexicoIdentifiers.js.map +1 -0
  141. package/dist/mexico/mexicoRules.js +16 -0
  142. package/dist/mexico/mexicoRules.js.map +1 -0
  143. package/dist/mexico.js +32 -0
  144. package/dist/mexico.js.map +1 -0
  145. package/dist/react-hook-form.js +40 -17
  146. package/dist/react-hook-form.js.map +1 -1
  147. package/dist/rich-text.js +19 -0
  148. package/dist/rich-text.js.map +1 -0
  149. package/dist/richText/FormRichTextField.js +12 -0
  150. package/dist/richText/FormRichTextField.js.map +1 -0
  151. package/dist/richText/RichTextEditor.js +108 -0
  152. package/dist/richText/RichTextEditor.js.map +1 -0
  153. package/dist/richText/RichTextLinkPopover.js +57 -0
  154. package/dist/richText/RichTextLinkPopover.js.map +1 -0
  155. package/dist/richText/RichTextToolbar.js +189 -0
  156. package/dist/richText/RichTextToolbar.js.map +1 -0
  157. package/dist/richText/RichTextView.js +26 -0
  158. package/dist/richText/RichTextView.js.map +1 -0
  159. package/dist/richText/richTextContentSx.js +42 -0
  160. package/dist/richText/richTextContentSx.js.map +1 -0
  161. package/dist/richText/richTextExtensions.js +44 -0
  162. package/dist/richText/richTextExtensions.js.map +1 -0
  163. package/dist/richText/richTextRules.js +46 -0
  164. package/dist/richText/richTextRules.js.map +1 -0
  165. package/dist/security/SessionsCard.js +11 -7
  166. package/dist/security/SessionsCard.js.map +1 -1
  167. package/dist/table.js +2 -0
  168. package/dist/table.js.map +1 -1
  169. package/dist/theme/components/glassComponents.js +27 -0
  170. package/dist/theme/components/glassComponents.js.map +1 -1
  171. package/dist/theme/components/minimalComponents.js +12 -0
  172. package/dist/theme/components/minimalComponents.js.map +1 -1
  173. package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +21 -0
  174. package/dist/types/DataTable/filterContext.d.ts +2 -1
  175. package/dist/types/DataTable/goToPageOptions.d.ts +15 -0
  176. package/dist/types/DataTable/index.d.ts +1 -0
  177. package/dist/types/date-pickers.d.ts +10 -1
  178. package/dist/types/datePickers/DateInput.d.ts +6 -4
  179. package/dist/types/datePickers/DateTimeInput.d.ts +50 -0
  180. package/dist/types/datePickers/FormDateTimeField.d.ts +7 -0
  181. package/dist/types/datePickers/FormMonthField.d.ts +7 -0
  182. package/dist/types/datePickers/FormTimeField.d.ts +7 -0
  183. package/dist/types/datePickers/MonthInput.d.ts +39 -0
  184. package/dist/types/datePickers/PickerFrame.d.ts +22 -0
  185. package/dist/types/datePickers/TimeInput.d.ts +38 -0
  186. package/dist/types/datePickers/dateTimeRules.d.ts +31 -0
  187. package/dist/types/datePickers/pickerValues.d.ts +114 -0
  188. package/dist/types/datePickers/useCurrentMinute.d.ts +5 -0
  189. package/dist/types/datePickers/usePickerField.d.ts +24 -0
  190. package/dist/types/dictionaries/dictionaries.d.ts +6 -3
  191. package/dist/types/forms/FormAttachmentsField.d.ts +7 -0
  192. package/dist/types/forms/FormCheckboxGroupField.d.ts +7 -0
  193. package/dist/types/forms/FormFileField.d.ts +7 -0
  194. package/dist/types/forms/FormFileUploadField.d.ts +7 -0
  195. package/dist/types/forms/FormImageField.d.ts +7 -0
  196. package/dist/types/forms/FormMaskedField.d.ts +7 -0
  197. package/dist/types/forms/FormRepeatingFields.d.ts +25 -0
  198. package/dist/types/forms/FormTransferListField.d.ts +7 -0
  199. package/dist/types/forms/index.d.ts +10 -1
  200. package/dist/types/forms/listRules.d.ts +48 -0
  201. package/dist/types/forms/serverProblemFields.d.ts +13 -0
  202. package/dist/types/forms.d.ts +1 -1
  203. package/dist/types/hookForm/FormAttachmentsField.d.ts +8 -0
  204. package/dist/types/hookForm/FormCheckboxGroupField.d.ts +8 -0
  205. package/dist/types/hookForm/FormFileField.d.ts +8 -0
  206. package/dist/types/hookForm/FormFileUploadField.d.ts +8 -0
  207. package/dist/types/hookForm/FormImageField.d.ts +8 -0
  208. package/dist/types/hookForm/FormMaskedField.d.ts +9 -0
  209. package/dist/types/hookForm/FormRepeatingFields.d.ts +27 -0
  210. package/dist/types/hookForm/FormTransferListField.d.ts +8 -0
  211. package/dist/types/index.d.ts +25 -3
  212. package/dist/types/inputs/CatalogInput.d.ts +7 -1
  213. package/dist/types/inputs/CheckboxGroupInput.d.ts +39 -0
  214. package/dist/types/inputs/MaskedInput.d.ts +35 -0
  215. package/dist/types/inputs/RepeatingFields.d.ts +52 -0
  216. package/dist/types/inputs/SelectInput.d.ts +6 -1
  217. package/dist/types/inputs/TransferListInput.d.ts +43 -0
  218. package/dist/types/inputs/inputMasks.d.ts +52 -0
  219. package/dist/types/inputs/selectAllState.d.ts +11 -0
  220. package/dist/types/inputs/useRowKeys.d.ts +11 -0
  221. package/dist/types/legal/CookieNotice.d.ts +30 -0
  222. package/dist/types/legal/TermsAcceptanceDialog.d.ts +32 -0
  223. package/dist/types/legal/consentStorage.d.ts +16 -0
  224. package/dist/types/legal/useCookieConsent.d.ts +13 -0
  225. package/dist/types/locales/en.d.ts +232 -0
  226. package/dist/types/locales/es.d.ts +232 -0
  227. package/dist/types/mexico/CfdiSummary.d.ts +17 -0
  228. package/dist/types/mexico/EfirmaFields.d.ts +38 -0
  229. package/dist/types/mexico/certificateReader.d.ts +37 -0
  230. package/dist/types/mexico/cfdiReader.d.ts +95 -0
  231. package/dist/types/mexico/derReader.d.ts +54 -0
  232. package/dist/types/mexico/efirmaKey.d.ts +26 -0
  233. package/dist/types/mexico/mexicoIdentifiers.d.ts +61 -0
  234. package/dist/types/mexico/mexicoRules.d.ts +29 -0
  235. package/dist/types/mexico.d.ts +11 -0
  236. package/dist/types/react-hook-form.d.ts +10 -1
  237. package/dist/types/rich-text.d.ts +5 -0
  238. package/dist/types/richText/FormRichTextField.d.ts +7 -0
  239. package/dist/types/richText/RichTextEditor.d.ts +42 -0
  240. package/dist/types/richText/RichTextLinkPopover.d.ts +17 -0
  241. package/dist/types/richText/RichTextToolbar.d.ts +15 -0
  242. package/dist/types/richText/RichTextView.d.ts +13 -0
  243. package/dist/types/richText/richTextContentSx.d.ts +7 -0
  244. package/dist/types/richText/richTextExtensions.d.ts +15 -0
  245. package/dist/types/richText/richTextRules.d.ts +41 -0
  246. package/dist/types/table.d.ts +1 -1
  247. package/dist/types/ui/actions/ActionsMenu.d.ts +18 -3
  248. package/dist/types/ui/actions/CopyButton.d.ts +7 -4
  249. package/dist/types/ui/actions/tableRowText.d.ts +6 -0
  250. package/dist/types/ui/cards/RecordCard.d.ts +65 -0
  251. package/dist/types/ui/cards/RecordCardSkeleton.d.ts +17 -0
  252. package/dist/types/ui/countdown/Countdown.d.ts +31 -0
  253. package/dist/types/ui/countdown/countdownParts.d.ts +39 -0
  254. package/dist/types/ui/countdown/useCountdown.d.ts +14 -0
  255. package/dist/types/ui/documents/PdfViewerDialog.d.ts +21 -0
  256. package/dist/types/ui/filters/FilterDrawer.d.ts +34 -0
  257. package/dist/types/ui/filters/filterDrawerFields.d.ts +139 -0
  258. package/dist/types/ui/help/HelpButton.d.ts +15 -0
  259. package/dist/types/ui/help/HelpCarouselDialog.d.ts +35 -0
  260. package/dist/types/ui/help/useHelpCarousel.d.ts +21 -0
  261. package/dist/types/ui/index.d.ts +10 -0
  262. package/dist/types/ui/labels/IconBadge.d.ts +34 -0
  263. package/dist/types/ui/navigation/RouteTabs.d.ts +44 -0
  264. package/dist/types/ui/navigation/routeTabMatching.d.ts +27 -0
  265. package/dist/types/upload/AttachmentsInput.d.ts +62 -0
  266. package/dist/types/upload/FileChip.d.ts +18 -0
  267. package/dist/types/upload/FileDropzone.d.ts +3 -0
  268. package/dist/types/upload/FileInput.d.ts +48 -0
  269. package/dist/types/upload/FileUploadInput.d.ts +80 -0
  270. package/dist/types/upload/ImageInput.d.ts +53 -0
  271. package/dist/types/upload/TemplateDownloadButton.d.ts +39 -0
  272. package/dist/types/upload/fileAppearance.d.ts +8 -0
  273. package/dist/types/upload/fileRules.d.ts +38 -0
  274. package/dist/types/upload/transferProgress.d.ts +18 -0
  275. package/dist/types/upload/uploadMachine.d.ts +6 -2
  276. package/dist/types/upload/uploadWithProgress.d.ts +6 -2
  277. package/dist/types/upload/useFilePicker.d.ts +39 -0
  278. package/dist/ui/actions/ActionsMenu.js +98 -15
  279. package/dist/ui/actions/ActionsMenu.js.map +1 -1
  280. package/dist/ui/actions/CopyButton.js +31 -11
  281. package/dist/ui/actions/CopyButton.js.map +1 -1
  282. package/dist/ui/actions/tableRowText.js +7 -0
  283. package/dist/ui/actions/tableRowText.js.map +1 -0
  284. package/dist/ui/cards/RecordCard.js +145 -0
  285. package/dist/ui/cards/RecordCard.js.map +1 -0
  286. package/dist/ui/cards/RecordCardSkeleton.js +33 -0
  287. package/dist/ui/cards/RecordCardSkeleton.js.map +1 -0
  288. package/dist/ui/countdown/Countdown.js +38 -0
  289. package/dist/ui/countdown/Countdown.js.map +1 -0
  290. package/dist/ui/countdown/countdownParts.js +34 -0
  291. package/dist/ui/countdown/countdownParts.js.map +1 -0
  292. package/dist/ui/countdown/useCountdown.js +35 -0
  293. package/dist/ui/countdown/useCountdown.js.map +1 -0
  294. package/dist/ui/documents/PdfViewerDialog.js +81 -0
  295. package/dist/ui/documents/PdfViewerDialog.js.map +1 -0
  296. package/dist/ui/filters/FilterDrawer.js +211 -0
  297. package/dist/ui/filters/FilterDrawer.js.map +1 -0
  298. package/dist/ui/filters/filterDrawerFields.js +119 -0
  299. package/dist/ui/filters/filterDrawerFields.js.map +1 -0
  300. package/dist/ui/help/HelpButton.js +17 -0
  301. package/dist/ui/help/HelpButton.js.map +1 -0
  302. package/dist/ui/help/HelpCarouselDialog.js +154 -0
  303. package/dist/ui/help/HelpCarouselDialog.js.map +1 -0
  304. package/dist/ui/help/useHelpCarousel.js +25 -0
  305. package/dist/ui/help/useHelpCarousel.js.map +1 -0
  306. package/dist/ui/labels/IconBadge.js +82 -0
  307. package/dist/ui/labels/IconBadge.js.map +1 -0
  308. package/dist/ui/navigation/RouteTabs.js +75 -0
  309. package/dist/ui/navigation/RouteTabs.js.map +1 -0
  310. package/dist/ui/navigation/routeTabMatching.js +23 -0
  311. package/dist/ui/navigation/routeTabMatching.js.map +1 -0
  312. package/dist/upload/AttachmentsInput.js +127 -0
  313. package/dist/upload/AttachmentsInput.js.map +1 -0
  314. package/dist/upload/FileChip.js +27 -0
  315. package/dist/upload/FileChip.js.map +1 -0
  316. package/dist/upload/FileDropzone.js +19 -84
  317. package/dist/upload/FileDropzone.js.map +1 -1
  318. package/dist/upload/FileInput.js +112 -0
  319. package/dist/upload/FileInput.js.map +1 -0
  320. package/dist/upload/FileUploadInput.js +285 -0
  321. package/dist/upload/FileUploadInput.js.map +1 -0
  322. package/dist/upload/ImageInput.js +125 -0
  323. package/dist/upload/ImageInput.js.map +1 -0
  324. package/dist/upload/TemplateDownloadButton.js +67 -0
  325. package/dist/upload/TemplateDownloadButton.js.map +1 -0
  326. package/dist/upload/fileAppearance.js +21 -0
  327. package/dist/upload/fileAppearance.js.map +1 -0
  328. package/dist/upload/fileRules.js +27 -1
  329. package/dist/upload/fileRules.js.map +1 -1
  330. package/dist/upload/transferProgress.js +23 -0
  331. package/dist/upload/transferProgress.js.map +1 -0
  332. package/dist/upload/uploadMachine.js +8 -8
  333. package/dist/upload/uploadMachine.js.map +1 -1
  334. package/dist/upload/uploadWithProgress.js +1 -1
  335. package/dist/upload/uploadWithProgress.js.map +1 -1
  336. package/dist/upload/useFilePicker.js +101 -0
  337. package/dist/upload/useFilePicker.js.map +1 -0
  338. package/package.json +44 -1
@@ -0,0 +1,167 @@
1
+ "use client";
2
+ import { jsxs, jsx } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import Alert from "@mui/material/Alert";
5
+ import Button from "@mui/material/Button";
6
+ import Chip from "@mui/material/Chip";
7
+ import Paper from "@mui/material/Paper";
8
+ import Stack from "@mui/material/Stack";
9
+ import Typography from "@mui/material/Typography";
10
+ import TextInput from "../inputs/TextInput.js";
11
+ import { CERTIFICATE_STATUS, certificateStatusOf, readCertificate } from "./certificateReader.js";
12
+ import { verifyEfirmaKey, EfirmaKeyError } from "./efirmaKey.js";
13
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
14
+ import { useFormatters } from "../toolkit/useFormatters.js";
15
+ import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
16
+ import { warnUnknownProps } from "../toolkit/unknownProps.js";
17
+ import DefinitionList from "../ui/layout/DefinitionList.js";
18
+ import FileInput from "../upload/FileInput.js";
19
+ const THEME_NAME = "CalcitaEfirmaFields";
20
+ const EfirmaRoot = piecePart(THEME_NAME, "Root", Stack);
21
+ const EfirmaCertificate = piecePart(THEME_NAME, "Certificate", Paper);
22
+ const EfirmaVerify = piecePart(THEME_NAME, "Verify", Stack);
23
+ const STATUS_COLORS = Object.freeze({
24
+ [CERTIFICATE_STATUS.valid]: "success",
25
+ [CERTIFICATE_STATUS.expired]: "error",
26
+ [CERTIFICATE_STATUS.notYetValid]: "warning"
27
+ });
28
+ const VERIFY_STATE = Object.freeze({ idle: "idle", verifying: "verifying", verified: "verified", failed: "failed" });
29
+ const EMPTY_VALUE = Object.freeze({ certificateFile: null, keyFile: null, password: "" });
30
+ function EfirmaFields(props) {
31
+ const {
32
+ value = EMPTY_VALUE,
33
+ onChange,
34
+ expectedRfc,
35
+ onCertificateRead,
36
+ onVerifyKey,
37
+ verifyLabel,
38
+ disabled = false,
39
+ sx,
40
+ texts,
41
+ ...unknownProps
42
+ } = usePieceProps(props, THEME_NAME);
43
+ warnUnknownProps(THEME_NAME, unknownProps);
44
+ const { t } = useToolkitTranslation("mexico", texts);
45
+ const { formatDate } = useFormatters();
46
+ const [certificate, setCertificate] = useState(null);
47
+ const [readError, setReadError] = useState(null);
48
+ const [verify, setVerify] = useState({ state: VERIFY_STATE.idle, message: null });
49
+ const change = (changes) => {
50
+ setVerify({ state: VERIFY_STATE.idle, message: null });
51
+ onChange({ ...value, ...changes });
52
+ };
53
+ const chooseCertificate = async (certificateFile) => {
54
+ change({ certificateFile });
55
+ setReadError(null);
56
+ if (!certificateFile) {
57
+ setCertificate(null);
58
+ onCertificateRead?.(null);
59
+ return;
60
+ }
61
+ try {
62
+ const read = readCertificate(new Uint8Array(await certificateFile.arrayBuffer()));
63
+ setCertificate(read);
64
+ onCertificateRead?.(read);
65
+ } catch {
66
+ setCertificate(null);
67
+ setReadError(t("efirma.notCertificate"));
68
+ onCertificateRead?.(null);
69
+ }
70
+ };
71
+ const verifyInBrowser = async ({ certificateFile, keyFile, password }) => {
72
+ try {
73
+ await verifyEfirmaKey({ certificateBytes: await certificateFile.arrayBuffer(), keyBytes: await keyFile.arrayBuffer(), password });
74
+ } catch (error) {
75
+ throw new Error(error instanceof EfirmaKeyError ? t(`efirma.problems.${error.reason}`) : t("efirma.verifyFailed"));
76
+ }
77
+ };
78
+ const verifyKey = async () => {
79
+ setVerify({ state: VERIFY_STATE.verifying, message: null });
80
+ try {
81
+ await (onVerifyKey ?? verifyInBrowser)({
82
+ certificateFile: value.certificateFile,
83
+ keyFile: value.keyFile,
84
+ password: value.password,
85
+ certificate
86
+ });
87
+ setVerify({ state: VERIFY_STATE.verified, message: null });
88
+ } catch (error) {
89
+ setVerify({ state: VERIFY_STATE.failed, message: error?.message ?? t("efirma.verifyFailed") });
90
+ }
91
+ };
92
+ const status = certificate ? certificateStatusOf(certificate) : null;
93
+ const isOtherRfc = Boolean(certificate && expectedRfc) && certificate.rfc?.toUpperCase() !== expectedRfc.toUpperCase();
94
+ const canVerify = Boolean(certificate && value.keyFile && value.password) && status === CERTIFICATE_STATUS.valid && !disabled;
95
+ return /* @__PURE__ */ jsxs(EfirmaRoot, { ownSx: { gap: 1 }, sx, children: [
96
+ /* @__PURE__ */ jsx(
97
+ FileInput,
98
+ {
99
+ label: t("efirma.certificate"),
100
+ value: value.certificateFile,
101
+ onChange: chooseCertificate,
102
+ accept: ".cer",
103
+ disabled,
104
+ error: readError ?? void 0,
105
+ helperText: t("efirma.certificateHelp"),
106
+ texts
107
+ }
108
+ ),
109
+ /* @__PURE__ */ jsx(
110
+ FileInput,
111
+ {
112
+ label: t("efirma.privateKey"),
113
+ value: value.keyFile,
114
+ onChange: (keyFile) => change({ keyFile }),
115
+ accept: ".key",
116
+ disabled,
117
+ helperText: t("efirma.privateKeyHelp"),
118
+ texts
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx(
122
+ TextInput,
123
+ {
124
+ label: t("efirma.password"),
125
+ type: "password",
126
+ value: value.password,
127
+ onChange: (password) => change({ password }),
128
+ autoComplete: "off",
129
+ disabled,
130
+ helperText: t("efirma.passwordHelp")
131
+ }
132
+ ),
133
+ certificate && /* @__PURE__ */ jsxs(EfirmaCertificate, { variant: "outlined", ownSx: { p: 2, borderRadius: 2 }, children: [
134
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", sx: { alignItems: "center", gap: 1, mb: 1.5, flexWrap: "wrap" }, children: [
135
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", component: "p", sx: { mr: "auto" }, children: certificate.name ?? certificate.organization ?? "—" }),
136
+ /* @__PURE__ */ jsx(Chip, { size: "small", color: STATUS_COLORS[status], label: t(`efirma.status.${status}`) })
137
+ ] }),
138
+ isOtherRfc && /* @__PURE__ */ jsx(Alert, { severity: "warning", sx: { mb: 1.5 }, children: t("efirma.otherRfc", { rfc: certificate.rfc, expected: expectedRfc }) }),
139
+ /* @__PURE__ */ jsx(DefinitionList, { entries: [
140
+ { label: t("efirma.rfc"), value: certificate.rfc ?? "—" },
141
+ ...certificate.representativeRfc ? [{ label: t("efirma.representative"), value: [certificate.representativeRfc, certificate.representativeCurp].filter(Boolean).join(" · ") }] : [],
142
+ ...certificate.curp ? [{ label: t("efirma.curp"), value: certificate.curp }] : [],
143
+ { label: t("efirma.certificateNumber"), value: certificate.certificateNumber },
144
+ { label: t("efirma.validity"), value: t("efirma.validityRange", { from: formatDate(certificate.validFrom), to: formatDate(certificate.validTo) }) },
145
+ ...certificate.branch ? [{ label: t("efirma.branch"), value: certificate.branch }] : []
146
+ ] })
147
+ ] }),
148
+ /* @__PURE__ */ jsxs(EfirmaVerify, { direction: "row", ownSx: { gap: 1.5, alignItems: "center", flexWrap: "wrap", mt: 1 }, children: [
149
+ /* @__PURE__ */ jsx(
150
+ Button,
151
+ {
152
+ variant: "outlined",
153
+ onClick: verifyKey,
154
+ disabled: !canVerify || verify.state === VERIFY_STATE.verifying,
155
+ loading: verify.state === VERIFY_STATE.verifying,
156
+ children: verifyLabel ?? t("efirma.verify")
157
+ }
158
+ ),
159
+ verify.state === VERIFY_STATE.verified && /* @__PURE__ */ jsx(Alert, { severity: "success", sx: { py: 0 }, children: t("efirma.verified") }),
160
+ verify.state === VERIFY_STATE.failed && /* @__PURE__ */ jsx(Alert, { severity: "error", role: "alert", sx: { py: 0 }, children: verify.message })
161
+ ] })
162
+ ] });
163
+ }
164
+ export {
165
+ EfirmaFields as default
166
+ };
167
+ //# sourceMappingURL=EfirmaFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EfirmaFields.js","sources":["../../src/mexico/EfirmaFields.jsx"],"sourcesContent":["// La e.firma (o el sello digital) de una persona o una empresa: el certificado (.cer), la llave privada (.key) y su\n// contraseña, con lo que dice el certificado a la vista (RFC, nombre, número de certificado, vigencia), leído en el navegador\n// sin subirlo.\n//\n// Avisa sin esperar al servidor: un .cer que no es certificado, uno vencido o que todavía no vale, y un RFC distinto al que se\n// espera (expectedRfc: el de la empresa).\n//\n// «Revisar la e.firma» abre la llave con su contraseña y comprueba que es la de ese certificado, en el navegador\n// (verifyEfirmaKey, con jsrsasign: la llave del SAT viene cifrada con 3DES, que el navegador no trae), y dice el motivo de un\n// «no»: la contraseña, otra llave o un archivo que no es llave. Con onVerifyKey la revisión es del proyecto (su API con\n// OpenSSL, por ejemplo). Calcita no manda la llave ni la contraseña a ningún lado por su cuenta.\n//\n// Controlado: value {certificateFile, keyFile, password} y onChange con el valor completo.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaEfirmaFields con defaultProps y\n// styleOverrides de sus partes: root, certificate, verify.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport Chip from '@mui/material/Chip';\nimport Paper from '@mui/material/Paper';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport TextInput from '@/inputs/TextInput.jsx';\nimport {CERTIFICATE_STATUS, certificateStatusOf, readCertificate} from '@/mexico/certificateReader.js';\nimport {EfirmaKeyError, verifyEfirmaKey} from '@/mexico/efirmaKey.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport DefinitionList from '@/ui/layout/DefinitionList.jsx';\nimport FileInput from '@/upload/FileInput.jsx';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaEfirmaFields)\nconst THEME_NAME = 'CalcitaEfirmaFields';\nconst EfirmaRoot = piecePart(THEME_NAME, 'Root', Stack);\nconst EfirmaCertificate = piecePart(THEME_NAME, 'Certificate', Paper);\nconst EfirmaVerify = piecePart(THEME_NAME, 'Verify', Stack);\n\n// el color del chip de la vigencia\nconst STATUS_COLORS = Object.freeze({\n [CERTIFICATE_STATUS.valid]: 'success', [CERTIFICATE_STATUS.expired]: 'error', [CERTIFICATE_STATUS.notYetValid]: 'warning',\n});\n// en qué va la revisión de la llave\nconst VERIFY_STATE = Object.freeze({idle: 'idle', verifying: 'verifying', verified: 'verified', failed: 'failed'});\nconst EMPTY_VALUE = Object.freeze({certificateFile: null, keyFile: null, password: ''});\n\n/**\n * @param {object} props\n * @param {{certificateFile: File|null, keyFile: File|null, password: string}} [props.value]\n * @param {(value: {certificateFile: File|null, keyFile: File|null, password: string}) => void} props.onChange el valor completo\n * @param {string} [props.expectedRfc] el RFC que debe traer el certificado (el de la empresa); si es otro, se avisa\n * @param {(certificate: ReturnType<typeof import('@/mexico/certificateReader.js').readCertificate>|null) => void} [props.onCertificateRead] lo que dice el certificado al elegirlo (null al quitarlo\n * o si no se pudo leer)\n * @param {(input: {certificateFile: File, keyFile: File, password: string, certificate: ReturnType<typeof import('@/mexico/certificateReader.js').readCertificate>}) => Promise<void>} [props.onVerifyKey]\n * la revisión del proyecto (su API); si truena, su mensaje se enseña. Sin él, se abre en el navegador con jsrsasign\n * @param {string} [props.verifyLabel] ya traducido; por omisión «Revisar la e.firma»\n * @param {boolean} [props.disabled]\n * @param {object} [props.sx]\n * @param {object} [props.texts] los textos que cambian, con la forma de `mexico` en los textos del paquete\n */\nexport default function EfirmaFields(props) {\n const {\n value = EMPTY_VALUE, onChange, expectedRfc, onCertificateRead, onVerifyKey, verifyLabel, disabled = false, sx, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('mexico', texts);\n const {formatDate} = useFormatters();\n const [certificate, setCertificate] = useState(null);\n const [readError, setReadError] = useState(null);\n const [verify, setVerify] = useState({state: VERIFY_STATE.idle, message: null});\n\n // cualquier cambio deja la revisión de la llave sin hacer: ya no es la misma combinación\n const change = (changes) => {\n setVerify({state: VERIFY_STATE.idle, message: null});\n onChange({...value, ...changes});\n };\n\n const chooseCertificate = async (certificateFile) => {\n change({certificateFile});\n setReadError(null);\n if (!certificateFile) {\n setCertificate(null);\n onCertificateRead?.(null);\n return;\n }\n try {\n const read = readCertificate(new Uint8Array(await certificateFile.arrayBuffer()));\n setCertificate(read);\n onCertificateRead?.(read);\n } catch {\n setCertificate(null);\n setReadError(t('efirma.notCertificate'));\n onCertificateRead?.(null);\n }\n };\n\n // sin revisión del proyecto, la del navegador, con el motivo de un «no» en palabras de la persona\n const verifyInBrowser = async ({certificateFile, keyFile, password}) => {\n try {\n await verifyEfirmaKey({certificateBytes: await certificateFile.arrayBuffer(), keyBytes: await keyFile.arrayBuffer(), password});\n } catch (error) {\n throw new Error(error instanceof EfirmaKeyError ? t(`efirma.problems.${error.reason}`) : t('efirma.verifyFailed'));\n }\n };\n\n const verifyKey = async () => {\n setVerify({state: VERIFY_STATE.verifying, message: null});\n try {\n await (onVerifyKey ?? verifyInBrowser)({\n certificateFile: value.certificateFile, keyFile: value.keyFile, password: value.password, certificate,\n });\n setVerify({state: VERIFY_STATE.verified, message: null});\n } catch (error) {\n setVerify({state: VERIFY_STATE.failed, message: error?.message ?? t('efirma.verifyFailed')});\n }\n };\n\n const status = certificate ? certificateStatusOf(certificate) : null;\n const isOtherRfc = Boolean(certificate && expectedRfc) && certificate.rfc?.toUpperCase() !== expectedRfc.toUpperCase();\n const canVerify = Boolean(certificate && value.keyFile && value.password) && status === CERTIFICATE_STATUS.valid && !disabled;\n\n return (\n <EfirmaRoot ownSx={{gap: 1}} sx={sx}>\n <FileInput label={t('efirma.certificate')} value={value.certificateFile} onChange={chooseCertificate} accept=\".cer\" disabled={disabled}\n error={readError ?? undefined} helperText={t('efirma.certificateHelp')} texts={texts} />\n <FileInput label={t('efirma.privateKey')} value={value.keyFile} onChange={(keyFile) => change({keyFile})} accept=\".key\" disabled={disabled}\n helperText={t('efirma.privateKeyHelp')} texts={texts} />\n <TextInput label={t('efirma.password')} type=\"password\" value={value.password} onChange={(password) => change({password})}\n autoComplete=\"off\" disabled={disabled} helperText={t('efirma.passwordHelp')} />\n\n {certificate && (\n <EfirmaCertificate variant=\"outlined\" ownSx={{p: 2, borderRadius: 2}}>\n <Stack direction=\"row\" sx={{alignItems: 'center', gap: 1, mb: 1.5, flexWrap: 'wrap'}}>\n <Typography variant=\"subtitle1\" component=\"p\" sx={{mr: 'auto'}}>{certificate.name ?? certificate.organization ?? '—'}</Typography>\n <Chip size=\"small\" color={STATUS_COLORS[status]} label={t(`efirma.status.${status}`)} />\n </Stack>\n {isOtherRfc && <Alert severity=\"warning\" sx={{mb: 1.5}}>{t('efirma.otherRfc', {rfc: certificate.rfc, expected: expectedRfc})}</Alert>}\n <DefinitionList entries={[\n {label: t('efirma.rfc'), value: certificate.rfc ?? '—'},\n ...(certificate.representativeRfc ? [{label: t('efirma.representative'), value: [certificate.representativeRfc, certificate.representativeCurp].filter(Boolean).join(' · ')}] : []),\n ...(certificate.curp ? [{label: t('efirma.curp'), value: certificate.curp}] : []),\n {label: t('efirma.certificateNumber'), value: certificate.certificateNumber},\n {label: t('efirma.validity'), value: t('efirma.validityRange', {from: formatDate(certificate.validFrom), to: formatDate(certificate.validTo)})},\n ...(certificate.branch ? [{label: t('efirma.branch'), value: certificate.branch}] : []),\n ]} />\n </EfirmaCertificate>\n )}\n\n <EfirmaVerify direction=\"row\" ownSx={{gap: 1.5, alignItems: 'center', flexWrap: 'wrap', mt: 1}}>\n <Button variant=\"outlined\" onClick={verifyKey} disabled={!canVerify || verify.state === VERIFY_STATE.verifying}\n loading={verify.state === VERIFY_STATE.verifying}>\n {verifyLabel ?? t('efirma.verify')}\n </Button>\n {verify.state === VERIFY_STATE.verified && <Alert severity=\"success\" sx={{py: 0}}>{t('efirma.verified')}</Alert>}\n {verify.state === VERIFY_STATE.failed && <Alert severity=\"error\" role=\"alert\" sx={{py: 0}}>{verify.message}</Alert>}\n </EfirmaVerify>\n </EfirmaRoot>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import { readDerNode, DER_TAG, derTimeOf, objectIdentifierOf, derTextOf } from "./derReader.js";
2
+ const NAME_OIDS = Object.freeze({
3
+ "2.5.4.3": "commonName",
4
+ "2.5.4.10": "organizationName",
5
+ "2.5.4.11": "organizationalUnitName",
6
+ "2.5.4.45": "uniqueIdentifier",
7
+ "2.5.4.5": "serialNumber",
8
+ "1.2.840.113549.1.9.1": "email"
9
+ });
10
+ const IDENTIFIER_SEPARATOR = "/";
11
+ const EXPLICIT_VERSION_TAG = 160;
12
+ const ASCII_DIGIT_ZERO = 48;
13
+ const ASCII_DIGIT_NINE = 57;
14
+ function namesOf(nameNode) {
15
+ const names = {};
16
+ for (const relativeName of nameNode.children ?? []) {
17
+ for (const attribute of relativeName.children ?? []) {
18
+ const [oidNode, valueNode] = attribute.children ?? [];
19
+ const key = NAME_OIDS[objectIdentifierOf(oidNode.content)];
20
+ if (key && valueNode) {
21
+ names[key] = derTextOf(valueNode).trim();
22
+ }
23
+ }
24
+ }
25
+ return names;
26
+ }
27
+ function certificateNumberOf(serialBytes) {
28
+ const isDigitText = serialBytes.length > 0 && serialBytes.every((byte) => byte >= ASCII_DIGIT_ZERO && byte <= ASCII_DIGIT_NINE);
29
+ if (isDigitText) {
30
+ return new TextDecoder("utf-8").decode(serialBytes);
31
+ }
32
+ return [...serialBytes].map((byte) => byte.toString(16).padStart(2, "0")).join("").toUpperCase();
33
+ }
34
+ function identifierPartsOf(text) {
35
+ const [own = "", representative = ""] = (text ?? "").split(IDENTIFIER_SEPARATOR).map((part) => part.trim());
36
+ return { own: own || null, representative: representative || null };
37
+ }
38
+ function readCertificate(certificateBytes) {
39
+ const bytes = certificateBytes instanceof Uint8Array ? certificateBytes : new Uint8Array(certificateBytes);
40
+ const certificate = readDerNode(bytes);
41
+ const toBeSigned = certificate.children?.[0];
42
+ if (certificate.tag !== DER_TAG.sequence || toBeSigned?.tag !== DER_TAG.sequence) {
43
+ throw new Error("El archivo no es un certificado");
44
+ }
45
+ const parts = toBeSigned.children;
46
+ const firstIndex = parts[0].tag === EXPLICIT_VERSION_TAG ? 1 : 0;
47
+ const [serialNode, , issuerNode, validityNode, subjectNode] = parts.slice(firstIndex);
48
+ const subject = namesOf(subjectNode);
49
+ const issuer = namesOf(issuerNode);
50
+ const rfcParts = identifierPartsOf(subject.uniqueIdentifier);
51
+ const curpParts = identifierPartsOf(subject.serialNumber);
52
+ return {
53
+ rfc: rfcParts.own,
54
+ representativeRfc: rfcParts.representative,
55
+ curp: curpParts.own,
56
+ representativeCurp: curpParts.representative,
57
+ name: subject.commonName ?? null,
58
+ organization: subject.organizationName ?? null,
59
+ branch: subject.organizationalUnitName ?? null,
60
+ email: subject.email ?? null,
61
+ certificateNumber: certificateNumberOf(serialNode.content),
62
+ validFrom: derTimeOf(validityNode.children[0]),
63
+ validTo: derTimeOf(validityNode.children[1]),
64
+ issuer: issuer.organizationName ?? issuer.commonName ?? null
65
+ };
66
+ }
67
+ function certificateStatusOf({ validFrom, validTo }, now = Date.now()) {
68
+ if (now < Date.parse(validFrom)) {
69
+ return CERTIFICATE_STATUS.notYetValid;
70
+ }
71
+ return now > Date.parse(validTo) ? CERTIFICATE_STATUS.expired : CERTIFICATE_STATUS.valid;
72
+ }
73
+ const CERTIFICATE_STATUS = Object.freeze({ notYetValid: "notYetValid", valid: "valid", expired: "expired" });
74
+ export {
75
+ CERTIFICATE_STATUS,
76
+ certificateStatusOf,
77
+ readCertificate
78
+ };
79
+ //# sourceMappingURL=certificateReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"certificateReader.js","sources":["../../src/mexico/certificateReader.js"],"sourcesContent":["// Lo que dice el certificado (.cer) de una e.firma o de un sello digital del SAT, leído en el navegador sin subirlo: el RFC,\n// la CURP, el nombre, el número de certificado y desde cuándo y hasta cuándo vale.\n//\n// Dónde lo pone el SAT en el certificado:\n// RFC x500UniqueIdentifier (2.5.4.45): «RFC» o, en persona moral, «RFC de la empresa / RFC del representante»\n// CURP serialNumber (2.5.4.5): «CURP» o « / CURP del representante» (la empresa no tiene)\n// nombre commonName (2.5.4.3); la razón social también en organizationName (2.5.4.10)\n// número el número de serie del certificado, que el SAT escribe con 20 dígitos en texto (30001000000400002434)\n// sucursal organizationalUnitName (2.5.4.11): lo traen los sellos digitales (CSD), no la e.firma\n//\n// No revisa la firma del SAT ni descifra la llave (.key): eso es del servidor o de onVerifyKey en EfirmaFields.\n//\n// Puro (sin React): se prueba con node (certificateReader.test.js).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {DER_TAG, derTextOf, derTimeOf, objectIdentifierOf, readDerNode} from '@/mexico/derReader.js';\n\n// los nombres que se leen del certificado\nconst NAME_OIDS = Object.freeze({\n '2.5.4.3': 'commonName',\n '2.5.4.10': 'organizationName',\n '2.5.4.11': 'organizationalUnitName',\n '2.5.4.45': 'uniqueIdentifier',\n '2.5.4.5': 'serialNumber',\n '1.2.840.113549.1.9.1': 'email',\n});\nconst IDENTIFIER_SEPARATOR = '/';\n// el [0] explícito de la versión: si está, el número de serie es la segunda pieza\nconst EXPLICIT_VERSION_TAG = 0xa0;\nconst ASCII_DIGIT_ZERO = 0x30;\nconst ASCII_DIGIT_NINE = 0x39;\n\n/**\n * Los nombres de un «Name» de X.509 (una lista de conjuntos de OID y valor), por su nombre de NAME_OIDS.\n *\n * @param {object} nameNode\n * @returns {Record<string, string>}\n */\nfunction namesOf(nameNode) {\n const names = {};\n for (const relativeName of nameNode.children ?? []) {\n for (const attribute of relativeName.children ?? []) {\n const [oidNode, valueNode] = attribute.children ?? [];\n const key = NAME_OIDS[objectIdentifierOf(oidNode.content)];\n if (key && valueNode) {\n names[key] = derTextOf(valueNode).trim();\n }\n }\n }\n return names;\n}\n\n/**\n * El número de certificado como lo escribe el SAT: sus bytes son dígitos en texto. Si no lo son, en hexadecimal.\n *\n * @param {Uint8Array} serialBytes\n * @returns {string}\n */\nfunction certificateNumberOf(serialBytes) {\n const isDigitText = serialBytes.length > 0 && serialBytes.every((byte) => byte >= ASCII_DIGIT_ZERO && byte <= ASCII_DIGIT_NINE);\n if (isDigitText) {\n return new TextDecoder('utf-8').decode(serialBytes);\n }\n return [...serialBytes].map((byte) => byte.toString(16).padStart(2, '0')).join('').toUpperCase();\n}\n\n/**\n * Las partes «propia / del representante» de un campo del SAT.\n *\n * @param {string|undefined} text\n * @returns {{own: string|null, representative: string|null}}\n */\nfunction identifierPartsOf(text) {\n const [own = '', representative = ''] = (text ?? '').split(IDENTIFIER_SEPARATOR).map((part) => part.trim());\n return {own: own || null, representative: representative || null};\n}\n\n/**\n * Lee un certificado .cer (DER).\n *\n * @param {ArrayBuffer|Uint8Array} certificateBytes\n * @returns {{rfc: string|null, representativeRfc: string|null, curp: string|null, representativeCurp: string|null, name: string|null, organization: string|null, branch: string|null, email: string|null, certificateNumber: string, validFrom: string, validTo: string, issuer: string|null}}\n */\nexport function readCertificate(certificateBytes) {\n const bytes = certificateBytes instanceof Uint8Array ? certificateBytes : new Uint8Array(certificateBytes);\n const certificate = readDerNode(bytes);\n const toBeSigned = certificate.children?.[0];\n if (certificate.tag !== DER_TAG.sequence || toBeSigned?.tag !== DER_TAG.sequence) {\n throw new Error('El archivo no es un certificado');\n }\n const parts = toBeSigned.children;\n const firstIndex = parts[0].tag === EXPLICIT_VERSION_TAG ? 1 : 0;\n // número de serie, algoritmo, emisor, vigencia y titular, en ese orden\n const [serialNode, , issuerNode, validityNode, subjectNode] = parts.slice(firstIndex);\n const subject = namesOf(subjectNode);\n const issuer = namesOf(issuerNode);\n const rfcParts = identifierPartsOf(subject.uniqueIdentifier);\n const curpParts = identifierPartsOf(subject.serialNumber);\n\n return {\n rfc: rfcParts.own,\n representativeRfc: rfcParts.representative,\n curp: curpParts.own,\n representativeCurp: curpParts.representative,\n name: subject.commonName ?? null,\n organization: subject.organizationName ?? null,\n branch: subject.organizationalUnitName ?? null,\n email: subject.email ?? null,\n certificateNumber: certificateNumberOf(serialNode.content),\n validFrom: derTimeOf(validityNode.children[0]),\n validTo: derTimeOf(validityNode.children[1]),\n issuer: issuer.organizationName ?? issuer.commonName ?? null,\n };\n}\n\n/**\n * ¿En qué está el certificado en este momento?\n *\n * @param {{validFrom: string, validTo: string}} certificate\n * @param {number} [now] milisegundos (Date.now())\n * @returns {'notYetValid'|'valid'|'expired'}\n */\nexport function certificateStatusOf({validFrom, validTo}, now = Date.now()) {\n if (now < Date.parse(validFrom)) {\n return CERTIFICATE_STATUS.notYetValid;\n }\n return now > Date.parse(validTo) ? CERTIFICATE_STATUS.expired : CERTIFICATE_STATUS.valid;\n}\n\n/** En qué está un certificado: aún no vale, vale o ya venció. */\nexport const CERTIFICATE_STATUS = Object.freeze({notYetValid: 'notYetValid', valid: 'valid', expired: 'expired'});\n"],"names":[],"mappings":";AAkBA,MAAM,YAAY,OAAO,OAAO;AAAA,EAC5B,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,wBAAwB;AAC5B,CAAC;AACD,MAAM,uBAAuB;AAE7B,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AAQzB,SAAS,QAAQ,UAAU;AACvB,QAAM,QAAQ,CAAA;AACd,aAAW,gBAAgB,SAAS,YAAY,CAAA,GAAI;AAChD,eAAW,aAAa,aAAa,YAAY,CAAA,GAAI;AACjD,YAAM,CAAC,SAAS,SAAS,IAAI,UAAU,YAAY,CAAA;AACnD,YAAM,MAAM,UAAU,mBAAmB,QAAQ,OAAO,CAAC;AACzD,UAAI,OAAO,WAAW;AAClB,cAAM,GAAG,IAAI,UAAU,SAAS,EAAE,KAAI;AAAA,MAC1C;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AACX;AAQA,SAAS,oBAAoB,aAAa;AACtC,QAAM,cAAc,YAAY,SAAS,KAAK,YAAY,MAAM,CAAC,SAAS,QAAQ,oBAAoB,QAAQ,gBAAgB;AAC9H,MAAI,aAAa;AACb,WAAO,IAAI,YAAY,OAAO,EAAE,OAAO,WAAW;AAAA,EACtD;AACA,SAAO,CAAC,GAAG,WAAW,EAAE,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,YAAW;AAClG;AAQA,SAAS,kBAAkB,MAAM;AAC7B,QAAM,CAAC,MAAM,IAAI,iBAAiB,EAAE,KAAK,QAAQ,IAAI,MAAM,oBAAoB,EAAE,IAAI,CAAC,SAAS,KAAK,MAAM;AAC1G,SAAO,EAAC,KAAK,OAAO,MAAM,gBAAgB,kBAAkB,KAAI;AACpE;AAQO,SAAS,gBAAgB,kBAAkB;AAC9C,QAAM,QAAQ,4BAA4B,aAAa,mBAAmB,IAAI,WAAW,gBAAgB;AACzG,QAAM,cAAc,YAAY,KAAK;AACrC,QAAM,aAAa,YAAY,WAAW,CAAC;AAC3C,MAAI,YAAY,QAAQ,QAAQ,YAAY,YAAY,QAAQ,QAAQ,UAAU;AAC9E,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACrD;AACA,QAAM,QAAQ,WAAW;AACzB,QAAM,aAAa,MAAM,CAAC,EAAE,QAAQ,uBAAuB,IAAI;AAE/D,QAAM,CAAC,YAAU,EAAI,YAAY,cAAc,WAAW,IAAI,MAAM,MAAM,UAAU;AACpF,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,WAAW,kBAAkB,QAAQ,gBAAgB;AAC3D,QAAM,YAAY,kBAAkB,QAAQ,YAAY;AAExD,SAAO;AAAA,IACH,KAAK,SAAS;AAAA,IACd,mBAAmB,SAAS;AAAA,IAC5B,MAAM,UAAU;AAAA,IAChB,oBAAoB,UAAU;AAAA,IAC9B,MAAM,QAAQ,cAAc;AAAA,IAC5B,cAAc,QAAQ,oBAAoB;AAAA,IAC1C,QAAQ,QAAQ,0BAA0B;AAAA,IAC1C,OAAO,QAAQ,SAAS;AAAA,IACxB,mBAAmB,oBAAoB,WAAW,OAAO;AAAA,IACzD,WAAW,UAAU,aAAa,SAAS,CAAC,CAAC;AAAA,IAC7C,SAAS,UAAU,aAAa,SAAS,CAAC,CAAC;AAAA,IAC3C,QAAQ,OAAO,oBAAoB,OAAO,cAAc;AAAA,EAChE;AACA;AASO,SAAS,oBAAoB,EAAC,WAAW,QAAO,GAAG,MAAM,KAAK,OAAO;AACxE,MAAI,MAAM,KAAK,MAAM,SAAS,GAAG;AAC7B,WAAO,mBAAmB;AAAA,EAC9B;AACA,SAAO,MAAM,KAAK,MAAM,OAAO,IAAI,mBAAmB,UAAU,mBAAmB;AACvF;AAGY,MAAC,qBAAqB,OAAO,OAAO,EAAC,aAAa,eAAe,OAAO,SAAS,SAAS,UAAS,CAAC;"}
@@ -0,0 +1,81 @@
1
+ const CFDI_VOUCHER_TYPE = Object.freeze({ income: "I", expense: "E", transfer: "T", payroll: "N", payment: "P" });
2
+ const TAG_PATTERN = /<(?:[A-Za-z_][\w.-]*:)?([A-Za-z_][\w.-]*)((?:\s+[\w:.-]+\s*=\s*(?:"[^"]*"|'[^']*'))*)\s*\/?>/g;
3
+ const ATTRIBUTE_PATTERN = /([\w:.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
4
+ const XML_ENTITIES = Object.freeze({ amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" });
5
+ const HEXADECIMAL_RADIX = 16;
6
+ const DECIMAL_RADIX = 10;
7
+ function decodeXmlText(value) {
8
+ return value.replace(/&(#x[\da-f]+|#\d+|\w+);/gi, (entity, code) => {
9
+ if (code[0] === "#") {
10
+ const isHexadecimal = code[1].toLowerCase() === "x";
11
+ return String.fromCodePoint(parseInt(code.slice(isHexadecimal ? 2 : 1), isHexadecimal ? HEXADECIMAL_RADIX : DECIMAL_RADIX));
12
+ }
13
+ return XML_ENTITIES[code] ?? entity;
14
+ });
15
+ }
16
+ function elementsOf(xmlText) {
17
+ const elements = [];
18
+ for (const [, name, attributesText] of xmlText.matchAll(TAG_PATTERN)) {
19
+ const attributes = {};
20
+ for (const [, attributeName, doubleQuoted, singleQuoted] of attributesText.matchAll(ATTRIBUTE_PATTERN)) {
21
+ attributes[attributeName] = decodeXmlText(doubleQuoted ?? singleQuoted);
22
+ }
23
+ elements.push({ name, attributes });
24
+ }
25
+ return elements;
26
+ }
27
+ function readCfdi(xmlText) {
28
+ const elements = elementsOf(xmlText ?? "");
29
+ const first = (name) => elements.find((element) => element.name === name)?.attributes;
30
+ const voucher = first("Comprobante");
31
+ const issuer = first("Emisor");
32
+ const receiver = first("Receptor");
33
+ if (!voucher || !issuer || !receiver) {
34
+ return null;
35
+ }
36
+ const totals = elements.find((element) => element.name === "Impuestos" && ("TotalImpuestosTrasladados" in element.attributes || "TotalImpuestosRetenidos" in element.attributes))?.attributes ?? {};
37
+ const stamp = first("TimbreFiscalDigital");
38
+ return {
39
+ version: voucher.Version ?? voucher.version ?? null,
40
+ series: voucher.Serie ?? null,
41
+ folio: voucher.Folio ?? null,
42
+ issuedAt: voucher.Fecha ?? null,
43
+ voucherType: voucher.TipoDeComprobante ?? null,
44
+ paymentMethod: voucher.MetodoPago ?? null,
45
+ paymentForm: voucher.FormaPago ?? null,
46
+ currency: voucher.Moneda ?? null,
47
+ exchangeRate: voucher.TipoCambio ?? null,
48
+ subtotal: voucher.SubTotal ?? null,
49
+ discount: voucher.Descuento ?? null,
50
+ total: voucher.Total ?? null,
51
+ placeOfIssue: voucher.LugarExpedicion ?? null,
52
+ issuer: { rfc: issuer.Rfc ?? null, name: issuer.Nombre ?? null, taxRegime: issuer.RegimenFiscal ?? null },
53
+ receiver: {
54
+ rfc: receiver.Rfc ?? null,
55
+ name: receiver.Nombre ?? null,
56
+ cfdiUse: receiver.UsoCFDI ?? null,
57
+ taxRegime: receiver.RegimenFiscalReceptor ?? null,
58
+ postalCode: receiver.DomicilioFiscalReceptor ?? null
59
+ },
60
+ concepts: elements.filter((element) => element.name === "Concepto").map(({ attributes }) => ({
61
+ productCode: attributes.ClaveProdServ ?? null,
62
+ quantity: attributes.Cantidad ?? null,
63
+ unitCode: attributes.ClaveUnidad ?? null,
64
+ description: attributes.Descripcion ?? null,
65
+ unitValue: attributes.ValorUnitario ?? null,
66
+ amount: attributes.Importe ?? null
67
+ })),
68
+ taxes: { transferred: totals.TotalImpuestosTrasladados ?? null, withheld: totals.TotalImpuestosRetenidos ?? null },
69
+ // sin timbre todavía no es factura ante el SAT: no tiene folio fiscal
70
+ stamp: stamp ? { uuid: stamp.UUID?.toUpperCase() ?? null, stampedAt: stamp.FechaTimbrado ?? null, certifierRfc: stamp.RfcProvCertif ?? null } : null
71
+ };
72
+ }
73
+ async function readCfdiFile(file) {
74
+ return readCfdi(await file.text());
75
+ }
76
+ export {
77
+ CFDI_VOUCHER_TYPE,
78
+ readCfdi,
79
+ readCfdiFile
80
+ };
81
+ //# sourceMappingURL=cfdiReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cfdiReader.js","sources":["../../src/mexico/cfdiReader.js"],"sourcesContent":["// Lo que dice una factura electrónica (el XML del CFDI 3.3 o 4.0), leído en el navegador sin subirla: quién la emitió, a\n// quién, cuánto, de qué tipo, sus conceptos y su folio fiscal (UUID) si ya está timbrada. Sirve para revisar antes de cargar\n// (el RFC del receptor es el de la empresa, no se cargó dos veces) y enseñar el resumen.\n//\n// Los datos del CFDI viven en los ATRIBUTOS de sus elementos (Comprobante, Emisor, Receptor, Concepto, TimbreFiscalDigital),\n// así que basta leer las etiquetas y sus atributos: sin DOMParser (no existe en Node, donde corren las pruebas) ni librerías.\n// El prefijo del espacio de nombres (cfdi:, tfd:) no importa: se busca por el nombre del elemento.\n//\n// No revisa el sello ni pregunta al SAT si está vigente: eso es del servidor.\n//\n// Los montos se quedan en TEXTO tal como vienen ('1160.00'): así los lee Money sin redondeos de float.\n//\n// Puro (sin React): se prueba con node (cfdiReader.test.js).\n//\n// Genérico del paquete: function (regla de la casa).\n\n/**\n * Una factura leída: los montos en texto, como vienen en el XML.\n *\n * @typedef {object} Cfdi\n * @property {string|null} version\n * @property {string|null} series\n * @property {string|null} folio\n * @property {string|null} issuedAt\n * @property {string|null} voucherType CFDI_VOUCHER_TYPE\n * @property {string|null} paymentMethod\n * @property {string|null} paymentForm\n * @property {string|null} currency\n * @property {string|null} exchangeRate\n * @property {string|null} subtotal\n * @property {string|null} discount\n * @property {string|null} total\n * @property {string|null} placeOfIssue\n * @property {{rfc: string|null, name: string|null, taxRegime: string|null}} issuer\n * @property {{rfc: string|null, name: string|null, cfdiUse: string|null, taxRegime: string|null, postalCode: string|null}} receiver\n * @property {Array<{productCode: string|null, quantity: string|null, unitCode: string|null, description: string|null, unitValue: string|null, amount: string|null}>} concepts\n * @property {{transferred: string|null, withheld: string|null}} taxes\n * @property {{uuid: string|null, stampedAt: string|null, certifierRfc: string|null}|null} stamp null: sin timbre del SAT\n */\n\n/** El tipo de comprobante (TipoDeComprobante): ingreso, egreso, traslado, nómina o pago. */\nexport const CFDI_VOUCHER_TYPE = Object.freeze({income: 'I', expense: 'E', transfer: 'T', payroll: 'N', payment: 'P'});\n\n// una etiqueta de apertura con sus atributos (las de cierre y los comentarios no traen datos)\nconst TAG_PATTERN = /<(?:[A-Za-z_][\\w.-]*:)?([A-Za-z_][\\w.-]*)((?:\\s+[\\w:.-]+\\s*=\\s*(?:\"[^\"]*\"|'[^']*'))*)\\s*\\/?>/g;\nconst ATTRIBUTE_PATTERN = /([\\w:.-]+)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/g;\nconst XML_ENTITIES = Object.freeze({amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\"});\nconst HEXADECIMAL_RADIX = 16;\nconst DECIMAL_RADIX = 10;\n\n/**\n * Un valor de atributo con sus entidades de XML ya cambiadas (&amp; → &, &#241; → ñ).\n *\n * @param {string} value\n * @returns {string}\n */\nfunction decodeXmlText(value) {\n return value.replace(/&(#x[\\da-f]+|#\\d+|\\w+);/gi, (entity, code) => {\n if (code[0] === '#') {\n const isHexadecimal = code[1].toLowerCase() === 'x';\n return String.fromCodePoint(parseInt(code.slice(isHexadecimal ? 2 : 1), isHexadecimal ? HEXADECIMAL_RADIX : DECIMAL_RADIX));\n }\n return XML_ENTITIES[code] ?? entity;\n });\n}\n\n/**\n * Los elementos del XML, en orden, con su nombre (sin prefijo) y sus atributos.\n *\n * @param {string} xmlText\n * @returns {Array<{name: string, attributes: Record<string, string>}>}\n */\nfunction elementsOf(xmlText) {\n const elements = [];\n for (const [, name, attributesText] of xmlText.matchAll(TAG_PATTERN)) {\n const attributes = {};\n for (const [, attributeName, doubleQuoted, singleQuoted] of attributesText.matchAll(ATTRIBUTE_PATTERN)) {\n attributes[attributeName] = decodeXmlText(doubleQuoted ?? singleQuoted);\n }\n elements.push({name, attributes});\n }\n return elements;\n}\n\n/**\n * Lee el XML de un CFDI.\n *\n * @param {string} xmlText\n * @returns {Cfdi|null} null si el XML no es un CFDI (no trae su Comprobante con Emisor y Receptor)\n */\nexport function readCfdi(xmlText) {\n const elements = elementsOf(xmlText ?? '');\n const first = (name) => elements.find((element) => element.name === name)?.attributes;\n const voucher = first('Comprobante');\n const issuer = first('Emisor');\n const receiver = first('Receptor');\n // guard clause: otro XML (una addenda suelta, un acuse) no es una factura\n if (!voucher || !issuer || !receiver) {\n return null;\n }\n // los impuestos del comprobante son los que traen los totales (los de cada concepto no)\n const totals = elements.find((element) => element.name === 'Impuestos'\n && ('TotalImpuestosTrasladados' in element.attributes || 'TotalImpuestosRetenidos' in element.attributes))?.attributes ?? {};\n const stamp = first('TimbreFiscalDigital');\n\n return {\n version: voucher.Version ?? voucher.version ?? null,\n series: voucher.Serie ?? null,\n folio: voucher.Folio ?? null,\n issuedAt: voucher.Fecha ?? null,\n voucherType: voucher.TipoDeComprobante ?? null,\n paymentMethod: voucher.MetodoPago ?? null,\n paymentForm: voucher.FormaPago ?? null,\n currency: voucher.Moneda ?? null,\n exchangeRate: voucher.TipoCambio ?? null,\n subtotal: voucher.SubTotal ?? null,\n discount: voucher.Descuento ?? null,\n total: voucher.Total ?? null,\n placeOfIssue: voucher.LugarExpedicion ?? null,\n issuer: {rfc: issuer.Rfc ?? null, name: issuer.Nombre ?? null, taxRegime: issuer.RegimenFiscal ?? null},\n receiver: {\n rfc: receiver.Rfc ?? null, name: receiver.Nombre ?? null, cfdiUse: receiver.UsoCFDI ?? null,\n taxRegime: receiver.RegimenFiscalReceptor ?? null, postalCode: receiver.DomicilioFiscalReceptor ?? null,\n },\n concepts: elements.filter((element) => element.name === 'Concepto').map(({attributes}) => ({\n productCode: attributes.ClaveProdServ ?? null,\n quantity: attributes.Cantidad ?? null,\n unitCode: attributes.ClaveUnidad ?? null,\n description: attributes.Descripcion ?? null,\n unitValue: attributes.ValorUnitario ?? null,\n amount: attributes.Importe ?? null,\n })),\n taxes: {transferred: totals.TotalImpuestosTrasladados ?? null, withheld: totals.TotalImpuestosRetenidos ?? null},\n // sin timbre todavía no es factura ante el SAT: no tiene folio fiscal\n stamp: stamp\n ? {uuid: stamp.UUID?.toUpperCase() ?? null, stampedAt: stamp.FechaTimbrado ?? null, certifierRfc: stamp.RfcProvCertif ?? null}\n : null,\n };\n}\n\n/**\n * Lee el CFDI de un archivo elegido (un File o un Blob).\n *\n * @param {Blob} file\n * @returns {Promise<Cfdi|null>} lo de readCfdi\n */\nexport async function readCfdiFile(file) {\n return readCfdi(await file.text());\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ const DER_TAG = Object.freeze({
2
+ integer: 2,
3
+ objectIdentifier: 6,
4
+ utf8String: 12,
5
+ printableString: 19,
6
+ teletexString: 20,
7
+ ia5String: 22,
8
+ utcTime: 23,
9
+ generalizedTime: 24,
10
+ bmpString: 30,
11
+ sequence: 48,
12
+ set: 49
13
+ });
14
+ const CONSTRUCTED_BIT = 32;
15
+ const LONG_LENGTH_BIT = 128;
16
+ const LOW_SEVEN_BITS = 127;
17
+ const OID_BASE = 128;
18
+ const OID_FIRST_ARC_DIVISOR = 40;
19
+ function readDerNode(bytes, offset = 0) {
20
+ const tag = bytes[offset];
21
+ let length = bytes[offset + 1];
22
+ let contentStart = offset + 2;
23
+ if (length & LONG_LENGTH_BIT) {
24
+ const lengthBytes = length & LOW_SEVEN_BITS;
25
+ length = 0;
26
+ for (let index = 0; index < lengthBytes; index += 1) {
27
+ length = length * 256 + bytes[contentStart + index];
28
+ }
29
+ contentStart += lengthBytes;
30
+ }
31
+ const end = contentStart + length;
32
+ if (tag === void 0 || end > bytes.length) {
33
+ throw new Error("El archivo no es un certificado: se cortó a media pieza");
34
+ }
35
+ const content = bytes.subarray(contentStart, end);
36
+ let children = null;
37
+ if (tag & CONSTRUCTED_BIT) {
38
+ children = [];
39
+ for (let childOffset = 0; childOffset < content.length; ) {
40
+ const child = readDerNode(content, childOffset);
41
+ children.push(child);
42
+ childOffset = child.end;
43
+ }
44
+ }
45
+ return { tag, content, end, children };
46
+ }
47
+ function objectIdentifierOf(content) {
48
+ const arcs = [Math.floor(content[0] / OID_FIRST_ARC_DIVISOR), content[0] % OID_FIRST_ARC_DIVISOR];
49
+ let value = 0;
50
+ for (const byte of content.subarray(1)) {
51
+ value = value * OID_BASE + (byte & LOW_SEVEN_BITS);
52
+ if (!(byte & LONG_LENGTH_BIT)) {
53
+ arcs.push(value);
54
+ value = 0;
55
+ }
56
+ }
57
+ return arcs.join(".");
58
+ }
59
+ function derTextOf({ tag, content }) {
60
+ if (tag === DER_TAG.bmpString) {
61
+ return new TextDecoder("utf-16be").decode(content);
62
+ }
63
+ if (tag === DER_TAG.teletexString) {
64
+ return new TextDecoder("latin1").decode(content);
65
+ }
66
+ return new TextDecoder("utf-8").decode(content);
67
+ }
68
+ function derTimeOf(node) {
69
+ const text = derTextOf(node);
70
+ const fullYearText = node.tag === DER_TAG.utcTime ? `${Number(text.slice(0, 2)) < 50 ? "20" : "19"}${text}` : text;
71
+ const [year, month, day, hour, minute, second] = [0, 4, 6, 8, 10, 12].map((start, index) => Number(fullYearText.slice(start, index === 0 ? 4 : start + 2)));
72
+ return new Date(Date.UTC(year, month - 1, day, hour, minute, second)).toISOString();
73
+ }
74
+ export {
75
+ DER_TAG,
76
+ derTextOf,
77
+ derTimeOf,
78
+ objectIdentifierOf,
79
+ readDerNode
80
+ };
81
+ //# sourceMappingURL=derReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derReader.js","sources":["../../src/mexico/derReader.js"],"sourcesContent":["// Lo mínimo para leer un archivo DER (el formato binario de los certificados .cer): cada pieza es etiqueta, largo y\n// contenido, y las que agrupan (SEQUENCE, SET) traen piezas adentro. No valida firmas ni descifra nada: solo lee.\n//\n// Lo usa certificateReader.js para sacar del .cer de la e.firma el RFC, el nombre, el número de certificado y la vigencia.\n// Va aquí en lugar de una librería (las de certificados pesan cientos de kB) porque leer es poco: lo que cuesta es descifrar\n// la llave, y eso no se hace aquí.\n//\n// Puro (sin React): se prueba con node junto con certificateReader.test.js.\n//\n// Genérico del paquete: function (regla de la casa).\n\n/** Las etiquetas DER que se leen. */\nexport const DER_TAG = Object.freeze({\n integer: 0x02,\n objectIdentifier: 0x06,\n utf8String: 0x0c,\n printableString: 0x13,\n teletexString: 0x14,\n ia5String: 0x16,\n utcTime: 0x17,\n generalizedTime: 0x18,\n bmpString: 0x1e,\n sequence: 0x30,\n set: 0x31,\n});\n// el bit que dice que la pieza agrupa otras (SEQUENCE, SET, los [0] explícitos)\nconst CONSTRUCTED_BIT = 0x20;\n// el bit alto del primer byte del largo: los siguientes N bytes dicen el largo\nconst LONG_LENGTH_BIT = 0x80;\nconst LOW_SEVEN_BITS = 0x7f;\nconst OID_BASE = 128;\nconst OID_FIRST_ARC_DIVISOR = 40;\n\n/**\n * Una pieza DER y, si agrupa otras, sus piezas.\n *\n * @param {Uint8Array} bytes\n * @param {number} [offset]\n * @returns {{tag: number, content: Uint8Array, end: number, children: Array<object>|null}}\n */\nexport function readDerNode(bytes, offset = 0) {\n const tag = bytes[offset];\n let length = bytes[offset + 1];\n let contentStart = offset + 2;\n if (length & LONG_LENGTH_BIT) {\n const lengthBytes = length & LOW_SEVEN_BITS;\n length = 0;\n for (let index = 0; index < lengthBytes; index += 1) {\n length = (length * 256) + bytes[contentStart + index];\n }\n contentStart += lengthBytes;\n }\n const end = contentStart + length;\n if (tag === undefined || end > bytes.length) {\n throw new Error('El archivo no es un certificado: se cortó a media pieza');\n }\n const content = bytes.subarray(contentStart, end);\n let children = null;\n if (tag & CONSTRUCTED_BIT) {\n children = [];\n for (let childOffset = 0; childOffset < content.length;) {\n const child = readDerNode(content, childOffset);\n children.push(child);\n childOffset = child.end;\n }\n }\n return {tag, content, end, children};\n}\n\n/**\n * Un identificador de objeto (OID) como se escribe: '2.5.4.45'.\n *\n * @param {Uint8Array} content\n * @returns {string}\n */\nexport function objectIdentifierOf(content) {\n const arcs = [Math.floor(content[0] / OID_FIRST_ARC_DIVISOR), content[0] % OID_FIRST_ARC_DIVISOR];\n let value = 0;\n for (const byte of content.subarray(1)) {\n value = (value * OID_BASE) + (byte & LOW_SEVEN_BITS);\n if (!(byte & LONG_LENGTH_BIT)) {\n arcs.push(value);\n value = 0;\n }\n }\n return arcs.join('.');\n}\n\n/**\n * El texto de una pieza de texto (UTF-8, imprimible, latin-1 o UTF-16 de los nombres con acentos).\n *\n * @param {{tag: number, content: Uint8Array}} node\n * @returns {string}\n */\nexport function derTextOf({tag, content}) {\n if (tag === DER_TAG.bmpString) {\n return new TextDecoder('utf-16be').decode(content);\n }\n if (tag === DER_TAG.teletexString) {\n return new TextDecoder('latin1').decode(content);\n }\n return new TextDecoder('utf-8').decode(content);\n}\n\n/**\n * Una fecha de certificado como instante ISO: UTCTime 'AAMMDDhhmmssZ' (de 1950 a 2049) o GeneralizedTime 'AAAAMMDDhhmmssZ'.\n *\n * @param {{tag: number, content: Uint8Array}} node\n * @returns {string} '2030-11-02T04:56:30.000Z'\n */\nexport function derTimeOf(node) {\n const text = derTextOf(node);\n const fullYearText = node.tag === DER_TAG.utcTime\n ? `${Number(text.slice(0, 2)) < 50 ? '20' : '19'}${text}`\n : text;\n const [year, month, day, hour, minute, second] = [0, 4, 6, 8, 10, 12].map((start, index) => Number(fullYearText.slice(start, index === 0 ? 4 : start + 2)));\n return new Date(Date.UTC(year, month - 1, day, hour, minute, second)).toISOString();\n}\n"],"names":[],"mappings":"AAYY,MAAC,UAAU,OAAO,OAAO;AAAA,EACjC,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,WAAW;AAAA,EACX,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,UAAU;AAAA,EACV,KAAK;AACT,CAAC;AAED,MAAM,kBAAkB;AAExB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,WAAW;AACjB,MAAM,wBAAwB;AASvB,SAAS,YAAY,OAAO,SAAS,GAAG;AAC3C,QAAM,MAAM,MAAM,MAAM;AACxB,MAAI,SAAS,MAAM,SAAS,CAAC;AAC7B,MAAI,eAAe,SAAS;AAC5B,MAAI,SAAS,iBAAiB;AAC1B,UAAM,cAAc,SAAS;AAC7B,aAAS;AACT,aAAS,QAAQ,GAAG,QAAQ,aAAa,SAAS,GAAG;AACjD,eAAU,SAAS,MAAO,MAAM,eAAe,KAAK;AAAA,IACxD;AACA,oBAAgB;AAAA,EACpB;AACA,QAAM,MAAM,eAAe;AAC3B,MAAI,QAAQ,UAAa,MAAM,MAAM,QAAQ;AACzC,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC7E;AACA,QAAM,UAAU,MAAM,SAAS,cAAc,GAAG;AAChD,MAAI,WAAW;AACf,MAAI,MAAM,iBAAiB;AACvB,eAAW,CAAA;AACX,aAAS,cAAc,GAAG,cAAc,QAAQ,UAAS;AACrD,YAAM,QAAQ,YAAY,SAAS,WAAW;AAC9C,eAAS,KAAK,KAAK;AACnB,oBAAc,MAAM;AAAA,IACxB;AAAA,EACJ;AACA,SAAO,EAAC,KAAK,SAAS,KAAK,SAAQ;AACvC;AAQO,SAAS,mBAAmB,SAAS;AACxC,QAAM,OAAO,CAAC,KAAK,MAAM,QAAQ,CAAC,IAAI,qBAAqB,GAAG,QAAQ,CAAC,IAAI,qBAAqB;AAChG,MAAI,QAAQ;AACZ,aAAW,QAAQ,QAAQ,SAAS,CAAC,GAAG;AACpC,YAAS,QAAQ,YAAa,OAAO;AACrC,QAAI,EAAE,OAAO,kBAAkB;AAC3B,WAAK,KAAK,KAAK;AACf,cAAQ;AAAA,IACZ;AAAA,EACJ;AACA,SAAO,KAAK,KAAK,GAAG;AACxB;AAQO,SAAS,UAAU,EAAC,KAAK,QAAO,GAAG;AACtC,MAAI,QAAQ,QAAQ,WAAW;AAC3B,WAAO,IAAI,YAAY,UAAU,EAAE,OAAO,OAAO;AAAA,EACrD;AACA,MAAI,QAAQ,QAAQ,eAAe;AAC/B,WAAO,IAAI,YAAY,QAAQ,EAAE,OAAO,OAAO;AAAA,EACnD;AACA,SAAO,IAAI,YAAY,OAAO,EAAE,OAAO,OAAO;AAClD;AAQO,SAAS,UAAU,MAAM;AAC5B,QAAM,OAAO,UAAU,IAAI;AAC3B,QAAM,eAAe,KAAK,QAAQ,QAAQ,UACpC,GAAG,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,IAAI,KACrD;AACN,QAAM,CAAC,MAAM,OAAO,KAAK,MAAM,QAAQ,MAAM,IAAI,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,EAAE,EAAE,IAAI,CAAC,OAAO,UAAU,OAAO,aAAa,MAAM,OAAO,UAAU,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC;AAC1J,SAAO,IAAI,KAAK,KAAK,IAAI,MAAM,QAAQ,GAAG,KAAK,MAAM,QAAQ,MAAM,CAAC,EAAE,YAAW;AACrF;"}
@@ -0,0 +1,51 @@
1
+ import { readDerNode, objectIdentifierOf } from "./derReader.js";
2
+ const EFIRMA_KEY_PROBLEM = Object.freeze({ notKey: "notKey", wrongPassword: "wrongPassword", otherCertificate: "otherCertificate" });
3
+ const PBES2_OBJECT_IDENTIFIER = "1.2.840.113549.1.5.13";
4
+ const HEXADECIMAL_RADIX = 16;
5
+ class EfirmaKeyError extends Error {
6
+ /**
7
+ * @param {string} reason EFIRMA_KEY_PROBLEM
8
+ */
9
+ constructor(reason) {
10
+ super(`La e.firma no pasó: ${reason}`);
11
+ this.name = "EfirmaKeyError";
12
+ this.reason = reason;
13
+ }
14
+ }
15
+ function hexOf(bytes) {
16
+ return Array.from(bytes, (byte) => byte.toString(HEXADECIMAL_RADIX).padStart(2, "0")).join("");
17
+ }
18
+ function isEncryptedPrivateKey(keyBytes) {
19
+ try {
20
+ const algorithm = readDerNode(keyBytes).children?.[0]?.children?.[0];
21
+ return Boolean(algorithm) && objectIdentifierOf(algorithm.content) === PBES2_OBJECT_IDENTIFIER;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+ async function verifyEfirmaKey({ certificateBytes, keyBytes, password }) {
27
+ const key = keyBytes instanceof Uint8Array ? keyBytes : new Uint8Array(keyBytes);
28
+ const certificate = certificateBytes instanceof Uint8Array ? certificateBytes : new Uint8Array(certificateBytes);
29
+ if (!isEncryptedPrivateKey(key)) {
30
+ throw new EfirmaKeyError(EFIRMA_KEY_PROBLEM.notKey);
31
+ }
32
+ const jsrsasignModule = await import("jsrsasign");
33
+ const { KEYUTIL, KJUR, X509 } = jsrsasignModule.default ?? jsrsasignModule;
34
+ let privateKey;
35
+ try {
36
+ privateKey = KEYUTIL.getKey(KJUR.asn1.ASN1Util.getPEMStringFromHex(hexOf(key), "ENCRYPTED PRIVATE KEY"), password);
37
+ } catch {
38
+ throw new EfirmaKeyError(EFIRMA_KEY_PROBLEM.wrongPassword);
39
+ }
40
+ const x509 = new X509();
41
+ x509.readCertHex(hexOf(certificate));
42
+ if (!x509.getPublicKey().n?.equals(privateKey.n)) {
43
+ throw new EfirmaKeyError(EFIRMA_KEY_PROBLEM.otherCertificate);
44
+ }
45
+ }
46
+ export {
47
+ EFIRMA_KEY_PROBLEM,
48
+ EfirmaKeyError,
49
+ verifyEfirmaKey
50
+ };
51
+ //# sourceMappingURL=efirmaKey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"efirmaKey.js","sources":["../../src/mexico/efirmaKey.js"],"sourcesContent":["// Abrir la llave privada (.key) de una e.firma con su contraseña y comprobar que es la del certificado (.cer), en el\n// navegador y sin mandar nada a ningún lado.\n//\n// Va con jsrsasign porque la llave del SAT viene cifrada (PKCS#8 con PBES2 y 3DES) y el navegador no trae 3DES. Se carga\n// hasta que se usa (import dinámico): la pantalla no la baja al abrir, solo al picar «Revisar».\n//\n// Los motivos de un «no» se distinguen (EFIRMA_KEY_PROBLEM): el archivo no es una llave, la contraseña no la abre, o la\n// llave abre pero es de otro certificado.\n//\n// Genérico del paquete: function (regla de la casa). Pruebas en efirmaKey.test.js.\nimport {objectIdentifierOf, readDerNode} from '@/mexico/derReader.js';\n\n/** Por qué no pasó la llave. */\nexport const EFIRMA_KEY_PROBLEM = Object.freeze({notKey: 'notKey', wrongPassword: 'wrongPassword', otherCertificate: 'otherCertificate'});\n\n// el algoritmo con que se cifra una llave PKCS#8 (PBES2): así empieza un .key del SAT\nconst PBES2_OBJECT_IDENTIFIER = '1.2.840.113549.1.5.13';\nconst HEXADECIMAL_RADIX = 16;\n\n/** Un «no» de la llave, con su motivo (reason es uno de EFIRMA_KEY_PROBLEM). */\nexport class EfirmaKeyError extends Error {\n /**\n * @param {string} reason EFIRMA_KEY_PROBLEM\n */\n constructor(reason) {\n super(`La e.firma no pasó: ${reason}`);\n this.name = 'EfirmaKeyError';\n this.reason = reason;\n }\n}\n\n/**\n * Los bytes como texto hexadecimal (lo que pide jsrsasign).\n *\n * @param {Uint8Array} bytes\n * @returns {string}\n */\nfunction hexOf(bytes) {\n return Array.from(bytes, (byte) => byte.toString(HEXADECIMAL_RADIX).padStart(2, '0')).join('');\n}\n\n/**\n * ¿Los bytes son una llave PKCS#8 cifrada con PBES2? (sin abrirla)\n *\n * @param {Uint8Array} keyBytes\n * @returns {boolean}\n */\nfunction isEncryptedPrivateKey(keyBytes) {\n try {\n const algorithm = readDerNode(keyBytes).children?.[0]?.children?.[0];\n return Boolean(algorithm) && objectIdentifierOf(algorithm.content) === PBES2_OBJECT_IDENTIFIER;\n } catch {\n return false;\n }\n}\n\n/**\n * Abre la llave con la contraseña y la compara con el certificado. Si todo está bien no regresa nada; si no, truena con\n * EfirmaKeyError y su motivo.\n *\n * @param {{certificateBytes: ArrayBuffer|Uint8Array, keyBytes: ArrayBuffer|Uint8Array, password: string}} efirma\n * @returns {Promise<void>}\n */\nexport async function verifyEfirmaKey({certificateBytes, keyBytes, password}) {\n const key = keyBytes instanceof Uint8Array ? keyBytes : new Uint8Array(keyBytes);\n const certificate = certificateBytes instanceof Uint8Array ? certificateBytes : new Uint8Array(certificateBytes);\n if (!isEncryptedPrivateKey(key)) {\n throw new EfirmaKeyError(EFIRMA_KEY_PROBLEM.notKey);\n }\n // jsrsasign se publica como CommonJS: según el empaquetador llega en default o suelta\n const jsrsasignModule = await import('jsrsasign');\n const {KEYUTIL, KJUR, X509} = jsrsasignModule.default ?? jsrsasignModule;\n\n let privateKey;\n try {\n privateKey = KEYUTIL.getKey(KJUR.asn1.ASN1Util.getPEMStringFromHex(hexOf(key), 'ENCRYPTED PRIVATE KEY'), password);\n } catch {\n // con otra contraseña el descifrado da basura y jsrsasign no la reconoce como llave\n throw new EfirmaKeyError(EFIRMA_KEY_PROBLEM.wrongPassword);\n }\n const x509 = new X509();\n x509.readCertHex(hexOf(certificate));\n // la llave es la del certificado si su módulo RSA es el mismo que el de la llave pública del certificado\n if (!x509.getPublicKey().n?.equals(privateKey.n)) {\n throw new EfirmaKeyError(EFIRMA_KEY_PROBLEM.otherCertificate);\n }\n}\n"],"names":[],"mappings":";AAaY,MAAC,qBAAqB,OAAO,OAAO,EAAC,QAAQ,UAAU,eAAe,iBAAiB,kBAAkB,mBAAkB,CAAC;AAGxI,MAAM,0BAA0B;AAChC,MAAM,oBAAoB;AAGnB,MAAM,uBAAuB,MAAM;AAAA;AAAA;AAAA;AAAA,EAItC,YAAY,QAAQ;AAChB,UAAM,uBAAuB,MAAM,EAAE;AACrC,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAClB;AACJ;AAQA,SAAS,MAAM,OAAO;AAClB,SAAO,MAAM,KAAK,OAAO,CAAC,SAAS,KAAK,SAAS,iBAAiB,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACjG;AAQA,SAAS,sBAAsB,UAAU;AACrC,MAAI;AACA,UAAM,YAAY,YAAY,QAAQ,EAAE,WAAW,CAAC,GAAG,WAAW,CAAC;AACnE,WAAO,QAAQ,SAAS,KAAK,mBAAmB,UAAU,OAAO,MAAM;AAAA,EAC3E,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AASO,eAAe,gBAAgB,EAAC,kBAAkB,UAAU,SAAQ,GAAG;AAC1E,QAAM,MAAM,oBAAoB,aAAa,WAAW,IAAI,WAAW,QAAQ;AAC/E,QAAM,cAAc,4BAA4B,aAAa,mBAAmB,IAAI,WAAW,gBAAgB;AAC/G,MAAI,CAAC,sBAAsB,GAAG,GAAG;AAC7B,UAAM,IAAI,eAAe,mBAAmB,MAAM;AAAA,EACtD;AAEA,QAAM,kBAAkB,MAAM,OAAO,WAAW;AAChD,QAAM,EAAC,SAAS,MAAM,KAAI,IAAI,gBAAgB,WAAW;AAEzD,MAAI;AACJ,MAAI;AACA,iBAAa,QAAQ,OAAO,KAAK,KAAK,SAAS,oBAAoB,MAAM,GAAG,GAAG,uBAAuB,GAAG,QAAQ;AAAA,EACrH,QAAQ;AAEJ,UAAM,IAAI,eAAe,mBAAmB,aAAa;AAAA,EAC7D;AACA,QAAM,OAAO,IAAI,KAAI;AACrB,OAAK,YAAY,MAAM,WAAW,CAAC;AAEnC,MAAI,CAAC,KAAK,aAAY,EAAG,GAAG,OAAO,WAAW,CAAC,GAAG;AAC9C,UAAM,IAAI,eAAe,mBAAmB,gBAAgB;AAAA,EAChE;AACJ;"}