@jjlmoya/utils-developer 1.2.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 (228) hide show
  1. package/package.json +64 -0
  2. package/src/category/i18n/en.ts +34 -0
  3. package/src/category/i18n/es.ts +34 -0
  4. package/src/category/i18n/fr.ts +34 -0
  5. package/src/category/index.ts +31 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +78 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/css_specificity_logic.test.ts +39 -0
  17. package/src/tests/faq_count.test.ts +19 -0
  18. package/src/tests/locale_completeness.test.ts +42 -0
  19. package/src/tests/mocks/astro_mock.js +2 -0
  20. package/src/tests/no_h1_in_components.test.ts +48 -0
  21. package/src/tests/schemas_fulfillment.test.ts +23 -0
  22. package/src/tests/seo_length.test.ts +22 -0
  23. package/src/tests/title_quality.test.ts +55 -0
  24. package/src/tests/tool_validation.test.ts +17 -0
  25. package/src/tool/aspectRatio/bibliography.astro +14 -0
  26. package/src/tool/aspectRatio/component.astro +468 -0
  27. package/src/tool/aspectRatio/i18n/en.ts +177 -0
  28. package/src/tool/aspectRatio/i18n/es.ts +177 -0
  29. package/src/tool/aspectRatio/i18n/fr.ts +177 -0
  30. package/src/tool/aspectRatio/index.ts +34 -0
  31. package/src/tool/aspectRatio/seo.astro +14 -0
  32. package/src/tool/aspectRatio/ui.ts +12 -0
  33. package/src/tool/calculadoraTiempoDatos/bibliography.astro +14 -0
  34. package/src/tool/calculadoraTiempoDatos/component.astro +829 -0
  35. package/src/tool/calculadoraTiempoDatos/i18n/en.ts +150 -0
  36. package/src/tool/calculadoraTiempoDatos/i18n/es.ts +163 -0
  37. package/src/tool/calculadoraTiempoDatos/i18n/fr.ts +150 -0
  38. package/src/tool/calculadoraTiempoDatos/index.ts +33 -0
  39. package/src/tool/calculadoraTiempoDatos/seo.astro +14 -0
  40. package/src/tool/calculadoraTiempoDatos/ui.ts +24 -0
  41. package/src/tool/colorConverter/bibliography.astro +14 -0
  42. package/src/tool/colorConverter/component.astro +504 -0
  43. package/src/tool/colorConverter/i18n/en.ts +126 -0
  44. package/src/tool/colorConverter/i18n/es.ts +126 -0
  45. package/src/tool/colorConverter/i18n/fr.ts +126 -0
  46. package/src/tool/colorConverter/index.ts +33 -0
  47. package/src/tool/colorConverter/seo.astro +14 -0
  48. package/src/tool/colorConverter/ui.ts +16 -0
  49. package/src/tool/conversorExcelCsvHtml/bibliography.astro +14 -0
  50. package/src/tool/conversorExcelCsvHtml/component.astro +595 -0
  51. package/src/tool/conversorExcelCsvHtml/i18n/en.ts +129 -0
  52. package/src/tool/conversorExcelCsvHtml/i18n/es.ts +129 -0
  53. package/src/tool/conversorExcelCsvHtml/i18n/fr.ts +129 -0
  54. package/src/tool/conversorExcelCsvHtml/index.ts +33 -0
  55. package/src/tool/conversorExcelCsvHtml/seo.astro +14 -0
  56. package/src/tool/conversorExcelCsvHtml/ui.ts +14 -0
  57. package/src/tool/cronGenerator/bibliography.astro +14 -0
  58. package/src/tool/cronGenerator/component.astro +667 -0
  59. package/src/tool/cronGenerator/i18n/en.ts +191 -0
  60. package/src/tool/cronGenerator/i18n/es.ts +191 -0
  61. package/src/tool/cronGenerator/i18n/fr.ts +191 -0
  62. package/src/tool/cronGenerator/index.ts +34 -0
  63. package/src/tool/cronGenerator/seo.astro +14 -0
  64. package/src/tool/cronGenerator/ui.ts +45 -0
  65. package/src/tool/cssSpecificityCalculator/bibliography.astro +14 -0
  66. package/src/tool/cssSpecificityCalculator/component.astro +292 -0
  67. package/src/tool/cssSpecificityCalculator/i18n/en.ts +168 -0
  68. package/src/tool/cssSpecificityCalculator/i18n/es.ts +168 -0
  69. package/src/tool/cssSpecificityCalculator/i18n/fr.ts +168 -0
  70. package/src/tool/cssSpecificityCalculator/index.ts +34 -0
  71. package/src/tool/cssSpecificityCalculator/logic.ts +100 -0
  72. package/src/tool/cssSpecificityCalculator/seo.astro +14 -0
  73. package/src/tool/cssSpecificityCalculator/ui-controller.ts +57 -0
  74. package/src/tool/cssSpecificityCalculator/ui.ts +11 -0
  75. package/src/tool/cssToInlineConverter/bibliography.astro +14 -0
  76. package/src/tool/cssToInlineConverter/component.astro +308 -0
  77. package/src/tool/cssToInlineConverter/i18n/en.ts +160 -0
  78. package/src/tool/cssToInlineConverter/i18n/es.ts +160 -0
  79. package/src/tool/cssToInlineConverter/i18n/fr.ts +160 -0
  80. package/src/tool/cssToInlineConverter/index.ts +34 -0
  81. package/src/tool/cssToInlineConverter/seo.astro +14 -0
  82. package/src/tool/cssToInlineConverter/ui.ts +12 -0
  83. package/src/tool/duplicateCssRemover/bibliography.astro +14 -0
  84. package/src/tool/duplicateCssRemover/component.astro +353 -0
  85. package/src/tool/duplicateCssRemover/i18n/en.ts +167 -0
  86. package/src/tool/duplicateCssRemover/i18n/es.ts +167 -0
  87. package/src/tool/duplicateCssRemover/i18n/fr.ts +167 -0
  88. package/src/tool/duplicateCssRemover/index.ts +34 -0
  89. package/src/tool/duplicateCssRemover/seo.astro +14 -0
  90. package/src/tool/duplicateCssRemover/ui.ts +12 -0
  91. package/src/tool/generadorSecurityTxt/bibliography.astro +14 -0
  92. package/src/tool/generadorSecurityTxt/component.astro +473 -0
  93. package/src/tool/generadorSecurityTxt/i18n/en.ts +173 -0
  94. package/src/tool/generadorSecurityTxt/i18n/es.ts +173 -0
  95. package/src/tool/generadorSecurityTxt/i18n/fr.ts +173 -0
  96. package/src/tool/generadorSecurityTxt/index.ts +33 -0
  97. package/src/tool/generadorSecurityTxt/seo.astro +14 -0
  98. package/src/tool/generadorSecurityTxt/ui.ts +27 -0
  99. package/src/tool/hashGenerator/bibliography.astro +14 -0
  100. package/src/tool/hashGenerator/component.astro +367 -0
  101. package/src/tool/hashGenerator/i18n/en.ts +118 -0
  102. package/src/tool/hashGenerator/i18n/es.ts +118 -0
  103. package/src/tool/hashGenerator/i18n/fr.ts +118 -0
  104. package/src/tool/hashGenerator/index.ts +33 -0
  105. package/src/tool/hashGenerator/md5.ts +138 -0
  106. package/src/tool/hashGenerator/seo.astro +14 -0
  107. package/src/tool/hashGenerator/ui.ts +11 -0
  108. package/src/tool/inspectorCertificadosSsl/bibliography.astro +14 -0
  109. package/src/tool/inspectorCertificadosSsl/component.astro +580 -0
  110. package/src/tool/inspectorCertificadosSsl/i18n/en.ts +214 -0
  111. package/src/tool/inspectorCertificadosSsl/i18n/es.ts +214 -0
  112. package/src/tool/inspectorCertificadosSsl/i18n/fr.ts +214 -0
  113. package/src/tool/inspectorCertificadosSsl/index.ts +33 -0
  114. package/src/tool/inspectorCertificadosSsl/seo.astro +14 -0
  115. package/src/tool/inspectorCertificadosSsl/ui.ts +29 -0
  116. package/src/tool/jsonFormatter/bibliography.astro +14 -0
  117. package/src/tool/jsonFormatter/component.astro +590 -0
  118. package/src/tool/jsonFormatter/i18n/en.ts +156 -0
  119. package/src/tool/jsonFormatter/i18n/es.ts +156 -0
  120. package/src/tool/jsonFormatter/i18n/fr.ts +156 -0
  121. package/src/tool/jsonFormatter/index.ts +34 -0
  122. package/src/tool/jsonFormatter/seo.astro +14 -0
  123. package/src/tool/jsonFormatter/ui.ts +15 -0
  124. package/src/tool/keycode/bibliography.astro +14 -0
  125. package/src/tool/keycode/component.astro +540 -0
  126. package/src/tool/keycode/i18n/en.ts +160 -0
  127. package/src/tool/keycode/i18n/es.ts +160 -0
  128. package/src/tool/keycode/i18n/fr.ts +160 -0
  129. package/src/tool/keycode/index.ts +34 -0
  130. package/src/tool/keycode/seo.astro +14 -0
  131. package/src/tool/keycode/ui.ts +10 -0
  132. package/src/tool/llmCostCalculator/bibliography.astro +14 -0
  133. package/src/tool/llmCostCalculator/component.astro +488 -0
  134. package/src/tool/llmCostCalculator/data.ts +99 -0
  135. package/src/tool/llmCostCalculator/i18n/en.ts +165 -0
  136. package/src/tool/llmCostCalculator/i18n/es.ts +165 -0
  137. package/src/tool/llmCostCalculator/i18n/fr.ts +165 -0
  138. package/src/tool/llmCostCalculator/index.ts +34 -0
  139. package/src/tool/llmCostCalculator/seo.astro +14 -0
  140. package/src/tool/llmCostCalculator/ui.ts +11 -0
  141. package/src/tool/mobileMockupGenerator/app.ts +614 -0
  142. package/src/tool/mobileMockupGenerator/bibliography.astro +14 -0
  143. package/src/tool/mobileMockupGenerator/component.astro +1044 -0
  144. package/src/tool/mobileMockupGenerator/constants.ts +48 -0
  145. package/src/tool/mobileMockupGenerator/i18n/en.ts +121 -0
  146. package/src/tool/mobileMockupGenerator/i18n/es.ts +121 -0
  147. package/src/tool/mobileMockupGenerator/i18n/fr.ts +121 -0
  148. package/src/tool/mobileMockupGenerator/index.ts +33 -0
  149. package/src/tool/mobileMockupGenerator/rendering.ts +189 -0
  150. package/src/tool/mobileMockupGenerator/seo.astro +14 -0
  151. package/src/tool/mobileMockupGenerator/store.ts +145 -0
  152. package/src/tool/mobileMockupGenerator/types.ts +41 -0
  153. package/src/tool/mobileMockupGenerator/ui.ts +48 -0
  154. package/src/tool/musicalTypography/bibliography.astro +14 -0
  155. package/src/tool/musicalTypography/component.astro +480 -0
  156. package/src/tool/musicalTypography/i18n/en.ts +179 -0
  157. package/src/tool/musicalTypography/i18n/es.ts +179 -0
  158. package/src/tool/musicalTypography/i18n/fr.ts +179 -0
  159. package/src/tool/musicalTypography/index.ts +33 -0
  160. package/src/tool/musicalTypography/seo.astro +14 -0
  161. package/src/tool/musicalTypography/ui.ts +25 -0
  162. package/src/tool/placeholderGenerator/bibliography.astro +14 -0
  163. package/src/tool/placeholderGenerator/component.astro +499 -0
  164. package/src/tool/placeholderGenerator/i18n/en.ts +176 -0
  165. package/src/tool/placeholderGenerator/i18n/es.ts +176 -0
  166. package/src/tool/placeholderGenerator/i18n/fr.ts +176 -0
  167. package/src/tool/placeholderGenerator/index.ts +34 -0
  168. package/src/tool/placeholderGenerator/seo.astro +14 -0
  169. package/src/tool/placeholderGenerator/ui.ts +14 -0
  170. package/src/tool/promptLibrary/bibliography.astro +14 -0
  171. package/src/tool/promptLibrary/component.astro +1131 -0
  172. package/src/tool/promptLibrary/controller.ts +49 -0
  173. package/src/tool/promptLibrary/i18n/en.ts +131 -0
  174. package/src/tool/promptLibrary/i18n/es.ts +131 -0
  175. package/src/tool/promptLibrary/i18n/fr.ts +126 -0
  176. package/src/tool/promptLibrary/index.ts +33 -0
  177. package/src/tool/promptLibrary/seo.astro +14 -0
  178. package/src/tool/promptLibrary/ui.ts +29 -0
  179. package/src/tool/readabilityCalculator/bibliography.astro +14 -0
  180. package/src/tool/readabilityCalculator/component.astro +534 -0
  181. package/src/tool/readabilityCalculator/i18n/en.ts +133 -0
  182. package/src/tool/readabilityCalculator/i18n/es.ts +133 -0
  183. package/src/tool/readabilityCalculator/i18n/fr.ts +133 -0
  184. package/src/tool/readabilityCalculator/index.ts +33 -0
  185. package/src/tool/readabilityCalculator/seo.astro +14 -0
  186. package/src/tool/readabilityCalculator/ui.ts +27 -0
  187. package/src/tool/svgSanitizer/bibliography.astro +14 -0
  188. package/src/tool/svgSanitizer/component.astro +554 -0
  189. package/src/tool/svgSanitizer/i18n/en.ts +125 -0
  190. package/src/tool/svgSanitizer/i18n/es.ts +125 -0
  191. package/src/tool/svgSanitizer/i18n/fr.ts +125 -0
  192. package/src/tool/svgSanitizer/index.ts +33 -0
  193. package/src/tool/svgSanitizer/seo.astro +14 -0
  194. package/src/tool/svgSanitizer/ui.ts +18 -0
  195. package/src/tool/svgToCss/bibliography.astro +14 -0
  196. package/src/tool/svgToCss/component.astro +381 -0
  197. package/src/tool/svgToCss/i18n/en.ts +174 -0
  198. package/src/tool/svgToCss/i18n/es.ts +174 -0
  199. package/src/tool/svgToCss/i18n/fr.ts +174 -0
  200. package/src/tool/svgToCss/index.ts +34 -0
  201. package/src/tool/svgToCss/seo.astro +14 -0
  202. package/src/tool/svgToCss/ui.ts +13 -0
  203. package/src/tool/urlCleaner/bibliography.astro +14 -0
  204. package/src/tool/urlCleaner/component.astro +458 -0
  205. package/src/tool/urlCleaner/i18n/en.ts +116 -0
  206. package/src/tool/urlCleaner/i18n/es.ts +116 -0
  207. package/src/tool/urlCleaner/i18n/fr.ts +116 -0
  208. package/src/tool/urlCleaner/index.ts +33 -0
  209. package/src/tool/urlCleaner/seo.astro +14 -0
  210. package/src/tool/urlCleaner/ui.ts +12 -0
  211. package/src/tool/urlEncoderDecoder/bibliography.astro +14 -0
  212. package/src/tool/urlEncoderDecoder/component.astro +291 -0
  213. package/src/tool/urlEncoderDecoder/i18n/en.ts +162 -0
  214. package/src/tool/urlEncoderDecoder/i18n/es.ts +162 -0
  215. package/src/tool/urlEncoderDecoder/i18n/fr.ts +162 -0
  216. package/src/tool/urlEncoderDecoder/index.ts +34 -0
  217. package/src/tool/urlEncoderDecoder/seo.astro +14 -0
  218. package/src/tool/urlEncoderDecoder/ui.ts +11 -0
  219. package/src/tool/utmGenerator/bibliography.astro +14 -0
  220. package/src/tool/utmGenerator/component.astro +270 -0
  221. package/src/tool/utmGenerator/i18n/en.ts +117 -0
  222. package/src/tool/utmGenerator/i18n/es.ts +117 -0
  223. package/src/tool/utmGenerator/i18n/fr.ts +117 -0
  224. package/src/tool/utmGenerator/index.ts +33 -0
  225. package/src/tool/utmGenerator/seo.astro +14 -0
  226. package/src/tool/utmGenerator/ui.ts +13 -0
  227. package/src/tools.ts +27 -0
  228. package/src/types.ts +72 -0
@@ -0,0 +1,580 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { KnownLocale } from '../../types';
4
+ import type { InspectorCertificadosSslUI } from './ui';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ ui?: Record<string, unknown>;
9
+ }
10
+
11
+ const { ui } = Astro.props;
12
+ const t = (ui ?? {}) as InspectorCertificadosSslUI;
13
+ ---
14
+
15
+ <div id="ics-root" data-i18n={JSON.stringify(t)} class="ics-root">
16
+ <div class="ics-container">
17
+ <div id="drop-zone" class="ics-drop-zone">
18
+ <input type="file" id="cert-file" class="ics-file-input" accept=".pem,.crt,.cer,.der" />
19
+ <div class="ics-drop-zone-content">
20
+ <div class="ics-icon-wrapper">
21
+ <Icon name="mdi:certificate" class="ics-certificate-icon" />
22
+ </div>
23
+ <h3 class="ics-drop-zone-title">{t.labelAnalyzeCertificate}</h3>
24
+ <p class="ics-drop-zone-text">{t.dragDropText}</p>
25
+ <p class="ics-drop-zone-subtext">
26
+ <small>{t.dragDropSubtext}</small>
27
+ </p>
28
+ </div>
29
+ </div>
30
+
31
+ <div id="error-message" class="ics-error-message"></div>
32
+
33
+ <div id="results" class="ics-results-grid">
34
+ <div class="ics-card" id="card-status">
35
+ <div class="ics-card-header">
36
+ <Icon name="mdi:shield-check" class="ics-card-icon" />
37
+ <h4>{t.cardStatusTitle}</h4>
38
+ </div>
39
+ <div class="ics-card-content">
40
+ <div class="ics-data-item">
41
+ <div id="validez-status"></div>
42
+ </div>
43
+ <div class="ics-data-item">
44
+ <span class="ics-data-label">{t.labelExpiryDate}:</span>
45
+ <span id="expiry-date" class="ics-data-value"></span>
46
+ </div>
47
+ <div class="ics-data-item">
48
+ <span class="ics-data-label">{t.labelIssueDate}:</span>
49
+ <span id="issue-date" class="ics-data-value"></span>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ <div class="ics-card" id="card-subject">
55
+ <div class="ics-card-header">
56
+ <Icon name="mdi:account-details" class="ics-card-icon" />
57
+ <h4>{t.cardSubjectTitle}</h4>
58
+ </div>
59
+ <div class="ics-card-content" id="subject-details"></div>
60
+ </div>
61
+
62
+ <div class="ics-card" id="card-issuer">
63
+ <div class="ics-card-header">
64
+ <Icon name="mdi:bank" class="ics-card-icon" />
65
+ <h4>{t.cardIssuerTitle}</h4>
66
+ </div>
67
+ <div class="ics-card-content" id="issuer-details"></div>
68
+ </div>
69
+
70
+ <div class="ics-card" id="card-fingerprints">
71
+ <div class="ics-card-header">
72
+ <Icon name="mdi:fingerprint" class="ics-card-icon" />
73
+ <h4>{t.cardFingerprintsTitle}</h4>
74
+ </div>
75
+ <div class="ics-card-content">
76
+ <div class="ics-data-item">
77
+ <span class="ics-data-label">{t.labelSha256}:</span>
78
+ <div id="sha256-fingerprint" class="ics-fingerprint"></div>
79
+ </div>
80
+ <div class="ics-data-item">
81
+ <span class="ics-data-label">{t.labelSha1}:</span>
82
+ <div id="sha1-fingerprint" class="ics-fingerprint"></div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <div class="ics-card" id="card-algorithm">
88
+ <div class="ics-card-header">
89
+ <Icon name="mdi:file-key" class="ics-card-icon" />
90
+ <h4>{t.cardTechnicalTitle}</h4>
91
+ </div>
92
+ <div class="ics-card-content">
93
+ <div class="ics-data-item">
94
+ <span class="ics-data-label">{t.labelSignatureAlgorithm}:</span>
95
+ <span id="sig-algo" class="ics-data-value"></span>
96
+ </div>
97
+ <div class="ics-data-item">
98
+ <span class="ics-data-label">{t.labelVersion}:</span>
99
+ <span id="cert-version" class="ics-data-value"></span>
100
+ </div>
101
+ <div class="ics-data-item">
102
+ <span class="ics-data-label">{t.labelSerialNumber}:</span>
103
+ <span id="serial-number" class="ics-data-value"></span>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <script is:inline src="https://cdn.jsdelivr.net/npm/node-forge@1.3.1/dist/forge.min.js"></script>
112
+
113
+ <script>
114
+ const dropZone = document.getElementById('drop-zone') as HTMLDivElement;
115
+ const fileInput = document.getElementById('cert-file') as HTMLInputElement;
116
+ const results = document.getElementById('results') as HTMLDivElement;
117
+ const errorMsg = document.getElementById('error-message') as HTMLDivElement;
118
+
119
+ fileInput.addEventListener('change', (e) => {
120
+ const file = (e.target as HTMLInputElement).files?.[0];
121
+ if (file) handleFile(file);
122
+ });
123
+
124
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach((eventName) => {
125
+ dropZone.addEventListener(eventName, (e) => {
126
+ e.preventDefault();
127
+ e.stopPropagation();
128
+ });
129
+ });
130
+
131
+ dropZone.addEventListener('dragover', () => dropZone.classList.add('ics-drag-active'));
132
+ dropZone.addEventListener('dragleave', () => dropZone.classList.remove('ics-drag-active'));
133
+ dropZone.addEventListener('drop', (e) => {
134
+ dropZone.classList.remove('ics-drag-active');
135
+ const file = e.dataTransfer?.files[0];
136
+ if (file) handleFile(file);
137
+ });
138
+
139
+ function isPemFormat(content: string): boolean {
140
+ return content.trim().includes('-----BEGIN CERTIFICATE-----');
141
+ }
142
+
143
+ function parsePemCertificate(pki: { certificateFromPem: (pem: string) => unknown }, content: string): unknown {
144
+ const pemMatch = content.trim().match(
145
+ /-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/
146
+ );
147
+ if (!pemMatch) throw new Error('Formato PEM malformado.');
148
+ return pki.certificateFromPem(pemMatch[0]);
149
+ }
150
+
151
+ function parseDerCertificate(
152
+ forge: Record<string, unknown>,
153
+ pki: { certificateFromAsn1: (asn1: unknown) => unknown },
154
+ bytes: Uint8Array
155
+ ): unknown {
156
+ const util = forge.util as { createBuffer: (data: number[]) => unknown };
157
+ const asn1 = (forge.asn1 as { fromDer: (buffer: unknown) => unknown }).fromDer(
158
+ util.createBuffer(Array.from(bytes))
159
+ );
160
+ return pki.certificateFromAsn1(asn1);
161
+ }
162
+
163
+ async function handleFile(file: File): Promise<void> {
164
+ errorMsg.style.display = 'none';
165
+ results.classList.remove('ics-visible');
166
+
167
+ try {
168
+ const buffer = await file.arrayBuffer();
169
+ const bytes = new Uint8Array(buffer);
170
+ const content = new TextDecoder().decode(bytes);
171
+
172
+ const forge = (window as Record<string, unknown>).forge;
173
+ if (!forge) throw new Error('La librería criptográfica no se ha cargado.');
174
+
175
+ const pki = forge.pki as { certificateFromPem: (pem: string) => unknown; certificateFromAsn1: (asn1: unknown) => unknown };
176
+ let cert: unknown;
177
+
178
+ if (isPemFormat(content)) {
179
+ cert = parsePemCertificate(pki, content);
180
+ } else {
181
+ cert = parseDerCertificate(forge, pki, bytes);
182
+ }
183
+
184
+ if (!cert) throw new Error('Información de certificado no válida.');
185
+
186
+ await displayCertInfo(cert as ForgeCert, buffer);
187
+ } catch (err: unknown) {
188
+ console.error('Error:', err);
189
+ const message = err instanceof Error ? err.message : 'Archivo no válido.';
190
+ errorMsg.textContent = `Error: ${message}`;
191
+ errorMsg.style.display = 'block';
192
+ }
193
+ }
194
+
195
+ interface CertAttribute {
196
+ shortName: string;
197
+ name: string;
198
+ value: string;
199
+ }
200
+
201
+ interface ForgeCert {
202
+ validity: {
203
+ notBefore: Date;
204
+ notAfter: Date;
205
+ };
206
+ subject: {
207
+ attributes: CertAttribute[];
208
+ };
209
+ issuer: {
210
+ attributes: CertAttribute[];
211
+ };
212
+ signatureOid: string;
213
+ version: number;
214
+ serialNumber: string;
215
+ }
216
+
217
+ function getUIData(): Record<string, string> {
218
+ return JSON.parse(document.getElementById('ics-root')?.dataset.i18n ?? '{}');
219
+ }
220
+
221
+ function bufToHex(buf: ArrayBuffer): string {
222
+ return Array.from(new Uint8Array(buf))
223
+ .map((b) => b.toString(16).padStart(2, '0').toUpperCase())
224
+ .join(':');
225
+ }
226
+
227
+ const FIELD_LABELS: Record<string, string> = {
228
+ CN: 'labelCommonName',
229
+ O: 'labelOrganization',
230
+ OU: 'labelOrgUnit',
231
+ L: 'labelLocality',
232
+ ST: 'labelState',
233
+ C: 'labelCountry',
234
+ E: 'labelEmail',
235
+ };
236
+
237
+ function getFieldLabel(uiData: Record<string, string>, shortName: string, name: string): string {
238
+ const key = FIELD_LABELS[shortName];
239
+ if (key && uiData[key]) return uiData[key];
240
+ if (name) return name;
241
+ return shortName || 'Unknown';
242
+ }
243
+
244
+ function renderDNFields(attributes: CertAttribute[]): string {
245
+ const uiData = getUIData();
246
+ return attributes
247
+ .map((attr) => {
248
+ const label = getFieldLabel(uiData, attr.shortName, attr.name);
249
+ return `
250
+ <div class="ics-data-item">
251
+ <span class="ics-data-label">${label}:</span>
252
+ <span class="ics-data-value">${attr.value}</span>
253
+ </div>
254
+ `;
255
+ })
256
+ .join('');
257
+ }
258
+
259
+ function updateValidityStatus(isValid: boolean): void {
260
+ const validezEl = document.getElementById('validez-status');
261
+ const uiData = getUIData();
262
+ if (validezEl) {
263
+ const statusClass = isValid ? 'ics-status-valid' : 'ics-status-expired';
264
+ const statusText = isValid ? uiData.statusValid || 'Válido' : uiData.statusExpired || 'No válido';
265
+ const statusSymbol = isValid ? '[+]' : '[-]';
266
+ validezEl.innerHTML = `<span class="ics-status-badge ${statusClass}"><span class="ics-status-symbol">${statusSymbol}</span>&nbsp;${statusText}</span>`;
267
+ }
268
+ }
269
+
270
+ function updateDateFields(notBefore: Date, notAfter: Date): void {
271
+ const expiryEl = document.getElementById('expiry-date');
272
+ const issueEl = document.getElementById('issue-date');
273
+ if (expiryEl) expiryEl.textContent = notAfter.toLocaleString();
274
+ if (issueEl) issueEl.textContent = notBefore.toLocaleString();
275
+ }
276
+
277
+ function updateDNFields(cert: ForgeCert): void {
278
+ const subjectDetails = document.getElementById('subject-details');
279
+ const issuerDetails = document.getElementById('issuer-details');
280
+ if (subjectDetails) subjectDetails.innerHTML = renderDNFields(cert.subject.attributes);
281
+ if (issuerDetails) issuerDetails.innerHTML = renderDNFields(cert.issuer.attributes);
282
+ }
283
+
284
+ async function updateFingerprints(rawBuffer: ArrayBuffer): Promise<void> {
285
+ const sha1Buf = await crypto.subtle.digest('SHA-1', rawBuffer);
286
+ const sha256Buf = await crypto.subtle.digest('SHA-256', rawBuffer);
287
+ const sha1El = document.getElementById('sha1-fingerprint');
288
+ const sha256El = document.getElementById('sha256-fingerprint');
289
+ if (sha1El) sha1El.textContent = bufToHex(sha1Buf);
290
+ if (sha256El) sha256El.textContent = bufToHex(sha256Buf);
291
+ }
292
+
293
+ function updateTechnicalDetails(cert: ForgeCert): void {
294
+ const sigEl = document.getElementById('sig-algo');
295
+ const verEl = document.getElementById('cert-version');
296
+ const serialEl = document.getElementById('serial-number');
297
+ const forge = (window as Record<string, unknown>).forge as { pki: { oids: Record<string, string> } };
298
+ if (sigEl && forge)
299
+ sigEl.textContent = forge.pki.oids[cert.signatureOid] || cert.signatureOid;
300
+ if (verEl) verEl.textContent = (cert.version + 1).toString();
301
+ if (serialEl) serialEl.textContent = cert.serialNumber;
302
+ }
303
+
304
+ async function displayCertInfo(cert: ForgeCert, rawBuffer: ArrayBuffer): Promise<void> {
305
+ const now = new Date();
306
+ const notBefore = cert.validity.notBefore;
307
+ const notAfter = cert.validity.notAfter;
308
+ const isValid = now >= notBefore && now <= notAfter;
309
+
310
+ updateValidityStatus(isValid);
311
+ updateDateFields(notBefore, notAfter);
312
+ updateDNFields(cert);
313
+ await updateFingerprints(rawBuffer);
314
+ updateTechnicalDetails(cert);
315
+
316
+ results.classList.add('ics-visible');
317
+ }
318
+ </script>
319
+
320
+ <style>
321
+ .ics-root {
322
+ width: 100%;
323
+ }
324
+
325
+ .ics-container {
326
+ display: flex;
327
+ flex-direction: column;
328
+ gap: 2rem;
329
+ width: 100%;
330
+ }
331
+
332
+ .ics-drop-zone {
333
+ border: 2px dashed rgba(16, 185, 129, 0.3);
334
+ border-radius: 1.5rem;
335
+ padding: 4rem 2rem;
336
+ text-align: center;
337
+ background: rgba(255, 255, 255, 0.05);
338
+ backdrop-filter: blur(10px);
339
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
340
+ cursor: pointer;
341
+ position: relative;
342
+ overflow: hidden;
343
+ }
344
+
345
+ :global(.theme-dark) .ics-drop-zone {
346
+ background: rgba(255, 255, 255, 0.03);
347
+ }
348
+
349
+ .ics-drop-zone:hover,
350
+ .ics-drop-zone.ics-drag-active {
351
+ border-color: var(--ics-theme-color, #10b981);
352
+ background: rgba(16, 185, 129, 0.05);
353
+ transform: translateY(-4px);
354
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
355
+ }
356
+
357
+ .ics-drop-zone-content {
358
+ display: flex;
359
+ flex-direction: column;
360
+ align-items: center;
361
+ gap: 1rem;
362
+ pointer-events: none;
363
+ }
364
+
365
+ .ics-icon-wrapper {
366
+ width: 80px;
367
+ height: 80px;
368
+ background: linear-gradient(135deg, rgba(16, 185, 129, 0.2), rgba(16, 185, 129, 0.05));
369
+ border-radius: 50%;
370
+ display: flex;
371
+ align-items: center;
372
+ justify-content: center;
373
+ font-size: 2.5rem;
374
+ color: var(--ics-theme-color, #10b981);
375
+ margin-bottom: 1rem;
376
+ box-shadow: 0 0 20px rgba(16, 185, 129, 0.2);
377
+ }
378
+
379
+ .ics-certificate-icon {
380
+ width: 2.5rem;
381
+ height: 2.5rem;
382
+ }
383
+
384
+ .ics-drop-zone-title {
385
+ font-size: 1.5rem;
386
+ font-weight: 700;
387
+ color: rgb(30, 41, 59);
388
+ margin: 0;
389
+ }
390
+
391
+ :global(.theme-dark) .ics-drop-zone-title {
392
+ color: rgb(226, 232, 240);
393
+ }
394
+
395
+ .ics-drop-zone-text {
396
+ color: rgb(100, 116, 139);
397
+ margin: 0;
398
+ }
399
+
400
+ :global(.theme-dark) .ics-drop-zone-text {
401
+ color: rgb(148, 163, 184);
402
+ }
403
+
404
+ .ics-drop-zone-subtext {
405
+ color: rgb(148, 163, 184);
406
+ margin: 0;
407
+ }
408
+
409
+ :global(.theme-dark) .ics-drop-zone-subtext {
410
+ color: rgb(100, 116, 139);
411
+ }
412
+
413
+ .ics-file-input {
414
+ position: absolute;
415
+ inset: 0;
416
+ opacity: 0;
417
+ cursor: pointer;
418
+ }
419
+
420
+ .ics-results-grid {
421
+ display: none;
422
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
423
+ gap: 1.5rem;
424
+ opacity: 0;
425
+ transform: translateY(20px);
426
+ transition: all 0.5s ease;
427
+ }
428
+
429
+ .ics-results-grid.ics-visible {
430
+ display: grid;
431
+ opacity: 1;
432
+ transform: translateY(0);
433
+ }
434
+
435
+ .ics-card {
436
+ background: rgba(255, 255, 255, 0.5);
437
+ border: 1px solid rgba(0, 0, 0, 0.05);
438
+ border-radius: 1.25rem;
439
+ padding: 1.5rem;
440
+ backdrop-filter: blur(10px);
441
+ display: flex;
442
+ flex-direction: column;
443
+ gap: 1rem;
444
+ transition: transform 0.3s ease, background 0.3s ease;
445
+ }
446
+
447
+ :global(.theme-dark) .ics-card {
448
+ background: rgba(255, 255, 255, 0.03);
449
+ border: 1px solid rgba(255, 255, 255, 0.08);
450
+ }
451
+
452
+ .ics-card:hover {
453
+ transform: translateY(-2px);
454
+ border-color: rgba(255, 255, 255, 0.15);
455
+ }
456
+
457
+ .ics-card-header {
458
+ display: flex;
459
+ align-items: center;
460
+ gap: 0.75rem;
461
+ padding-bottom: 0.75rem;
462
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
463
+ }
464
+
465
+ .ics-card-icon {
466
+ font-size: 1.25rem;
467
+ color: var(--ics-theme-color, #10b981);
468
+ width: 1.25rem;
469
+ height: 1.25rem;
470
+ }
471
+
472
+ .ics-card-header h4 {
473
+ margin: 0;
474
+ font-size: 0.9rem;
475
+ font-weight: 700;
476
+ text-transform: uppercase;
477
+ letter-spacing: 0.05em;
478
+ color: rgb(30, 41, 59);
479
+ opacity: 0.8;
480
+ }
481
+
482
+ :global(.theme-dark) .ics-card-header h4 {
483
+ color: rgb(226, 232, 240);
484
+ }
485
+
486
+ .ics-card-content {
487
+ overflow-wrap: break-word;
488
+ }
489
+
490
+ .ics-data-item {
491
+ display: flex;
492
+ flex-direction: column;
493
+ gap: 0.25rem;
494
+ margin-bottom: 1rem;
495
+ }
496
+
497
+ .ics-data-label {
498
+ font-size: 0.75rem;
499
+ color: rgb(71, 85, 105);
500
+ font-weight: 600;
501
+ text-transform: uppercase;
502
+ letter-spacing: 0.025em;
503
+ }
504
+
505
+ :global(.theme-dark) .ics-data-label {
506
+ color: rgb(148, 163, 184);
507
+ }
508
+
509
+ .ics-data-value {
510
+ font-size: 1rem;
511
+ color: rgb(15, 23, 42);
512
+ line-height: 1.5;
513
+ word-break: break-all;
514
+ }
515
+
516
+ :global(.theme-dark) .ics-data-value {
517
+ color: rgb(226, 232, 240);
518
+ }
519
+
520
+ .ics-status-badge {
521
+ display: inline-flex;
522
+ align-items: center;
523
+ padding: 0.25rem 0.75rem;
524
+ border-radius: 2rem;
525
+ font-size: 0.75rem;
526
+ font-weight: 600;
527
+ }
528
+
529
+ .ics-status-symbol {
530
+ font-weight: bold;
531
+ }
532
+
533
+ .ics-status-valid {
534
+ background: rgba(34, 197, 94, 0.1);
535
+ color: #22c55e;
536
+ border: 1px solid rgba(34, 197, 94, 0.2);
537
+ }
538
+
539
+ .ics-status-expired {
540
+ background: rgba(239, 68, 68, 0.1);
541
+ color: #ef4444;
542
+ border: 1px solid rgba(239, 68, 68, 0.2);
543
+ }
544
+
545
+ .ics-fingerprint {
546
+ background: rgba(0, 0, 0, 0.05);
547
+ padding: 0.75rem;
548
+ border-radius: 0.75rem;
549
+ font-size: 0.85rem;
550
+ color: rgb(30, 41, 59);
551
+ word-break: break-all;
552
+ border: 1px solid rgba(0, 0, 0, 0.05);
553
+ }
554
+
555
+ :global(.theme-dark) .ics-fingerprint {
556
+ background: rgba(0, 0, 0, 0.2);
557
+ color: rgb(212, 212, 212);
558
+ border: 1px solid rgba(255, 255, 255, 0.03);
559
+ }
560
+
561
+ .ics-error-message {
562
+ background: rgba(239, 68, 68, 0.1);
563
+ border: 1px solid rgba(239, 68, 68, 0.2);
564
+ color: #ef4444;
565
+ padding: 1rem;
566
+ border-radius: 1rem;
567
+ text-align: center;
568
+ display: none;
569
+ }
570
+
571
+ @media (max-width: 768px) {
572
+ .ics-drop-zone {
573
+ padding: 3rem 1.5rem;
574
+ }
575
+
576
+ .ics-results-grid {
577
+ grid-template-columns: 1fr;
578
+ }
579
+ }
580
+ </style>