swl-ses 3.3.2

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 (177) hide show
  1. package/CLAUDE.md +425 -0
  2. package/_userland/agentes/.gitkeep +0 -0
  3. package/_userland/habilidades/.gitkeep +0 -0
  4. package/agentes/accesibilidad-wcag-swl.md +683 -0
  5. package/agentes/arquitecto-swl.md +210 -0
  6. package/agentes/auto-evolucion-swl.md +408 -0
  7. package/agentes/backend-api-swl.md +442 -0
  8. package/agentes/backend-node-swl.md +439 -0
  9. package/agentes/backend-python-swl.md +469 -0
  10. package/agentes/backend-workers-swl.md +444 -0
  11. package/agentes/cloud-infra-swl.md +466 -0
  12. package/agentes/consolidador-swl.md +487 -0
  13. package/agentes/datos-swl.md +568 -0
  14. package/agentes/depurador-swl.md +301 -0
  15. package/agentes/devops-ci-swl.md +352 -0
  16. package/agentes/disenador-ui-swl.md +546 -0
  17. package/agentes/documentador-swl.md +323 -0
  18. package/agentes/frontend-angular-swl.md +603 -0
  19. package/agentes/frontend-css-swl.md +700 -0
  20. package/agentes/frontend-react-swl.md +672 -0
  21. package/agentes/frontend-swl.md +483 -0
  22. package/agentes/frontend-tailwind-swl.md +808 -0
  23. package/agentes/implementador-swl.md +235 -0
  24. package/agentes/investigador-swl.md +274 -0
  25. package/agentes/investigador-ux-swl.md +482 -0
  26. package/agentes/migrador-swl.md +389 -0
  27. package/agentes/mobile-android-swl.md +473 -0
  28. package/agentes/mobile-cross-swl.md +501 -0
  29. package/agentes/mobile-ios-swl.md +464 -0
  30. package/agentes/notificador-swl.md +886 -0
  31. package/agentes/observabilidad-swl.md +408 -0
  32. package/agentes/orquestador-swl.md +490 -0
  33. package/agentes/planificador-swl.md +222 -0
  34. package/agentes/producto-prd-swl.md +565 -0
  35. package/agentes/release-manager-swl.md +545 -0
  36. package/agentes/rendimiento-swl.md +691 -0
  37. package/agentes/revisor-codigo-swl.md +254 -0
  38. package/agentes/revisor-seguridad-swl.md +316 -0
  39. package/agentes/tdd-qa-swl.md +323 -0
  40. package/agentes/ux-disenador-swl.md +498 -0
  41. package/bin/swl-ses.js +119 -0
  42. package/comandos/swl/actualizar.md +117 -0
  43. package/comandos/swl/aprender.md +348 -0
  44. package/comandos/swl/auditar-deps.md +390 -0
  45. package/comandos/swl/autoresearch.md +346 -0
  46. package/comandos/swl/checkpoint.md +296 -0
  47. package/comandos/swl/compactar.md +283 -0
  48. package/comandos/swl/crear-skill.md +609 -0
  49. package/comandos/swl/discutir-fase.md +230 -0
  50. package/comandos/swl/ejecutar-fase.md +302 -0
  51. package/comandos/swl/evolucionar.md +377 -0
  52. package/comandos/swl/instalar.md +220 -0
  53. package/comandos/swl/mapear-codebase.md +205 -0
  54. package/comandos/swl/nuevo-proyecto.md +154 -0
  55. package/comandos/swl/planear-fase.md +221 -0
  56. package/comandos/swl/release.md +405 -0
  57. package/comandos/swl/salud.md +382 -0
  58. package/comandos/swl/verificar.md +292 -0
  59. package/habilidades/accesibilidad-a11y/SKILL.md +584 -0
  60. package/habilidades/angular-avanzado/SKILL.md +491 -0
  61. package/habilidades/angular-moderno/SKILL.md +326 -0
  62. package/habilidades/api-rest-diseno/SKILL.md +302 -0
  63. package/habilidades/api-rest-diseno/recursos/openapi-template.yaml +506 -0
  64. package/habilidades/aprendizaje-continuo/SKILL.md +369 -0
  65. package/habilidades/async-python/SKILL.md +474 -0
  66. package/habilidades/auth-patrones/SKILL.md +488 -0
  67. package/habilidades/auto-evolucion-protocolo/SKILL.md +376 -0
  68. package/habilidades/autoresearch/SKILL.md +248 -0
  69. package/habilidades/autoresearch/recursos/checklist-template.md +191 -0
  70. package/habilidades/autoresearch/scripts/calcular-score.js +88 -0
  71. package/habilidades/checklist-calidad/SKILL.md +247 -0
  72. package/habilidades/checklist-calidad/recursos/quality-report-template.md +148 -0
  73. package/habilidades/checklist-seguridad/SKILL.md +224 -0
  74. package/habilidades/checkpoints-verificacion/SKILL.md +309 -0
  75. package/habilidades/checkpoints-verificacion/recursos/checkpoint-templates.md +360 -0
  76. package/habilidades/ci-cd-pipelines/SKILL.md +583 -0
  77. package/habilidades/ci-cd-pipelines/recursos/github-actions-template.yaml +403 -0
  78. package/habilidades/cloud-aws/SKILL.md +497 -0
  79. package/habilidades/compactacion-contexto/SKILL.md +201 -0
  80. package/habilidades/contenedores-docker/SKILL.md +453 -0
  81. package/habilidades/contenedores-docker/recursos/dockerfile-template.dockerfile +160 -0
  82. package/habilidades/css-moderno/SKILL.md +463 -0
  83. package/habilidades/datos-etl/SKILL.md +486 -0
  84. package/habilidades/dependencias-auditoria/SKILL.md +293 -0
  85. package/habilidades/deprecacion-migracion/SKILL.md +485 -0
  86. package/habilidades/design-tokens/SKILL.md +519 -0
  87. package/habilidades/discutir-fase/SKILL.md +167 -0
  88. package/habilidades/diseno-responsivo/SKILL.md +326 -0
  89. package/habilidades/django-experto/SKILL.md +395 -0
  90. package/habilidades/doc-sync/SKILL.md +259 -0
  91. package/habilidades/ejecutar-fase/SKILL.md +199 -0
  92. package/habilidades/estructura-proyecto-claude/SKILL.md +459 -0
  93. package/habilidades/estructura-proyecto-claude/recursos/claude-md-template.md +261 -0
  94. package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +213 -0
  95. package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +77 -0
  96. package/habilidades/estructura-proyecto-claude/recursos/variantes-por-stack.md +177 -0
  97. package/habilidades/event-driven/SKILL.md +580 -0
  98. package/habilidades/extractor-de-aprendizajes/SKILL.md +234 -0
  99. package/habilidades/fastapi-experto/SKILL.md +368 -0
  100. package/habilidades/frontend-avanzado/SKILL.md +555 -0
  101. package/habilidades/git-worktrees-paralelo/SKILL.md +246 -0
  102. package/habilidades/iam-secretos/SKILL.md +511 -0
  103. package/habilidades/instalar-sistema/SKILL.md +140 -0
  104. package/habilidades/kubernetes-orquestacion/SKILL.md +549 -0
  105. package/habilidades/manejo-errores/SKILL.md +512 -0
  106. package/habilidades/mapear-codebase/SKILL.md +199 -0
  107. package/habilidades/microservicios/SKILL.md +473 -0
  108. package/habilidades/mobile-flutter/SKILL.md +566 -0
  109. package/habilidades/mobile-react-native/SKILL.md +493 -0
  110. package/habilidades/monitoring-alertas/SKILL.md +447 -0
  111. package/habilidades/node-experto/SKILL.md +521 -0
  112. package/habilidades/notificaciones-multicanal/SKILL.md +448 -0
  113. package/habilidades/notificaciones-multicanal/recursos/config-template.json +115 -0
  114. package/habilidades/nuevo-proyecto/SKILL.md +183 -0
  115. package/habilidades/patrones-python/SKILL.md +381 -0
  116. package/habilidades/performance-baseline/SKILL.md +243 -0
  117. package/habilidades/planear-fase/SKILL.md +184 -0
  118. package/habilidades/postgresql-experto/SKILL.md +379 -0
  119. package/habilidades/react-experto/SKILL.md +434 -0
  120. package/habilidades/react-optimizacion/SKILL.md +328 -0
  121. package/habilidades/release-semver/SKILL.md +226 -0
  122. package/habilidades/release-semver/scripts/generar-changelog.sh +238 -0
  123. package/habilidades/sql-optimizacion/SKILL.md +314 -0
  124. package/habilidades/tailwind-experto/SKILL.md +412 -0
  125. package/habilidades/tdd-workflow/SKILL.md +267 -0
  126. package/habilidades/testing-python/SKILL.md +350 -0
  127. package/habilidades/threat-model-lite/SKILL.md +218 -0
  128. package/habilidades/typescript-avanzado/SKILL.md +454 -0
  129. package/habilidades/ux-diseno/SKILL.md +488 -0
  130. package/habilidades/validacion-ci-sistema/SKILL.md +543 -0
  131. package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +286 -0
  132. package/habilidades/verificar-trabajo/SKILL.md +208 -0
  133. package/habilidades/wireframes-flujos/SKILL.md +396 -0
  134. package/habilidades/workflow-claude-code/SKILL.md +359 -0
  135. package/hooks/calidad-pre-commit.js +578 -0
  136. package/hooks/escaneo-secretos.js +302 -0
  137. package/hooks/extraccion-aprendizajes.js +550 -0
  138. package/hooks/linea-estado.js +249 -0
  139. package/hooks/monitor-contexto.js +230 -0
  140. package/hooks/proteccion-rutas.js +249 -0
  141. package/manifiestos/hooks-config.json +41 -0
  142. package/manifiestos/modulos.json +318 -0
  143. package/manifiestos/perfiles.json +189 -0
  144. package/package.json +45 -0
  145. package/plantillas/PROJECT.md +122 -0
  146. package/plantillas/REQUIREMENTS.md +132 -0
  147. package/plantillas/ROADMAP.md +143 -0
  148. package/plantillas/STATE.md +109 -0
  149. package/plantillas/research/ARCHITECTURE.md +220 -0
  150. package/plantillas/research/FEATURES.md +175 -0
  151. package/plantillas/research/PITFALLS.md +299 -0
  152. package/plantillas/research/STACK.md +233 -0
  153. package/plantillas/research/SUMMARY.md +165 -0
  154. package/plugin.json +144 -0
  155. package/reglas/accesibilidad.md +269 -0
  156. package/reglas/api-diseno.md +400 -0
  157. package/reglas/arquitectura.md +183 -0
  158. package/reglas/cloud-infra.md +247 -0
  159. package/reglas/docs.md +245 -0
  160. package/reglas/estilo-codigo.md +179 -0
  161. package/reglas/git-workflow.md +186 -0
  162. package/reglas/performance.md +195 -0
  163. package/reglas/pruebas.md +159 -0
  164. package/reglas/seguridad.md +151 -0
  165. package/reglas/skills-estandar.md +473 -0
  166. package/scripts/actualizar.js +51 -0
  167. package/scripts/desinstalar.js +86 -0
  168. package/scripts/doctor.js +222 -0
  169. package/scripts/inicializar.js +89 -0
  170. package/scripts/instalador.js +333 -0
  171. package/scripts/lib/detectar-runtime.js +177 -0
  172. package/scripts/lib/estado.js +112 -0
  173. package/scripts/lib/hooks-settings.js +283 -0
  174. package/scripts/lib/manifiestos.js +138 -0
  175. package/scripts/lib/seguridad.js +160 -0
  176. package/scripts/publicar.js +209 -0
  177. package/scripts/validar.js +120 -0
@@ -0,0 +1,555 @@
1
+ ---
2
+ name: frontend-avanzado
3
+ description: >
4
+ Frontend avanzado: Web Workers, Service Workers, PWA, WebSockets, SSE, IndexedDB,
5
+ Web Components, Shadow DOM, CSS Container Queries, CSS Layers, View Transitions API.
6
+ Patrones de optimización de rendimiento.
7
+ ---
8
+
9
+ # Frontend Avanzado — APIs Nativas y Rendimiento
10
+
11
+ ## Por qué importan las APIs nativas del navegador
12
+
13
+ Los frameworks abstraen muchas APIs del navegador, pero conocerlas directamente permite:
14
+ - Descargar trabajo pesado del hilo principal (Web Workers).
15
+ - Ofrecer experiencias offline (Service Workers + IndexedDB).
16
+ - Actualización en tiempo real sin polling (WebSockets/SSE).
17
+ - Estilos que responden al contenedor, no sólo a la pantalla (Container Queries).
18
+
19
+ ---
20
+
21
+ ## Web Workers — Cómputo en Hilo Separado
22
+
23
+ ### MAL: cálculo pesado en el hilo principal
24
+
25
+ ```typescript
26
+ // MAL — bloquea el hilo principal, congela la UI
27
+ function calcularEstadisticas(datos: number[]): EstadisticasResultado {
28
+ // Operación O(n²) — bloquea la UI durante segundos
29
+ const correlaciones = datos.flatMap((a, i) =>
30
+ datos.slice(i + 1).map(b => calcularCorrelacion(a, b))
31
+ );
32
+ return { correlaciones, promedio: datos.reduce((s, n) => s + n, 0) / datos.length };
33
+ }
34
+
35
+ // En el componente — BLOQUEA renderizado
36
+ const stats = calcularEstadisticas(misMuchosDatos); // ❌ Freeze de UI
37
+ ```
38
+
39
+ ### BIEN: Worker con comunicación tipada
40
+
41
+ ```typescript
42
+ // estadisticas.worker.ts
43
+ /// <reference lib="webworker" />
44
+
45
+ interface MensajeEntrada {
46
+ tipo: "CALCULAR";
47
+ datos: number[];
48
+ id: string;
49
+ }
50
+
51
+ interface MensajeSalida {
52
+ tipo: "RESULTADO" | "ERROR" | "PROGRESO";
53
+ id: string;
54
+ payload: EstadisticasResultado | string | number;
55
+ }
56
+
57
+ self.addEventListener("message", (event: MessageEvent<MensajeEntrada>) => {
58
+ const { tipo, datos, id } = event.data;
59
+
60
+ if (tipo === "CALCULAR") {
61
+ try {
62
+ // Reportar progreso cada 10%
63
+ const total = datos.length;
64
+ const correlaciones: number[] = [];
65
+
66
+ for (let i = 0; i < total; i++) {
67
+ for (let j = i + 1; j < total; j++) {
68
+ correlaciones.push(calcularCorrelacion(datos[i], datos[j]));
69
+ }
70
+ if (i % Math.floor(total / 10) === 0) {
71
+ self.postMessage({
72
+ tipo: "PROGRESO",
73
+ id,
74
+ payload: Math.round((i / total) * 100),
75
+ } satisfies MensajeSalida);
76
+ }
77
+ }
78
+
79
+ self.postMessage({
80
+ tipo: "RESULTADO",
81
+ id,
82
+ payload: { correlaciones, promedio: datos.reduce((s, n) => s + n, 0) / total },
83
+ } satisfies MensajeSalida);
84
+ } catch (e) {
85
+ self.postMessage({ tipo: "ERROR", id, payload: String(e) } satisfies MensajeSalida);
86
+ }
87
+ }
88
+ });
89
+
90
+ // servicio-worker.service.ts (Angular)
91
+ @Injectable({ providedIn: "root" })
92
+ export class ServicioEstadisticas {
93
+ private worker = new Worker(
94
+ new URL("../workers/estadisticas.worker", import.meta.url),
95
+ { type: "module" }
96
+ );
97
+
98
+ calcular(datos: number[]): Observable<EstadisticasResultado> {
99
+ const id = crypto.randomUUID();
100
+
101
+ return new Observable(observer => {
102
+ const manejador = (event: MessageEvent<MensajeSalida>) => {
103
+ if (event.data.id !== id) return;
104
+
105
+ switch (event.data.tipo) {
106
+ case "PROGRESO":
107
+ // Emitir progreso si se requiere
108
+ break;
109
+ case "RESULTADO":
110
+ observer.next(event.data.payload as EstadisticasResultado);
111
+ observer.complete();
112
+ this.worker.removeEventListener("message", manejador);
113
+ break;
114
+ case "ERROR":
115
+ observer.error(new Error(event.data.payload as string));
116
+ this.worker.removeEventListener("message", manejador);
117
+ break;
118
+ }
119
+ };
120
+
121
+ this.worker.addEventListener("message", manejador);
122
+ this.worker.postMessage({ tipo: "CALCULAR", datos, id });
123
+
124
+ return () => this.worker.removeEventListener("message", manejador);
125
+ });
126
+ }
127
+ }
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Service Workers — Estrategias de Caché
133
+
134
+ ```typescript
135
+ // service-worker.ts
136
+ const CACHE_VERSION = "v3";
137
+ const CACHE_ESTATICO = `estatico-${CACHE_VERSION}`;
138
+ const CACHE_DINAMICO = `dinamico-${CACHE_VERSION}`;
139
+
140
+ const RECURSOS_PRECACHEADOS = [
141
+ "/",
142
+ "/manifest.json",
143
+ "/offline.html",
144
+ ];
145
+
146
+ // Instalación — precachear recursos estáticos
147
+ self.addEventListener("install", (event: ExtendableEvent) => {
148
+ event.waitUntil(
149
+ caches.open(CACHE_ESTATICO).then(cache => cache.addAll(RECURSOS_PRECACHEADOS))
150
+ );
151
+ (self as ServiceWorkerGlobalScope).skipWaiting();
152
+ });
153
+
154
+ // Activación — limpiar cachés viejos
155
+ self.addEventListener("activate", (event: ExtendableEvent) => {
156
+ event.waitUntil(
157
+ caches.keys().then(claves =>
158
+ Promise.all(
159
+ claves
160
+ .filter(c => c !== CACHE_ESTATICO && c !== CACHE_DINAMICO)
161
+ .map(c => caches.delete(c))
162
+ )
163
+ )
164
+ );
165
+ (self as ServiceWorkerGlobalScope).clients.claim();
166
+ });
167
+
168
+ // Estrategias de fetch
169
+ self.addEventListener("fetch", (event: FetchEvent) => {
170
+ const { request } = event;
171
+ const url = new URL(request.url);
172
+
173
+ // API calls — Network First (con fallback a caché)
174
+ if (url.pathname.startsWith("/api/")) {
175
+ event.respondWith(networkFirst(request));
176
+ return;
177
+ }
178
+
179
+ // Recursos estáticos — Cache First
180
+ if (request.destination === "image" || url.pathname.endsWith(".js")) {
181
+ event.respondWith(cacheFirst(request));
182
+ return;
183
+ }
184
+
185
+ // HTML — Stale While Revalidate
186
+ event.respondWith(staleWhileRevalidate(request));
187
+ });
188
+
189
+ async function networkFirst(request: Request): Promise<Response> {
190
+ try {
191
+ const respuesta = await fetch(request.clone());
192
+ const cache = await caches.open(CACHE_DINAMICO);
193
+ cache.put(request, respuesta.clone());
194
+ return respuesta;
195
+ } catch {
196
+ const cached = await caches.match(request);
197
+ return cached ?? caches.match("/offline.html") as Promise<Response>;
198
+ }
199
+ }
200
+
201
+ async function cacheFirst(request: Request): Promise<Response> {
202
+ const cached = await caches.match(request);
203
+ if (cached) return cached;
204
+
205
+ const respuesta = await fetch(request);
206
+ const cache = await caches.open(CACHE_DINAMICO);
207
+ cache.put(request, respuesta.clone());
208
+ return respuesta;
209
+ }
210
+
211
+ async function staleWhileRevalidate(request: Request): Promise<Response> {
212
+ const cache = await caches.open(CACHE_DINAMICO);
213
+ const cached = await caches.match(request);
214
+
215
+ const fetchPromise = fetch(request).then(respuesta => {
216
+ cache.put(request, respuesta.clone());
217
+ return respuesta;
218
+ });
219
+
220
+ return cached ?? fetchPromise;
221
+ }
222
+ ```
223
+
224
+ ---
225
+
226
+ ## WebSockets — Conexión Robusta con Reconexión
227
+
228
+ ```typescript
229
+ // websocket.service.ts
230
+ @Injectable({ providedIn: "root" })
231
+ export class WebSocketService {
232
+ private socket: WebSocket | null = null;
233
+ private reintentos = 0;
234
+ private readonly MAX_REINTENTOS = 5;
235
+ private readonly DELAY_BASE_MS = 1000;
236
+
237
+ private readonly _mensajes$ = new Subject<MensajeWS>();
238
+ private readonly _estado$ = new BehaviorSubject<"conectando" | "conectado" | "desconectado">("desconectado");
239
+
240
+ readonly mensajes$ = this._mensajes$.asObservable();
241
+ readonly estado$ = this._estado$.asObservable();
242
+
243
+ conectar(url: string): void {
244
+ this._estado$.next("conectando");
245
+ this.socket = new WebSocket(url);
246
+
247
+ this.socket.onopen = () => {
248
+ this.reintentos = 0;
249
+ this._estado$.next("conectado");
250
+ };
251
+
252
+ this.socket.onmessage = (event: MessageEvent) => {
253
+ try {
254
+ const mensaje = JSON.parse(event.data) as MensajeWS;
255
+ this._mensajes$.next(mensaje);
256
+ } catch {
257
+ console.warn("Mensaje WS no parseado:", event.data);
258
+ }
259
+ };
260
+
261
+ this.socket.onclose = (event: CloseEvent) => {
262
+ this._estado$.next("desconectado");
263
+
264
+ // No reconectar si fue cierre limpio
265
+ if (event.wasClean || this.reintentos >= this.MAX_REINTENTOS) return;
266
+
267
+ const delay = this.DELAY_BASE_MS * 2 ** this.reintentos;
268
+ this.reintentos++;
269
+
270
+ setTimeout(() => this.conectar(url), delay);
271
+ };
272
+
273
+ this.socket.onerror = () => {
274
+ // onclose se llamará después del error
275
+ };
276
+ }
277
+
278
+ enviar<T>(tipo: string, payload: T): void {
279
+ if (this.socket?.readyState !== WebSocket.OPEN) {
280
+ throw new Error("WebSocket no conectado");
281
+ }
282
+ this.socket.send(JSON.stringify({ tipo, payload, timestamp: Date.now() }));
283
+ }
284
+
285
+ desconectar(): void {
286
+ this.reintentos = this.MAX_REINTENTOS; // Prevenir reconexión automática
287
+ this.socket?.close(1000, "Cierre normal");
288
+ }
289
+ }
290
+ ```
291
+
292
+ ---
293
+
294
+ ## Server-Sent Events — Para Flujos Unidireccionales
295
+
296
+ ```typescript
297
+ // sse.service.ts — más simple que WS para flujos del servidor al cliente
298
+ @Injectable({ providedIn: "root" })
299
+ export class SSEService {
300
+
301
+ escuchar<T>(url: string): Observable<T> {
302
+ return new Observable<T>(observer => {
303
+ const fuente = new EventSource(url, { withCredentials: true });
304
+
305
+ fuente.onmessage = (event: MessageEvent) => {
306
+ try {
307
+ observer.next(JSON.parse(event.data) as T);
308
+ } catch {
309
+ observer.error(new Error(`SSE: JSON inválido: ${event.data}`));
310
+ }
311
+ };
312
+
313
+ fuente.onerror = () => {
314
+ observer.error(new Error("Conexión SSE perdida"));
315
+ };
316
+
317
+ // Cleanup al desuscribirse
318
+ return () => fuente.close();
319
+ }).pipe(
320
+ // Reconexión automática con delay
321
+ retryWhen(errores =>
322
+ errores.pipe(
323
+ delayWhen((_, intento) => timer(Math.min(1000 * 2 ** intento, 30000)))
324
+ )
325
+ )
326
+ );
327
+ }
328
+ }
329
+
330
+ // Uso en componente
331
+ export class NotificacionesComponent {
332
+ private sseService = inject(SSEService);
333
+ private destroy$ = new Subject<void>();
334
+
335
+ readonly notificaciones = signal<Notificacion[]>([]);
336
+
337
+ ngOnInit(): void {
338
+ this.sseService
339
+ .escuchar<Notificacion>("/api/notificaciones/stream")
340
+ .pipe(takeUntil(this.destroy$))
341
+ .subscribe(notificacion => {
342
+ this.notificaciones.update(lista => [notificacion, ...lista].slice(0, 50));
343
+ });
344
+ }
345
+
346
+ ngOnDestroy(): void {
347
+ this.destroy$.next();
348
+ this.destroy$.complete();
349
+ }
350
+ }
351
+ ```
352
+
353
+ ---
354
+
355
+ ## IndexedDB — Almacenamiento Estructurado Offline
356
+
357
+ ```typescript
358
+ // idb.service.ts — usando la librería `idb` para API promisificada
359
+ import { openDB, DBSchema, IDBPDatabase } from "idb";
360
+
361
+ interface EsquemaBD extends DBSchema {
362
+ pedidos: {
363
+ key: string;
364
+ value: { id: string; datos: object; sincronizado: boolean; timestamp: number };
365
+ indexes: { "por-sincronizado": boolean };
366
+ };
367
+ cache_respuestas: {
368
+ key: string; // URL
369
+ value: { url: string; datos: unknown; expira: number };
370
+ };
371
+ }
372
+
373
+ @Injectable({ providedIn: "root" })
374
+ export class AlmacenamientoLocalService {
375
+ private db!: IDBPDatabase<EsquemaBD>;
376
+
377
+ async inicializar(): Promise<void> {
378
+ this.db = await openDB<EsquemaBD>("mi-app-db", 2, {
379
+ upgrade(db, versionAnterior) {
380
+ if (versionAnterior < 1) {
381
+ const storePedidos = db.createObjectStore("pedidos", { keyPath: "id" });
382
+ storePedidos.createIndex("por-sincronizado", "sincronizado");
383
+ db.createObjectStore("cache_respuestas", { keyPath: "url" });
384
+ }
385
+ if (versionAnterior < 2) {
386
+ // Migraciones incrementales por versión
387
+ }
388
+ },
389
+ });
390
+ }
391
+
392
+ async guardarPedidoOffline(pedido: object & { id: string }): Promise<void> {
393
+ await this.db.put("pedidos", {
394
+ id: pedido.id,
395
+ datos: pedido,
396
+ sincronizado: false,
397
+ timestamp: Date.now(),
398
+ });
399
+ }
400
+
401
+ async sincronizarPendientes(): Promise<void> {
402
+ const pendientes = await this.db.getAllFromIndex("pedidos", "por-sincronizado", false);
403
+
404
+ for (const pendiente of pendientes) {
405
+ try {
406
+ await fetch("/api/pedidos", {
407
+ method: "POST",
408
+ body: JSON.stringify(pendiente.datos),
409
+ headers: { "Content-Type": "application/json" },
410
+ });
411
+ await this.db.put("pedidos", { ...pendiente, sincronizado: true });
412
+ } catch {
413
+ // Sin red — se reintentará en la próxima sesión
414
+ }
415
+ }
416
+ }
417
+ }
418
+ ```
419
+
420
+ ---
421
+
422
+ ## CSS Container Queries
423
+
424
+ ```css
425
+ /* ANTES: media queries globales — el componente depende del viewport */
426
+ @media (max-width: 768px) {
427
+ .tarjeta-producto { flex-direction: column; }
428
+ }
429
+
430
+ /* AHORA: container queries — el componente responde a su contenedor */
431
+ .contenedor-tarjetas {
432
+ container-type: inline-size;
433
+ container-name: tarjetas;
434
+ }
435
+
436
+ /* El componente es reutilizable en cualquier contexto */
437
+ @container tarjetas (min-width: 400px) {
438
+ .tarjeta-producto {
439
+ display: flex;
440
+ flex-direction: row;
441
+ gap: 1rem;
442
+ }
443
+ }
444
+
445
+ @container tarjetas (max-width: 399px) {
446
+ .tarjeta-producto {
447
+ display: flex;
448
+ flex-direction: column;
449
+ }
450
+ }
451
+
452
+ /* Container queries con unidades de contenedor */
453
+ .titulo-tarjeta {
454
+ font-size: clamp(0.875rem, 4cqi, 1.25rem); /* cqi = % del ancho del contenedor */
455
+ }
456
+ ```
457
+
458
+ ---
459
+
460
+ ## CSS Cascade Layers
461
+
462
+ ```css
463
+ /* Orden de capas: menor a mayor especificidad */
464
+ @layer reset, base, componentes, utilidades, sobreescrituras;
465
+
466
+ @layer reset {
467
+ *, *::before, *::after { box-sizing: border-box; margin: 0; }
468
+ }
469
+
470
+ @layer base {
471
+ body { font-family: system-ui, sans-serif; line-height: 1.5; }
472
+ h1, h2, h3 { line-height: 1.2; }
473
+ }
474
+
475
+ @layer componentes {
476
+ .btn {
477
+ padding: 0.5rem 1rem;
478
+ border-radius: 0.25rem;
479
+ cursor: pointer;
480
+ }
481
+ .btn-primario { background: var(--color-primario); color: white; }
482
+ }
483
+
484
+ @layer utilidades {
485
+ /* Las utilidades siempre ganan sobre componentes */
486
+ .mt-4 { margin-top: 1rem; }
487
+ .hidden { display: none; }
488
+ }
489
+
490
+ @layer sobreescrituras {
491
+ /* Personalizaciones específicas de tema/cliente */
492
+ .btn-primario { border-radius: 9999px; } /* Botones pill para cliente X */
493
+ }
494
+ ```
495
+
496
+ ---
497
+
498
+ ## View Transitions API
499
+
500
+ ```typescript
501
+ // view-transitions.service.ts
502
+ @Injectable({ providedIn: "root" })
503
+ export class TransicionesService {
504
+ private router = inject(Router);
505
+
506
+ inicializarTransiciones(): void {
507
+ if (!("startViewTransition" in document)) return; // Fallback
508
+
509
+ this.router.events
510
+ .pipe(filter(e => e instanceof NavigationStart))
511
+ .subscribe(() => {
512
+ (document as any).startViewTransition(() =>
513
+ // El router navega dentro de la transición
514
+ new Promise<void>(resolve => {
515
+ const sub = this.router.events
516
+ .pipe(filter(e => e instanceof NavigationEnd), take(1))
517
+ .subscribe(() => { sub.unsubscribe(); resolve(); });
518
+ })
519
+ );
520
+ });
521
+ }
522
+ }
523
+ ```
524
+
525
+ ```css
526
+ /* Transición de vista personalizada */
527
+ ::view-transition-old(root) {
528
+ animation: desvanece-sale 0.3s ease-out;
529
+ }
530
+
531
+ ::view-transition-new(root) {
532
+ animation: desvanece-entra 0.3s ease-in;
533
+ }
534
+
535
+ /* Transición específica para elemento */
536
+ .tarjeta-detalle {
537
+ view-transition-name: tarjeta-activa;
538
+ }
539
+
540
+ @keyframes desvanece-sale { to { opacity: 0; transform: translateX(-20px); } }
541
+ @keyframes desvanece-entra { from { opacity: 0; transform: translateX( 20px); } }
542
+ ```
543
+
544
+ ---
545
+
546
+ ## Checklist de Revisión — Frontend Avanzado
547
+
548
+ - [ ] Cálculos que toman >50ms están en un Web Worker, no en el hilo principal.
549
+ - [ ] El Service Worker implementa la estrategia correcta por tipo de recurso (Cache First, Network First, SWR).
550
+ - [ ] IndexedDB tiene esquema versionado con migraciones incrementales.
551
+ - [ ] Las conexiones WebSocket/SSE tienen reconexión automática con backoff exponencial.
552
+ - [ ] Los Container Queries se usan en lugar de media queries para componentes reutilizables.
553
+ - [ ] CSS Layers están definidas con orden explícito (`@layer reset, base, componentes, utilidades`).
554
+ - [ ] View Transitions tiene fallback para navegadores sin soporte.
555
+ - [ ] El Service Worker se actualiza correctamente al publicar nuevas versiones (`skipWaiting` + `clients.claim`).