---
name: frontend-avanzado
description: >
  Frontend avanzado: Web Workers, Service Workers, PWA, WebSockets, SSE, IndexedDB,
  Web Components, Shadow DOM, CSS Container Queries, CSS Layers, View Transitions API.
  Patrones de optimización de rendimiento.
version: "1.1.0"
evolved: true
evolved-from: "1.0.0"
evolved-at: "2026-04-24"
evolved-by: "aprender"
evolved-note: "Sección nueva: CSP 'unsafe-inline' como transitorio aceptable con plan de migración a nonce"
herramientasPermitidas: [Read, Grep]
evolvable: true  # default para skill estandar
exclusiones:
  - "No cargar para componentes UI estándar con frameworks (React, Angular, Vue) sin necesidad de APIs nativas — para implementación de componentes cargar el skill del framework."
  - "No cargar para diseño responsivo (breakpoints, container queries en Tailwind, mobile-first layouts) — para diseño responsivo cargar `diseno-responsivo`."
  - "No cargar para optimización de bundle (tree shaking, code splitting, Vite, Webpack) — para build optimization cargar el skill del bundler del proyecto."
  - "No cargar para notificaciones push del lado cliente (Push API, Notification API) sin Service Worker completo — para integraciones de notificaciones cargar `notificaciones-multicanal`."
---
# Frontend Avanzado — APIs Nativas y Rendimiento

## Cuándo NO cargar

- La tarea es implementar componentes UI con React, Angular o Vue sin necesitar Web Workers, Service Workers ni IndexedDB: cargar el skill del framework.
- La tarea es diseño responsivo (breakpoints, Tailwind mobile-first, container queries para layout): cargar `diseno-responsivo`.
- La tarea es optimización del bundle de build (tree shaking, code splitting, Vite, Webpack): cargar el skill del bundler.
- La tarea es integración de notificaciones push sin implementar el Service Worker completo: cargar `notificaciones-multicanal`.

## Por qué importan las APIs nativas del navegador

Los frameworks abstraen muchas APIs del navegador, pero conocerlas directamente permite:
- Descargar trabajo pesado del hilo principal (Web Workers).
- Ofrecer experiencias offline (Service Workers + IndexedDB).
- Actualización en tiempo real sin polling (WebSockets/SSE).
- Estilos que responden al contenedor, no solo a la pantalla (Container Queries).

---

## Web Workers — Reglas

- Todo cálculo que tome >50ms DEBE ir en un Web Worker.
- Comunicación tipada con interfaces `MensajeEntrada` / `MensajeSalida`.
- Reportar progreso para operaciones largas.
- NUNCA bloquear el hilo principal con operaciones O(n2).

---

## Service Workers — Estrategias de Caché

| Tipo de recurso | Estrategia |
|----------------|------------|
| APIs | Network First (fallback a caché) |
| Recursos estáticos (JS, imágenes) | Cache First |
| HTML | Stale While Revalidate |

Reglas obligatorias:
- `skipWaiting()` + `clients.claim()` para actualización inmediata.
- Limpiar cachés viejos en el evento `activate`.
- Precachear recursos críticos en `install`.
- Página `/offline.html` como fallback de última instancia.

---

## WebSockets vs SSE — Cuándo Usar Cuál

| Criterio | WebSocket | SSE |
|----------|-----------|-----|
| Dirección | Bidireccional | Servidor -> Cliente |
| Protocolo | ws:// / wss:// | HTTP estándar |
| Reconexión | Manual | Automática nativa |
| Complejidad | Mayor | Menor |

**Regla**: Si solo necesitas flujo del servidor al cliente, usa SSE. Es más simple.

Ambos DEBEN tener:
- Reconexión automática con backoff exponencial.
- Manejo de estado (conectando / conectado / desconectado).
- Cleanup al destruir el componente.

---

## IndexedDB — Reglas

- Esquema versionado con migraciones incrementales en `upgrade`.
- Índices para queries frecuentes.
- Patrón offline-first: guardar localmente, sincronizar cuando hay red.
- NUNCA almacenar datos sensibles sin cifrado adicional.

---

## CSS Container Queries — Reglas

- Preferir container queries sobre media queries para componentes reutilizables.
- El componente responde a su contenedor, no al viewport.
- Unidades `cqi` (container query inline) para tipografía fluida dentro del contenedor.

```css
.contenedor { container-type: inline-size; container-name: tarjetas; }

@container tarjetas (min-width: 400px) {
  .tarjeta { flex-direction: row; }
}
@container tarjetas (max-width: 399px) {
  .tarjeta { flex-direction: column; }
}
```

---

## CSS Cascade Layers — Reglas

- SIEMPRE declarar orden explícito de capas al inicio del archivo.
- Orden recomendado: `@layer reset, base, componentes, utilidades, sobreescrituras`.
- Las utilidades siempre ganan sobre componentes (por orden de capa, no especificidad).

```css
@layer reset, base, componentes, utilidades, sobreescrituras;

@layer componentes {
  .btn { padding: 0.5rem 1rem; border-radius: 0.25rem; }
}
@layer utilidades {
  .hidden { display: none; }
}
```

---

## View Transitions API — Reglas

- SIEMPRE verificar soporte: `if (!("startViewTransition" in document)) return;`
- Personalizar con `::view-transition-old` y `::view-transition-new`.
- `view-transition-name` para transiciones específicas de elemento.

---

## Checklist de Revisión

- [ ] Cálculos >50ms en Web Worker, no en hilo principal
- [ ] Service Worker con estrategia correcta por tipo de recurso
- [ ] IndexedDB con esquema versionado
- [ ] WebSocket/SSE con reconexión automática y backoff
- [ ] Container Queries para componentes reutilizables
- [ ] CSS Layers con orden explícito
- [ ] View Transitions con fallback
- [ ] Service Worker actualiza correctamente (`skipWaiting` + `clients.claim`)

Para implementaciones completas de cada API (Web Workers, Service Workers, WebSocket, SSE, IndexedDB, View Transitions), ver [recursos/apis-nativas-ejemplos.md](recursos/apis-nativas-ejemplos.md).

## Gotchas / Errores comunes no obvios

**Un Web Worker falla silenciosamente cuando importa módulos ES que no están disponibles en el contexto del worker**: los Web Workers no tienen acceso al `window`, al DOM ni a módulos que dependan de él. Si el worker importa un módulo que llama a `document.createElement` o `window.fetch` internamente, el worker termina sin mensaje de error visible en la consola principal. Causa: los errores del worker solo se propagan si el código del worker los emite explícitamente. Fix: envolver todo el cuerpo del worker en un `try/catch` y usar `postMessage({ error: e.message })` para reportar fallos al hilo principal. Verificar con `worker.onerror = e => console.error(e)` antes de enviar el primer mensaje.

**Service Worker en `skipWaiting` + `clients.claim()` puede forzar una actualización de SW mientras el usuario tiene formularios a medio llenar**: `skipWaiting()` activa el SW nuevo inmediatamente, y `clients.claim()` toma control de todas las pestañas. Si el SW nuevo tiene breaking changes en el caché (versión diferente), las peticiones en vuelo en otras pestañas pueden recibir respuestas inesperadas o el estado local puede quedar inconsistente. Causa: la actualización de SW es asíncrona pero `clients.claim()` es agresivo. Fix: usar `skipWaiting()` solo en SWs que no cambien la estrategia de caché, y comunicar la actualización al cliente con `postMessage({ type: 'SW_UPDATED' })` para que el usuario pueda guardar y recargar voluntariamente.

**`IndexedDB` en Safari Private Browsing retorna quota de 0 bytes y cualquier `put()` lanza un error de quota excedida**: Safari en modo privado restringe IndexedDB a 0 bytes disponibles, pero la apertura de la BD (`indexedDB.open()`) no falla — solo las operaciones de escritura. Causa: Safari no comunica el modo privado al API web, y los errores de quota en IDB se lanzan de forma asíncrona en el handler de transacción. Fix: implementar un feature-detect en el `onsuccess` del `open()` intentando una operación de escritura de prueba y manejar el `QuotaExceededError`. Si se detecta, degradar gracefully a `sessionStorage` o a operación solo en memoria con aviso al usuario.

**La View Transitions API con `view-transition-name` en listas dinámicas produce transiciones que se solapan si dos elementos tienen el mismo nombre**: si el `view-transition-name` se genera dinámicamente con un valor que puede repetirse (como el tipo de componente en lugar del ID), el browser lanza un error de "duplicate names" y cancela toda la transición sin efecto visual. Causa: la especificación prohíbe tener dos elementos con el mismo `view-transition-name` activos simultáneamente. Fix: usar siempre el ID único del elemento como parte del nombre: `view-transition-name: item-${id}`. Agregar en el CSS `view-transition-name: none` en el `:hover` para deshabilitar la transición del elemento que no se está navegando si hay listas largas.

---

## CSP con `'unsafe-inline'` es transitorio aceptable; endurecer con nonce

### Contexto

Content Security Policy (CSP) es un header HTTP que mitiga XSS restringiendo
qué fuentes de scripts, estilos e imágenes puede cargar el navegador. La
directiva más estricta es `script-src 'self'` (solo scripts del mismo origen),
pero muchas UIs legacy o con inyección de estilos dinámicos requieren
temporalmente `'unsafe-inline'` para funcionar.

### Regla

`'unsafe-inline'` es **aceptable como paso transitorio** en migraciones hacia
CSP estricto, **pero nunca como estado final**. Debe venir acompañado de:

1. Comentario `TODO: migrar a nonce` en el código que emite el CSP.
2. Fecha objetivo de remoción (máximo 90 días).
3. Issue en el tracker referenciando el TODO.

### Patrón de migración en 3 fases

```python
# Fase 1 — transitoria: permitir inline con warning claro
# TODO(2026-07-01): migrar a nonce-based CSP; eliminar 'unsafe-inline'
# Issue: https://tracker/X-1234
CSP_TRANSITORIO = "; ".join([
    "default-src 'self'",
    "script-src 'self' 'unsafe-inline'",  # ← deuda técnica temporal
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data:",
    "connect-src 'self'",
])


# Fase 2 — nonce por request: eliminar 'unsafe-inline' de scripts
import secrets

def construir_csp_con_nonce(nonce: str) -> str:
    return "; ".join([
        "default-src 'self'",
        f"script-src 'self' 'nonce-{nonce}'",
        "style-src 'self' 'unsafe-inline'",  # estilos todavía pendientes
        "img-src 'self' data:",
        "connect-src 'self'",
    ])


# En FastAPI middleware
@app.middleware("http")
async def csp_middleware(request: Request, call_next):
    nonce = secrets.token_urlsafe(16)
    request.state.csp_nonce = nonce  # disponible para templates
    response = await call_next(request)
    response.headers["Content-Security-Policy"] = construir_csp_con_nonce(nonce)
    return response


# Template usa el nonce en <script>
# <script nonce="{{ request.state.csp_nonce }}">...</script>


# Fase 3 — final: sin 'unsafe-inline' ni nonce necesario
CSP_ESTRICTO = "; ".join([
    "default-src 'self'",
    "script-src 'self'",
    "style-src 'self'",
    "img-src 'self' data:",
    "connect-src 'self'",
])
```

### Reglas

- **Nunca deployar Fase 1 sin fecha de migración a Fase 2/3 comprometida** — sin fecha, la deuda persiste indefinidamente.
- **`script-src` debe migrar a nonce antes que `style-src`** — los ataques XSS vía `<script>` son mucho más impactantes que vía `<style>`.
- **El nonce es por-request**, regenerado en cada response. Cachearlo entre requests anula la protección.
- **Agregar `Content-Security-Policy-Report-Only`** en staging antes de activar en producción para observar violaciones sin romper la UI.
- **Listar endpoints de report**: `report-uri /csp-report` o `report-to csp-endpoint` para recolectar violaciones y detectar intentos de XSS bloqueados.

### Anti-patrón

```python
# MAL — 'unsafe-inline' permanente sin TODO ni plan de migración
CSP = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'"
```

`'unsafe-eval'` es aún más permisivo y habilita ataques de eval() dinámico —
debe eliminarse inmediatamente, no en fases. `'unsafe-inline'` puede coexistir
temporalmente.

### Frameworks de seguridad

- OWASP Top 10 A03:2021 — Injection (XSS).
- MITRE ATT&CK T1189 — Drive-by Compromise.
- NIST CSF PR.PS-06 — Communications and control networks are protected.
