Tu base de conocimiento: frameworks, diseño y ecosistema web · v0.6
11 módulos
7 activos · 3 en plan
Frameworks & Tecnologías
25 tecnologías · 5 categorías · Guía de cuándo usar cada una
Frontend UI
Meta-fw
Backend
CMS
No-Code
Gana   Válido   ·No ideal
Diseño
Design Systems · Herramientas de UI · Gestión de color · Accesibilidad
Métricas de Design Systems
Lista exhaustiva · fuente: design_system_metrics_exhaustive_v2
Adopción no es solo "cuánto se usa." Lo más importante es si se usa correctamente y por qué razón cuando no se usa.
Adopción en diseño (Figma)
  • % de componentes usados del total publicado en la librería
  • Tasa de detach — cuántos componentes se desvinculan. Señala gaps del sistema
  • Frecuencia de uso por componente — cuáles se usan casi nunca
  • Equipos activos que han insertado componentes en el último mes
  • % de archivos de producto que usan la librería conectada
  • Adopción de tokens (colores, tipografía, espaciado) además de componentes
  • Adopción de variables y estilos de la librería
Adopción en código
  • Cobertura — % de UI construida con componentes del DS vs custom
  • Instancias en producción de cada componente (no solo en diseño)
  • Desviaciones activas — componentes custom que no son del DS
  • Versión adoptada — cuántos proyectos siguen en versiones antiguas
  • Descargas/dependencias npm del paquete del DS
  • Cobertura de tokens en CSS/variables del código base
  • Adopción por equipo o repositorio — para identificar quién no usa
Adopción de documentación
  • Visitas a la doc (páginas vistas, usuarios únicos, tiempo de sesión)
  • Páginas más visitadas vs páginas que nadie visita
  • Tasa de rebote — si entran y salen sin navegar
  • Búsquedas sin resultado — qué busca la gente y no encuentra
  • Tickets de soporte por temas que deberían estar en la doc
  • Tiempo hasta encontrar lo que necesitan (en tests de usabilidad)
Adopción cualitativa
  • Awareness — ¿los equipos saben que el DS existe?
  • Percepción — ¿lo ven como una herramienta o como una restricción?
  • Participación en office hours, reuniones del DS
  • NPS interno — ¿lo recomendarían a otro equipo?
  • Razones de no uso — por qué no lo adoptan (encuestas)
  • Scorecard por equipo — autoevaluación del nivel de adopción
La eficiencia tiene dos caras: la del equipo que hace el DS, y la de los equipos que lo consumen.
Velocidad de entrega
  • Time to market — antes vs después del DS
  • Tiempo de diseño de pantalla estándar (benchmarking con/sin DS)
  • Tiempo de desarrollo de un componente nuevo vs reusar uno
  • Tiempo de handoff diseño → desarrollo (¿mejora con la doc del DS?)
  • Ciclos de revisión — cuántas iteraciones necesita un diseño hasta aprobarse
  • Lead time de un componente nuevo desde petición hasta publicación
Ahorro de tiempo y coste
  • Horas ahorradas por reutilización de componentes (tiempo estimado vs construir desde cero)
  • Coste de diseño duplicado — cuántos equipos hacen lo mismo por separado
  • Reducción de deuda técnica en CSS o estilos redundantes
  • Coste de refactoring evitado gracias a componentes estandarizados
  • Ratio de reutilización — cuántos componentes reutilizados vs creados desde cero por sprint
Calidad del proceso
  • Bugs visuales en producción relacionados con inconsistencias de UI
  • Bugs evitados por tener componentes testeados de base
  • Tasa de revisiones de QA antes vs después de adoptar el DS
  • Errores de handoff — discrepancias entre diseño y código final
  • Tiempo de onboarding de nuevos diseñadores/devs hasta ser autosuficientes
Salud del propio DS
  • Cadencia de publicación — cada cuánto se actualiza el DS
  • Backlog pendiente — peticiones acumuladas sin resolver
  • Tiempo de respuesta a issues o peticiones de equipos
  • % de componentes deprecated que aún se usan
  • Cobertura Figma-código — cuántos componentes existen en ambos lados
  • Número de librerías activas — síntoma de fragmentación si hay muchas
La consistencia es medible. No hay que quedarse en la percepción subjetiva.
Consistencia visual
  • Redundancia CSS — duplicados de colores, tipografías, espaciados
  • Estilos custom fuera del DS en archivos de diseño
  • Variaciones no documentadas de componentes en producción
  • Divergencia visual entre productos del mismo ecosistema
  • Parity diseño-código — % de componentes de Figma con equivalente exacto en código
Accesibilidad
  • % de componentes que pasan WCAG AA o AAA
  • Ratio de contraste en tokens de color
  • Cobertura de ARIA en componentes interactivos
  • Regresiones de accesibilidad introducidas en updates
  • Tests automatizados de a11y que pasan en CI/CD
  • Issues de accesibilidad reportados en producción
Usabilidad del propio DS
  • SUS score (System Usability Scale) aplicado al DS como producto interno
  • Tasa de éxito en tareas clave de la doc (¿pueden encontrar lo que buscan?)
  • Tasa de error al implementar un componente por primera vez
  • Tiempo en completar tareas con el DS vs sin él
  • Claridad percibida de la documentación (escala 1–5 en encuestas)
Impacto en el usuario final
  • Quejas de usuarios sobre inconsistencias visuales o de interacción
  • Tasa de error en tareas clave de los productos (refleja calidad de UI)
  • NPS o CSAT de los productos como proxy de coherencia de experiencia
  • Satisfacción con la UI en investigación de usuario periódica
  • Flujos coherentes — ¿los usuarios reconocen patrones entre productos?
Las métricas de equipo son las más ignoradas y las más importantes. Un DS que nadie quiere usar no vale nada.
Satisfacción y motivación
  • Satisfacción laboral — encuestas periódicas sobre cómo se siente el equipo
  • Aumento de creatividad percibida — ¿el DS libera tiempo para innovar?
  • Rotación del equipo de diseño como señal de motivación
  • Compromiso — participación voluntaria en mejoras del DS
  • Autonomía percibida — ¿sienten que el DS los limita o los empodera?
  • Framework AMP — autonomía, maestría y propósito (Daniel Pink)
Contribución y gobernanza
  • Contribuciones externas — propuestas de componentes de equipos no-core
  • Pull requests o issues creados por equipos consumidores
  • Participación en reuniones de DS (office hours, sincs)
  • Feedback recibido sobre el DS — volumen y calidad
  • Peticiones de nuevos componentes vs peticiones resueltas
  • Embajadores del DS — personas en otros equipos que lo promueven
Colaboración entre disciplinas
  • Fricción diseño-dev — quejas cruzadas, malentendidos en handoff
  • Alineación percibida con producto, marketing, ingeniería
  • Ritmo de sincronización — ¿se mantienen reuniones regulares de DS?
  • Uso compartido — diseñadores y devs consultan la misma fuente de verdad
  • Decisiones bloqueadas por falta de un componente o patrón definido
Cultura de diseño
  • Visibilidad del DS en la organización — ¿los stakeholders lo conocen?
  • Apoyo ejecutivo — inversión y reconocimiento desde arriba
  • Evangelización — cuánta gente habla del DS proactivamente
  • Percepción de valor por parte de equipos no-diseño
  • Sentimiento de "alivio" (Dann Mall) — el DS reduce la carga mental
Conectar el DS con métricas de negocio es lo que le da peso frente a stakeholders.
ROI y valor económico
  • Coste de mantener el DS vs coste de no tenerlo (inconsistencias, bugs, duplicación)
  • Horas de diseño/dev ahorradas × coste hora → valor en €
  • Reducción de bugs en prod y su coste de resolución
  • Velocidad de lanzamiento de nuevas features — ¿el DS acelera?
  • Coste de onboarding antes vs después del DS
Impacto en producto
  • Tasa de conversión en flujos rediseñados con el DS
  • Retención de usuarios como proxy de calidad de experiencia
  • Reducción de soporte por confusión de UI
  • Velocidad de escala — cuántos productos nuevos se lanzan usando el DS
  • Consistencia de marca — percepción externa de coherencia
OKRs y frameworks
  • OKRs del DS — objetivos trimestrales con resultados clave medibles
  • Alineación con OKRs de producto — ¿el DS contribuye a métricas del negocio?
  • Progreso de madurez — modelo de madurez del DS (inicial → gestionado → optimizado)
  • Escalabilidad — cuántos productos puede soportar el DS actual
  • Cobertura de superficie — % del portfolio que usa el DS
Métricas de DesignOps
  • Eficacia del equipo de diseño — % de tareas completadas vs planificadas
  • Iteraciones necesarias para refinar un diseño hasta aprobarse
  • Frecuencia de errores de usuario en diseños producidos con el DS
  • Tasa de éxito de tarea — usuarios que completan una tarea en los productos
  • Tiempo en completar tareas por usuarios en los productos
El tamaño del equipo cambia qué medir, cómo medirlo y qué hacer con los datos.
Equipos grandes (10+ diseñadores)
  • Adopción por equipo/squad — identifica quién no usa y por qué
  • Gobernanza formal — métricas de contribuciones, PRs, aprobaciones
  • Cobertura en código con herramientas automáticas (react-scanner, Omlet)
  • Versión adoptada por repositorio — evitar fragmentación de versiones
  • Dashboards en tiempo real de uso de componentes
  • SUS score del DS como producto interno formal
  • NPS interno con encuestas periódicas estructuradas
  • Embajadores por equipo como multiplicadores de adopción
  • ROI documentado para justificar inversión ejecutiva
  • OKRs formales del equipo del DS alineados con la empresa
  • Tiempo de respuesta a peticiones de equipos consumidores
Equipos pequeños (2–5 diseñadores)
  • Adopción binaria primero — ¿se usa? ¿lo conocen todos?
  • Conversaciones directas en vez de encuestas — más fácil y más rico
  • Tiempo ahorrado percibido por los propios diseñadores
  • Detach rate en Figma — señal práctica de gaps sin necesitar herramientas
  • Backlog de componentes pendientes — proxy de salud del DS
  • Riesgo de abandono — si no se siente útil, el DS muere solo
  • Documentación accesible — ¿puedes explicarlo a alguien nuevo en 10 min?
  • Coste de mantenimiento — ¿cabe en la carga del equipo?
  • Flexibilidad percibida — si es muy rígido, se abandona
  • Métricas cualitativas primero; automatizar solo cuando haya masa crítica
Tríada: 1 designer + 1 PM + 1–3 devs
Qué medir
  • Fricción en handoff — el dev trabaja directamente con el diseñador, cualquier ambigüedad del DS se nota inmediatamente
  • Tiempo de implementación por componente — el dev es quien más sufre si el DS no tiene tokens o doc clara
  • Detach rate — en triadas es normal que el dev adapte porque el DS no cubre el caso de uso exacto
  • Alineación PM-diseño — ¿el PM conoce qué existe en el DS antes de definir features?
  • Velocidad de prototipado — si el DS acelera las demos al PM o los primeros wireframes
  • Deuda acumulada — componentes custom que "ya arreglaremos" y nunca se arreglan
Particularidades clave
  • El designer hace y consume el DS — es al mismo tiempo quien lo mantiene y quien lo usa, lo que crea un sesgo enorme
  • El dev puede volverse el DS — si hay 2–3 devs, uno puede desarrollar un sistema propio en código que diverge del diseño
  • El PM decide sin datos de DS — si el PM no tiene visibilidad de qué componentes existen, pedirá cosas que ya están resueltas
  • No hay quién mida — en una tríada nadie tiene el rol de DesignOps, así que medir tiene que ser casi automático
  • El DS puede ser informal — a veces es solo una librería de Figma y un Notion; lo relevante es si reduce las conversaciones repetidas
Señales prácticas (sin infraestructura)
  • ¿Cuántas veces por sprint el dev pregunta al designer sobre estilos o comportamientos ya definidos?
  • ¿El PM revisa el DS antes de escribir una historia de usuario?
  • ¿Hay componentes en código que el designer no sabe que existen?
  • ¿Cuánto tarda un nuevo dev en entender qué hay en el DS y empezar a usarlo?
  • ¿Se abre Figma para reusar o se clona código de otro sitio?
  • ¿El DS aparece en la retro? — si aparece como problema, está fallando
Riesgo principal de la tríada: que el DS sea invisible para el PM y el dev acabe construyendo su propio sistema de facto en código. La métrica más valiosa aquí es si los tres hablan el mismo lenguaje cuando se refieren a un componente — si no, el DS no está funcionando.
Glosario de Design Systems
Terminología esencial · busca, filtra, estudia
Herramientas de Design Systems & UI
Glosario personal · Añade nuevas herramientas con un clic
Color Tips & Gestión de Color
Base teórica y práctica para trabajar color con criterio en producto digital: modos de color, rampas, contraste y tokenización
Fundamentos aplicables a producto real
Esta sección es viva: iremos añadiendo buenas prácticas, criterios de decisión y ejemplos para diseño y desarrollo.
Accesibilidad
WCAG 2.2 · Principios POUR · Criterios · ARIA · Herramientas
Accesibilidad no es una feature opcional. Es diseñar para que cualquier persona — con o sin discapacidad visual, motora, cognitiva o auditiva — pueda usar tu producto. Las WCAG del W3C se articulan sobre 4 principios agrupados en el acrónimo POUR.
P
Perceptible
La información y los componentes de la interfaz deben ser presentados de manera que puedan percibirse visual, táctil o auditivamente.
alt text en imágenessubtítulos en vídeocontraste de colorno solo color para transmitir info
O
Operable
Los componentes y la navegación deben ser operables independientemente del dispositivo de entrada que use el usuario.
navegable con tecladosin trampas de focotiempo suficientesin contenido que cause ataques
U
Comprensible
La información y el funcionamiento deben ser comprensibles. El contenido debe ser legible y predecible, y los formularios deben ayudar a evitar y corregir errores.
idioma de la página declaradonavegación predecibleerrores identificadosetiquetas en formularios
R
Robusto
El contenido debe ser interpretado de forma fiable por una amplia gama de agentes de usuario, incluidas las tecnologías de asistencia como lectores de pantalla.
HTML semántico válidoroles ARIA correctosnombre + rol + valorcompatible con AT
A
Mínimo
Criterios básicos sin los cuales algunos usuarios no pueden acceder al contenido en absoluto.
Todo sitio público. Base legal en la mayoría de países.
AA
Estándar · El objetivo real
El nivel objetivo para la mayoría de webs y apps. Incluye contraste 4.5:1, navegación por teclado completa, texto escalable, reflow a 320px y los nuevos criterios de WCAG 2.2.
Obligatorio en sector público UE (Directiva 2016/2102). Estándar de facto de la industria.
AAA
Avanzado · Aspiracional
Criterios de máxima accesibilidad. El W3C no lo recomienda como requisito global. Se aplica a secciones específicas o audiencias especiales.
Plataformas especializadas, contenido crítico, audiencias con necesidades específicas.
Ratios de contraste · referencia rápida
Texto normal (<18pt / <14pt bold)AA 4.5 : 1AAA 7 : 1
Texto grande (≥18pt / ≥14pt bold)AA 3 : 1AAA 4.5 : 1
Componentes UI e iconos informativosAA 3 : 1— no aplica
Texto decorativo / logotipos— exento— exento
WCAG 2.2 · Publicada octubre 2023. Los criterios marcados con new 2.2 son nuevos respecto a WCAG 2.1.
PPerceptible
CriterioDescripciónNivel
1.1.1 Non-text ContentTodas las imágenes e iconos tienen alternativas en textoA
1.3.1 Info and RelationshipsLa información transmitida visualmente también está disponible programáticamenteA
1.4.1 Use of ColorEl color no es el único medio visual para transmitir informaciónA
1.4.3 Contrast (Minimum)4.5:1 para texto normal, 3:1 para texto grandeAA
1.4.4 Resize TextEl texto se puede redimensionar al 200% sin pérdida de funcionalidadAA
1.4.6 Contrast (Enhanced)7:1 para texto normal, 4.5:1 para texto grandeAAA
1.4.10 ReflowEl contenido reflota a 320px de ancho sin scroll horizontalAA
1.4.11 Non-text ContrastComponentes UI tienen contraste 3:1AA
OOperable
CriterioDescripciónNivel
2.1.1 KeyboardToda la funcionalidad disponible vía tecladoA
2.1.2 No Keyboard TrapEl foco de teclado se puede mover desde cualquier componenteA
2.4.1 Bypass BlocksSkip link o navegación por landmarks disponibleA
2.4.3 Focus OrderEl orden de foco preserva el significadoA
2.4.4 Link PurposeEl propósito del enlace es claro por su texto o contextoA
2.4.6 Headings and LabelsLos encabezados y labels son descriptivosAA
2.4.7 Focus VisibleEl indicador de foco es visibleAA
2.4.11 Focus Not Obscured new 2.2El elemento enfocado no queda completamente oculto por contenido del autorAA
2.5.7 Dragging Movements new 2.2Las acciones de arrastre tienen alternativas de puntero únicoAA
2.5.8 Target Size (Minimum) new 2.2Los objetivos interactivos son al menos 24×24 CSS pxAA
UComprensible
CriterioDescripciónNivel
3.1.1 Language of PageEl idioma por defecto está especificado en el HTMLA
3.2.3 Consistent NavigationLa navegación es consistente entre páginasAA
3.3.1 Error IdentificationLos errores de entrada están claramente descritosA
3.3.2 Labels or InstructionsLos campos de formulario tienen labels o instruccionesA
3.3.3 Error SuggestionSe sugieren correcciones de errores cuando es posibleAA
3.3.8 Accessible Authentication new 2.2No se requiere test de función cognitiva para login sin alternativaAA
RRobusto
CriterioDescripciónNivel
4.1.2 Name, Role, ValueLos componentes UI tienen nombres accesibles y roles correctosA
4.1.3 Status MessagesLos mensajes de estado son anunciados a los lectores de pantallaAA
ARIA (Accessible Rich Internet Applications) añade semántica a elementos que HTML no describe por sí solo. Regla de oro: usa siempre HTML semántico nativo primero — ARIA complementa, no reemplaza.
Botones
Usa <button> nativo siempre que sea posible. Añade aria-label cuando el texto visible no sea suficientemente descriptivo.
<button>Guardar cambios</button>

<button aria-label="Cerrar diálogo">×</button>

<!-- Si no puedes usar button: -->
<div role="button" tabindex="0"
     aria-pressed="false">Toggle</div>
Formularios
Cada input necesita un <label> asociado. Usa aria-describedby para instrucciones y aria-invalid + aria-errormessage para errores.
<label for="email">Correo electrónico</label>
<input type="email" id="email"
       aria-describedby="email-hint"
       aria-invalid="false">
<p id="email-hint">Nunca compartiremos tu email.</p>
Navegación
Usa <nav> con aria-label para distinguir múltiples navs. Indica la página actual con aria-current="page".
<nav aria-label="Principal">
  <ul>
    <li><a href="/" aria-current="page">Inicio</a></li>
    <li><a href="/sobre">Sobre nosotros</a></li>
  </ul>
</nav>
Modales / Diálogos
Usa role="dialog" + aria-modal="true" + aria-labelledby. El foco debe moverse al modal al abrirse y volver al trigger al cerrarse.
<div role="dialog" aria-modal="true"
     aria-labelledby="dialog-title">
  <h2 id="dialog-title">Confirmar acción</h2>
  <button autofocus>Cancelar</button>
</div>
Links externos
Informa siempre que un link abre en pestaña nueva. Usa texto visualmente oculto para lectores de pantalla.
<a href="https://externo.com"
   target="_blank" rel="noopener">
  Sitio externo
  <span class="visually-hidden">
    (abre en nueva pestaña)
  </span>
</a>
Live Regions
Para anunciar contenido dinámico sin mover el foco. aria-live="polite" espera; assertive interrumpe.
<!-- Notificaciones no urgentes -->
<div aria-live="polite" aria-atomic="true">
  Guardado correctamente
</div>

<!-- Errores críticos -->
<div role="alert">
  Error: el campo es obligatorio
</div>
Ninguna herramienta automatizada detecta el 100% de los problemas de accesibilidad — se estima que cubren entre el 30–40%. Son el primer filtro, no el definitivo.
🪓
axe DevTools
Extensión browser
El motor de testing de accesibilidad más usado. Integrable en CI/CD. Muestra problemas directamente sobre el DOM con explicaciones y enlaces a la documentación WCAG.
Chrome/Edge/Firefoxcero falsos positivosCI integrable
🌊
WAVE
Extensión browser
Visualiza los errores directamente sobre la página con iconos superpuestos. Muy visual para aprender qué está fallando y por qué.
visualeducativoWebAIM
🔦
Lighthouse
Integrado en Chrome
Auditoría de accesibilidad, rendimiento, SEO y buenas prácticas integrada en Chrome DevTools. Puntúa de 0 a 100.
DevTools → Lighthousepuntuación 0–100CI
🎙️
NVDA
Lector de pantalla · Windows
El lector de pantalla gratuito más usado en Windows. Prueba con NVDA + Chrome o Firefox para simular la experiencia real de un usuario con discapacidad visual.
Windowsgratuitoprueba real
🗣️
VoiceOver
Lector de pantalla · Apple
Integrado en macOS e iOS. Actívalo con Cmd+F5. Prueba con Safari en macOS para la combinación más usada en Apple.
macOS / iOSintegradoCmd+F5
🎨
Who Can Use
Web App · Contraste
Visualiza cómo perciben un par de colores personas con diferentes tipos de daltonismo o baja visión. Imprescindible al definir paletas de color en un DS.
contrastedaltonismoDesign Systems
Glosario Técnico
Términos que la IA usa y que confunden · Qué significan · Cómo afectan a tu proyecto
VibeTips · Prompts Estructurales
Recetas de prompts para pedirle a la IA código de calidad con cada tecnología
Dónde Desplegar
Plataformas de hosting · Cuándo usar cada una · Compatible con qué frameworks
Regla general: Si usas Astro/Next.js → empieza en Vercel. Si usas Django/Laravel → Railway o Render. Si es solo frontend estático → Netlify o Cloudflare Pages. Si quieres BaaS → Supabase ya lo incluye.
Despliegue & Infraestructura
DNS · cPanel · Caché · Docker · Nginx · Seguridad · Integraciones CRM
El DNS es la agenda de teléfonos de internet. Traduce nombres de dominio legibles (miweb.com) a direcciones IP numéricas. Sin entender DNS, no puedes controlar dónde apunta tu web ni tu email.
Flujo: ¿Qué pasa cuando alguien escribe tu dominio?
Browserescribe miweb.com
Resolver DNS local¿lo tengo en caché?
Root DNS¿quién gestiona .com?
Nameserver del dominiotabla de registros DNS
IP del servidorregistro A
cPanel es el panel de control del servidor. Una aplicación web instalada en tu servidor (o gestionada por tu hosting como Verpex) que te permite gestionar dominios, emails, bases de datos, archivos y métricas desde una interfaz visual.
La caché es guardar una copia de algo para no tener que calcularlo o pedirlo de nuevo. Cada capa de caché tiene su lugar: no todas son iguales ni sirven para lo mismo.
Docker empaqueta tu aplicación y todo lo que necesita para correr en un contenedor portátil. Docker Compose orquesta múltiples contenedores (frontend, backend, base de datos) para que funcionen juntos con una sola configuración.
Arquitectura: Frontend + API + HubSpot (tu caso real)
Visitantebrowser HTTPS
GET / o POST /api/*
Nginx Containerúnico punto de entrada público
sirve estáticos directamente
Frontend estáticoHTML/JS/CSS en disco
proxy_pass /api/*
Node.js Expressred interna · nunca expuesto
POST + API token
HubSpot CRM APIservicio externo
La seguridad no es una capa que se añade al final. Cada capa tiene sus propias vulnerabilidades. Cloudflare protege a nivel de red, SSL cifra el canal, SSH protege el acceso al servidor, y las variables de entorno protegen las credenciales.
Integrar un CRM o servicio externo siempre sigue el mismo patrón: credenciales seguras en el backend, nunca en el frontend. El browser es público — cualquier token que pongas en el JS del cliente puede ser extraído.
Docker · Curso Completo
8 unidades · Conceptos · Comandos · Dockerfile · Compose · Redes · Volúmenes · Kubernetes · Herramientas
UD01 + UD02Introducción a contenedores y DockerConceptos base
Un contenedor no es una VM. Comparte el kernel del host pero aísla procesos, red y sistema de archivos. Más ligero, arranca en milisegundos y consume mucha menos RAM que una máquina virtual.
UD03 · UD04 · UD05 · UD06CheatSheet de comandos DockerReferencia rápida
Los comandos más usados organizados por categoría. docker para contenedores e imágenes individuales, docker compose para stacks multi-servicio.
UD04Gestión de imágenes y DockerfileBuild personalizado
Una imagen es la receta; un contenedor es el plato cocinado. El Dockerfile define paso a paso cómo construir tu imagen personalizada. Cada instrucción crea una capa — Docker reutiliza capas cacheadas para acelerar los builds.
Anatomía de un Dockerfile · Instrucciones principales
UD05Redes y volúmenes en DockerComunicación y persistencia
Sin red, los contenedores son islas. Sin volúmenes, los datos mueren con el contenedor. Redes Docker controlan qué contenedores se pueden ver entre sí. Volúmenes persisten datos más allá del ciclo de vida del contenedor.
Tipos de red
Tipos de volumen / almacenamiento
UD06Docker ComposeOrquestación multi-contenedor
Docker Compose define toda tu infraestructura en un archivo YAML. Un solo docker compose up -d levanta todos los servicios, redes y volúmenes configurados. Es el estándar para desarrollo y despliegues pequeños.
Anatomía de docker-compose.yml · Claves principales
UD07Utilidades para gestionar DockerGUIs y monitores
Más allá de la línea de comandos, hay herramientas que hacen el día a día con Docker mucho más visual y manejable. Desde extensiones de VS Code hasta paneles web completos.
UD08Introducción a KubernetesOrquestación a escala
Kubernetes (K8s) es el siguiente nivel de Docker Compose. Mientras Compose gestiona contenedores en un solo host, Kubernetes los orquesta en un clúster de máquinas: auto-scaling, self-healing, rolling updates y balanceo de carga incluidos.
Objetos principales de Kubernetes
UD03–UD08Casos prácticos del cursoLo que realmente construyes
Los casos prácticos del curso van de lo simple a lo complejo. Cada uno introduce una capa nueva: primero un contenedor solo, luego redes, luego Compose, luego stacks reales con IA y proxies.
Roadmap del Sistema
Plan de evolución de este OS · Qué viene, por qué y en qué orden
Este sistema nació como una matriz de decisión de frameworks. La conversación lo está convirtiendo en un sistema operativo personal de aprendizaje para VibeCoding: un lugar donde entender qué usas, cómo medirlo, qué significa lo que la IA te dice, y dónde vive lo que construyes. Este roadmap es el plan estructurado para llegar ahí.
Git & Documentación
Convenciones de commits · flujo add/commit/push/pull · rebase · buenas prácticas de documentación
Documentar bien y commitear bien son parte del mismo trabajo. Si el código explica el qué, el commit y la doc deben explicar el por qué.
Qué documentar siempre
  • Objetivo de cada módulo o feature.
  • Cómo correr el proyecto en local.
  • Variables de entorno necesarias (sin secretos reales).
  • Decisiones de arquitectura y trade-offs.
  • Pasos de despliegue y rollback.
Dónde poner cada cosa
  • README: onboarding y comandos base.
  • Comentarios: lógica compleja, no obviedades.
  • Mensajes de commit: propósito del cambio.
  • Pull Request: contexto, impacto, riesgos, pruebas.
  • CHANGELOG: cambios visibles para usuarios/equipo.
Guía completa de comandos Git: acciones clave, comando recomendado y para qué sirve cada uno en el flujo diario.
Acción Comando Explicación
Configurar usuariogit config --global user.name "Nombre"Define quién realiza los cambios.
Inicializargit initCrea un repositorio local nuevo.
Clonargit clone [URL]Descarga un proyecto remoto a tu PC.
Estado actualgit statusMuestra qué archivos has modificado.
Preparar cambiosgit add [archivo]Añade archivos al área de staging.
Guardar cambiosgit commit -m "mensaje"Crea una foto o versión del proyecto.
Ver cambiosgit diffMuestra las líneas exactas que modificaste.
Crear nueva ramagit switch -c [nombre]Crea y te posiciona en una nueva rama.
Cambiar de ramagit switch [nombre]Cambia a una rama existente.
Salto rápidogit switch -Vuelve a la rama donde estabas antes.
Fusionar ramasgit merge [rama]Une el trabajo de una rama a la actual.
Historialgit log --onelineMuestra los commits realizados.
Subir cambiosgit push -u origin [rama]Sube tus commits a GitHub.
Descargar cambiosgit pullTrae y une el trabajo del servidor.
Deshacer cambiosgit restore [archivo]Descarta cambios locales no deseados.
Guardar temporalgit stashEsconde cambios actuales para trabajar en otra cosa.
Recuperar guardadogit stash popAplica los cambios que tenías escondidos.
Consejo para tu flujo
Si alguna vez sientes que Git se ha vuelto muy complejo, vuelve a este flujo seguro:
git pull
git switch -c tu-rama
git add .
git commit -m "..."
git push
Conventional Commits ayuda a tener historial legible, changelogs automáticos y releases limpias.
Tipo Cuándo usarlo Ejemplo
featNueva funcionalidadfeat(auth): agregar login con magic link
fixCorrección de bugfix(api): manejar null en respuesta de perfil
docsCambios de documentacióndocs(readme): explicar setup de Docker
refactorMejora interna sin cambiar comportamientorefactor(ui): extraer card base reusable
testTests nuevos o ajustes de teststest(user): cubrir flujo de registro
choreTareas de mantenimientochore(deps): actualizar eslint a v9
Formato recomendado:
type(scope): mensaje corto en imperativo

Ejemplos:
feat(docs): crear sección de flujo git
fix(glosario): mejorar contraste de etiquetas
docs(commits): añadir guía de conventional commits
Flujo base seguro: trabaja en rama, haz commits pequeños, sincroniza con pull --rebase y luego push.
1
Traer cambios
git pull --rebase origin main
2
Preparar cambios
git add .
3
Crear commit
git commit -m "feat(scope): mensaje"
4
Subir rama
git push origin mi-rama
Pull vs Rebase
  • merge: conserva ramas tal cual, crea merge commit.
  • rebase: reescribe tus commits encima de la rama base.
  • ventaja rebase: historial lineal y limpio.
  • regla: no rebasees commits ya compartidos por todo el equipo.
Comandos útiles
git status
git log --oneline --graph --decorate
git diff
git rebase -i HEAD~3
git commit --amend
git push --force-with-lease
Astro · Arquitectura & Islands
Cómo funciona Astro · 15 capas · React Islands · basado en el proyecto real Watan
React · Guía de aprendizaje
Fundamentos · Estado & eventos · Gestión del estado · Hooks · Pensar en React · Basado en react.dev/learn
La UI en React es un árbol de componentes. Cada componente es una función JS que devuelve JSX. Los datos fluyen de padre a hijo mediante props. Nada más — el resto es consecuencia de este modelo.
Concepto base
¿Qué es un componente?
Una función JavaScript que empieza en PascalCase y devuelve JSX. React la convierte en un elemento de la UI reutilizable, anidable y que puede recibir datos.
La mayúscula no es convención — es obligatoria. React usa eso para distinguir componentes (<Boton>) de tags HTML nativos (<button>). Un componente en minúscula se trata como HTML y no funciona.
// Componente correcto — PascalCase, devuelve JSX
export default function Tarjeta() {
  return <div className="tarjeta">Hola</div>;
}

// Reutilizable: úsalo tantas veces como quieras
<Tarjeta />
<Tarjeta />
<Tarjeta />
Un componente solo puede devolver UN elemento raíz. Si necesitas devolver varios, envuélvelos en <>...</> (Fragment) — no añade nodos al DOM.
JSX
JSX no es HTML
JSX parece HTML pero es JavaScript disfrazado. El compilador lo transforma en React.createElement(). Por eso sigue las reglas de JS, no de HTML.
Dado que JSX es JS, las palabras reservadas de JS (class, for) están prohibidas como atributos — de ahí className y htmlFor. Todos los tags deben cerrarse porque JS no tiene HTML implícito.
// HTML              → JSX
class="btn"          → className="btn"
for="campo"          → htmlFor="campo"
<br>                 → <br />
<img src="x.png">   → <img src="x.png" />
<!-- comentario --> → {/* comentario */}
onclick="fn()"       → onClick={fn}
style="color:red"    → style={{ color: 'red' }}

// Expresiones JS dentro de JSX — con llaves {}
<h1>{titulo}</h1>
<img src={usuario.foto} alt={usuario.nombre} />
<div className={activo ? 'btn-on' : 'btn-off'}>
Props
Props — datos de padre a hijo
Las props son los "argumentos" de un componente. Fluyen siempre de padre a hijo (unidireccional). Dentro del componente son de solo lectura — nunca se modifican.
Las props hacen los componentes reutilizables con datos diferentes. Sin props, <Tarjeta> siempre mostraría lo mismo. Con props, <Tarjeta titulo="Airbnb" precio="500€" /> lo hace configurable. Cualquier intento de modificar una prop dentro del componente es un bug.
// Definición — desestructuración + tipos + valor por defecto
function Tarjeta({ titulo, precio = 'Consultar', destacada = false }: {
  titulo: string;
  precio?: string;
  destacada?: boolean;
}) {
  return <div className={destacada ? 'card card--top' : 'card'}>
    <h3>{titulo}</h3>
    <p>{precio}</p>
  </div>;
}

// Uso desde el padre
<Tarjeta titulo="Airbnb Valencia" precio="500€" destacada />
Props especiales
La prop children
children es la prop especial que recibe el contenido HTML que el padre pone entre las etiquetas del componente. Sirve para componentes "contenedor" que no saben qué tendrán dentro.
// Componente contenedor genérico
function Panel({ titulo, children }: {
  titulo: string;
  children: React.ReactNode;
}) {
  return (
    <div className="panel">
      <h2>{titulo}</h2>
      <div className="panel-body">
        {children}  {/* ← aquí entra lo que el padre ponga */}
      </div>
    </div>
  );
}

// Uso — lo que está entre las etiquetas va a children
<Panel titulo="Resultados">
  <p>3 apartamentos encontrados</p>
  <ListaApartamentos />
</Panel>
Pureza
Componente puro
Con las mismas props, siempre devuelve el mismo JSX. No modifica nada fuera de la función durante el render (sin efectos secundarios).
React puede re-renderizar un componente cuando quiera y tantas veces como quiera. Si el render tiene efectos secundarios (modificar variables externas, llamar APIs, leer la hora), el resultado será impredecible. En Strict Mode, React renderiza dos veces a propósito para detectar impurezas.
// MAL — efecto secundario en render
let visitas = 0;
function Impuro() {
  visitas++;          // ← modifica algo externo durante render
  return <p>Visitas: {visitas}</p>;
}

// BIEN — solo depende de sus props
function Puro({ nombre }: { nombre: string }) {
  const saludo = `Hola, ${nombre}`;   // ← calculado a partir de props
  return <p>{saludo}</p>;           // mismas props → mismo JSX
}
Renderizado condicional
Mostrar u ocultar elementos
En JSX no hay if directo — pero hay tres patrones equivalentes. Cada uno tiene su caso de uso.
// 1. && — cuando solo hay un caso (mostrar o nada)
{tieneFotos && <Galeria fotos={fotos} />}

// ⚠ cuidado con 0: {count && <X />} pinta "0" si count=0
// Solución: {count > 0 && <X />}

// 2. Ternario — cuando hay dos casos (esto o aquello)
{cargando ? <Spinner /> : <Contenido />}
<button className={activo ? 'btn-on' : 'btn-off'}>

// 3. Variable precomputada — condición compleja
let contenido;
if (cargando) contenido = <Spinner />;
else if (error) contenido = <Error msg={error} />;
else contenido = <Contenido datos={datos} />;
return <div>{contenido}</div>;
Listas
Renderizar arrays con .map()
Para renderizar una lista de elementos, transforma el array en JSX con .map(). Cada elemento necesita una prop key única y estable.
key permite a React identificar qué elemento cambió, se añadió o se eliminó sin re-renderizar toda la lista. Sin key funciona, pero lento y con bugs en animaciones. Con el índice funciona si el orden nunca cambia — si puede cambiar (filtros, reordenación), usa el ID del dato.
// Correcto — key es el ID del dato (estable y único)
{apartamentos.map((apt) => (
  <Tarjeta key={apt.id} titulo={apt.titulo} precio={apt.precio} />
))}

// Aceptable solo si el orden NUNCA cambia
{items.map((item, i) => <li key={i}>{item}</li>)}

// Filtrar + mapear
{apartamentos
  .filter(apt => apt.disponible)
  .map(apt => <Tarjeta key={apt.id} {...apt} />)}
Nunca uses Math.random() como key — cambia en cada render y React re-monta el componente entero cada vez.
El estado es la memoria privada de un componente. Cuando cambia, React vuelve a ejecutar la función del componente (re-render) y actualiza el DOM con el nuevo resultado. Es el único mecanismo legítimo para cambiar la UI en respuesta a acciones del usuario.
Hook fundamental
useState
Devuelve un array de dos elementos: el valor actual y una función para actualizarlo (setter). Llama al setter → React re-renderiza el componente con el nuevo valor.
Las variables normales no funcionan para la UI porque cuando las cambias React no se entera y no redibuja nada. El setter de useState le avisa a React: "este componente tiene datos nuevos, vuelve a renderizarlo".
import { useState } from 'react';

function Contador() {
  const [cuenta, setCuenta] = useState(0);
  //     ↑ valor  ↑ setter    ↑ valor inicial

  return (
    <div>
      <p>Clicks: {cuenta}</p>
      <button onClick={() => setCuenta(cuenta + 1)}>
        +1
      </button>
    </div>
  );
}

// MAL — React no se entera, no redibuja
let cuenta = 0;
cuenta++;           // ← invisible para React

// BIEN — React redibuja con el nuevo valor
setCuenta(cuenta + 1);
Concepto clave
El estado como instantánea
El estado no es una variable que cambia — es una fotografía del valor en el momento del render. Cada render tiene su propia copia fija del estado.
Este es el concepto más contraintuitivo de React. Cuando llamas a setCuenta(cuenta + 1) tres veces en el mismo handler, las tres lecturas de cuenta son el mismo valor (el de este render). No acumulas +3, acumulas +1.
// Ejemplo — cuenta empieza en 0
function handler() {
  setCuenta(cuenta + 1); // programa: "pon el estado a 0+1 = 1"
  setCuenta(cuenta + 1); // programa: "pon el estado a 0+1 = 1"
  setCuenta(cuenta + 1); // programa: "pon el estado a 0+1 = 1"
  // Resultado: cuenta = 1, no 3
  // 'cuenta' es siempre 0 durante todo este render
}
Para acumular múltiples cambios, usa la forma de función: setCuenta(prev => prev + 1) — ve la sección siguiente.
Actualizaciones en cola
La forma prev =>
Cuando el nuevo estado depende del estado anterior, pasa una función al setter. React la llama con el valor más reciente, no con el del render actual.
// Con función — acumula correctamente
function handler() {
  setCuenta(prev => prev + 1); // prev=0 → 1
  setCuenta(prev => prev + 1); // prev=1 → 2
  setCuenta(prev => prev + 1); // prev=2 → 3
  // Resultado: cuenta = 3 ✓
}

// Regla práctica:
// · El nuevo valor no depende del anterior → setCuenta(5)
// · El nuevo valor SÍ depende del anterior → setCuenta(prev => prev + 1)
// · Modificar objetos/arrays               → setCuenta(prev => ({ ...prev, campo: nuevo }))

// Ejemplo real — toggle
setExpandido(prev => !prev);

// Ejemplo real — añadir a array
setItems(prev => [...prev, nuevoItem]);
Estado con objetos
Actualizar objetos — spread
El estado en React es inmutable. Para cambiar un campo de un objeto, crea un objeto nuevo con todos los campos del anterior más el campo modificado. Usa el spread operator.
Si mutas el objeto directamente (usuario.nombre = 'Ana'), React compara la referencia del objeto — y es la misma que antes, así que decide que no hay cambios y no redibuja. Siempre crea un objeto nuevo.
const [usuario, setUsuario] = useState({ nombre: 'Ana', edad: 25, ciudad: 'Valencia' });

// MAL — muta el objeto existente, React no redibuja
usuario.edad = 26;
setUsuario(usuario);

// BIEN — crea un objeto nuevo con spread
setUsuario({ ...usuario, edad: 26 });
//          ↑ copia todos los campos, luego sobreescribe solo 'edad'

// Con objeto anidado — hay que hacer spread en cada nivel
const [form, setForm] = useState({ nombre: 'Ana', direccion: { ciudad: 'Valencia', cp: '46001' } });
setForm({ ...form, direccion: { ...form.direccion, ciudad: 'Madrid' } });
Estado con arrays
Actualizar arrays — sin mutar
Igual que con objetos: nunca mutaciones directas. Los métodos de array se dividen en permitidos (crean array nuevo) y prohibidos (mutan el original).
const [items, setItems] = useState(['Wifi', 'Parking', 'Piscina']);

// ✓ PERMITIDOS — crean array nuevo
setItems([...items, 'Gym']);               // añadir al final
setItems(['Nuevo', ...items]);             // añadir al principio
setItems(items.filter(i => i !== 'Parking')); // eliminar
setItems(items.map(i => i === 'Wifi' ? 'WiFi 6' : i)); // editar
setItems([...items].sort());              // sort (sobre copia)

// ✗ PROHIBIDOS — mutan el array original
items.push('Gym');          // ← React no redibuja
items.splice(1, 1);         // ← React no redibuja
items.sort();               // ← muta in-place
items.reverse();            // ← muta in-place
Si el código de actualización se vuelve muy verboso con objetos anidados, considera la librería Immer — permite escribir mutaciones que internamente crea objetos nuevos.
Eventos
Manejadores de eventos
Los eventos se pasan como props en camelCase. La diferencia clave: onClick={fn} pasa la función; onClick={fn()} la ejecuta durante el render — bug clásico.
// onClick — click
<button onClick={() => setCuenta(cuenta + 1)}>+1</button>

// onClick con argumento
<button onClick={() => eliminar(item.id)}>Borrar</button>

// onChange — input (e.target.value = texto escrito)
<input onChange={(e) => setTexto(e.target.value)} value={texto} />

// onSubmit — formulario (siempre preventDefault)
<form onSubmit={(e) => {
  e.preventDefault();   // evita recarga de página
  enviar(texto);
}}>

// Pasar el manejador como prop (event handlers como props)
function Boton({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>;
}
<Boton onClick={() => setCuenta(0)}>Reset</Boton>
Cuándo el estado en un componente ya no es suficiente. Hay cuatro problemas frecuentes: estado mal estructurado, estado que necesitan varios componentes, prop drilling (pasar props 5 niveles), y lógica de actualización muy compleja. Cada uno tiene su solución.
Principio DRY
Qué no guardar en estado
Si un valor se puede calcular a partir del estado o las props, no es estado — es estado derivado. Calcularlo en el render, no guardarlo en useState.
Guardar estado derivado crea duplicación y fuente de verdad ambigua: si nombre cambia, ¿se actualiza también nombreCompleto? Con la sincronización manual siempre hay un momento en que los dos están desincronizados.
// MAL — nombreCompleto es derivado, no estado propio
const [nombre, setNombre] = useState('Ana');
const [apellido, setApellido] = useState('García');
const [nombreCompleto, setNombreCompleto] = useState('Ana García'); // ← innecesario

// BIEN — se calcula en el render, siempre sincronizado
const [nombre, setNombre] = useState('Ana');
const [apellido, setApellido] = useState('García');
const nombreCompleto = `${nombre} ${apellido}`; // ← calculado, no estado

// Otros ejemplos de estado derivado (no usar useState):
const totalItems = carrito.length;          // derivado de carrito
const precioTotal = items.reduce(...);      // derivado de items
const itemsFiltrados = items.filter(...);   // derivado de items + filtro
Principio
Reglas de estructura del estado
Cinco reglas de la documentación oficial para tener un estado bien diseñado.
// 1. Agrupa lo que siempre cambia junto
// MAL:
const [x, setX] = useState(0);
const [y, setY] = useState(0);
// BIEN:
const [pos, setPos] = useState({ x: 0, y: 0 });

// 2. Evita el estado contradictorio
// MAL: cargando=true y error="algo" al mismo tiempo → imposible en prod
// BIEN: un solo campo 'status': 'idle' | 'loading' | 'success' | 'error'

// 3. Evita duplicar datos del padre
// MAL: recibir usuario como prop y también guardarlo en estado propio
// BIEN: usar directamente la prop

// 4. Evita anidado profundo — aplana la estructura
// MAL: { pais: { ciudad: { barrio: { calle: '...' } } } }
// BIEN: { pais: 'ES', ciudad: 'Valencia', barrio: 'Ruzafa', calle: '...' }
Compartir estado
Levantar el estado al padre
Cuando dos componentes hermanos necesitan compartir datos o sincronizarse, el estado sube al ancestro común más cercano. Los hijos reciben el valor y los callbacks como props.
El flujo de datos en React es unidireccional (solo hacia abajo). Si dos hermanos comparten estado local propio, están desincronizados. La única forma de sincronizarlos es que el padre sea el propietario del estado y lo comparta hacia abajo.
// Dos paneles que se sincronizan — solo uno abierto a la vez
function Acordeon() {
  const [abierto, setAbierto] = useState(null); // ← estado en el padre

  return (
    <>
      <Panel titulo="Servicios" esActivo={abierto === 0} onToggle={() => setAbierto(0)} />
      <Panel titulo="Precios"   esActivo={abierto === 1} onToggle={() => setAbierto(1)} />
    </>
  );
}

// Panel no tiene estado propio — recibe todo del padre
function Panel({ titulo, esActivo, onToggle }) {
  return (
    <div>
      <button onClick={onToggle}>{titulo}</button>
      {esActivo && <div>Contenido...</div>}
    </div>
  );
}
Prop drilling
Context — evitar pasar props en cadena
Cuando una prop tiene que pasar por 4 componentes intermedios que no la usan, Context la hace disponible en cualquier descendiente sin pasarla manualmente.
Context no es un sustituto de las props — es para datos que son genuinamente "globales" al árbol: el locale activo, el usuario autenticado, el tema visual. Para datos que solo comparten 2-3 componentes cercanos, levanta el estado y pasa props normales.
// 1. Crear el contexto
const LocaleContext = createContext('es');

// 2. Proveedor — envuelve el árbol donde quieres que esté disponible
function App() {
  const [locale, setLocale] = useState('es');
  return (
    <LocaleContext.Provider value={locale}>
      <Pagina />   {/* Pagina → Seccion → Componente → ... */}
    </LocaleContext.Provider>
  );
}

// 3. Consumir en cualquier descendiente (sin pasar props)
function BotonIdioma() {
  const locale = useContext(LocaleContext);
  return <span>Idioma: {locale}</span>;
}
Lógica compleja
useReducer — consolidar actualizaciones
Sustituye a useState cuando la lógica de actualización es compleja: múltiples sub-valores relacionados, actualizaciones que dependen del estado anterior, lógica repartida en muchos handlers.
Con useState, si tienes 5 acciones distintas que modifican el mismo estado, la lógica queda dispersa en 5 handlers. Con useReducer, toda la lógica vive en una función pura (reducer) que es fácil de testear, leer y compartir.
// El reducer — función pura: (estadoActual, acción) → nuevoEstado
function reducer(state, action) {
  switch (action.type) {
    case 'añadir':   return [...state, action.item];
    case 'eliminar': return state.filter(i => i.id !== action.id);
    case 'limpiar':  return [];
    default: throw new Error(`Acción desconocida: ${action.type}`);
  }
}

function Lista() {
  const [items, dispatch] = useReducer(reducer, []);

  return (
    <>
      <button onClick={() => dispatch({ type: 'añadir', item: nuevoItem })}>
        Añadir
      </button>
      <button onClick={() => dispatch({ type: 'limpiar' })}>
        Limpiar todo
      </button>
    </>
  );
}
HerramientaCuándoNo usar cuando
useStateEstado simple e independiente de un componente. La mayoría de casos.La lógica de actualización es muy compleja o hay muchas acciones.
useReducerEstado con múltiples sub-valores relacionados. Muchas acciones distintas. Necesitas testear la lógica por separado.Estado simple con 1-2 valores. No añade valor frente a useState.
Levantar estadoDos componentes hermanos necesitan los mismos datos o sincronizarse.Están muy lejos en el árbol — crear prop drilling de 5+ niveles.
ContextDatos globales al árbol: locale, usuario autenticado, tema.Datos que solo necesitan 2-3 componentes cercanos — usa props normales.
Los hooks son funciones que empiezan por use. Solo pueden llamarse en el nivel superior de un componente o de otro hook — nunca dentro de condicionales, bucles o funciones anidadas. Esta regla garantiza que React siempre los llama en el mismo orden.
useRef referencia al DOM · valor sin re-render
Cuando necesitas acceso directo a un elemento del DOM (medir su altura, hacer .focus(), inicializar una librería) o guardar un valor que no debe provocar re-render cuando cambia.
A diferencia de useState, cambiar ref.current no redibuja el componente. Útil para valores de "infraestructura" que el usuario no necesita ver: un timer ID, una instancia de mapa Leaflet, el valor anterior de un estado.
import { useRef } from 'react';

// Caso 1 — referencia al DOM
function Formulario() {
  const inputRef = useRef<HTMLInputElement>(null);

  const enfocar = () => inputRef.current?.focus(); // ?. porque puede ser null inicialmente

  return <>
    <input ref={inputRef} type="text" />
    <button onClick={enfocar}>Enfocar</button>
  </>;
}

// Caso 2 — array de refs (uno por elemento de lista)
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
<div ref={(el) => { itemRefs.current[i] = el; }}>

// Caso 3 — valor que no provoca re-render (ej: ID de timer)
const timerId = useRef<number | null>(null);
timerId.current = setTimeout(() => { ... }, 1000);
clearTimeout(timerId.current);
useEffect efectos después del paint
Cuando necesitas sincronizar el componente con algo externo: llamar a una API, suscribirte a eventos del DOM, inicializar una librería que necesita el DOM real, conectar a un websocket.
Los efectos corren después de que React haya pintado en pantalla. El usuario ya ve el resultado antes de que el efecto corra. Si el efecto tarda mucho (un fetch lento), el componente ya está visible con el estado de carga — la UX no se bloquea.
import { useEffect, useState } from 'react';

function Perfil({ userId }: { userId: number }) {
  const [datos, setDatos] = useState(null);

  useEffect(() => {
    // Este código corre DESPUÉS de que React pinte
    fetch(`/api/usuario/${userId}`)
      .then(r => r.json())
      .then(d => setDatos(d));

    // Función de limpieza — corre antes del próximo efecto o al desmontar
    return () => {
      // Cancelar la petición, desuscribirse, limpiar timers, etc.
    };
  }, [userId]); // ← dependencias: re-ejecuta cuando userId cambia

  // Variantes del array de dependencias:
  // []          → solo corre al montar (nunca más)
  // [a, b]      → corre al montar y cuando a o b cambian
  // (sin array) → corre en cada render — casi siempre un bug

  return <div>{datos?.nombre}</div>;
}
useLayoutEffect efectos antes del paint
Solo cuando necesitas medir o modificar el DOM antes de que el usuario lo vea. Si lo haces con useEffect, el usuario verá un parpadeo (el estado incorrecto un frame antes de corregirse).
Corre síncronamente después del commit de React pero antes de que el navegador pinte. Es más caro que useEffect porque bloquea el paint. Úsalo solo para animaciones y medidas del DOM que necesitan ser invisibles para el usuario.
import { useLayoutEffect, useRef } from 'react';

// Animar altura de un acordeón sin parpadeo
function Acordeon({ abierto, children }) {
  const contenedorRef = useRef<HTMLDivElement>(null);

  useLayoutEffect(() => {
    const el = contenedorRef.current;
    if (!el) return;
    // Cambia la altura ANTES de que el usuario vea el render
    // → sin parpadeo, animación suave
    el.style.maxHeight = abierto ? `${el.scrollHeight}px` : '0';
  }, [abierto]); // re-ejecuta cada vez que cambia 'abierto'

  return (
    <div ref={contenedorRef} style={{ overflow: 'hidden', transition: 'max-height 0.3s' }}>
      {children}
    </div>
  );
}

// Regla práctica:
// Empieza siempre con useEffect.
// Si ves un parpadeo visual, cambia a useLayoutEffect.
useId IDs únicos para accesibilidad
Cuando necesitas conectar elementos de formulario o ARIA con id y htmlFor / aria-controls, y el componente puede instanciarse varias veces en la misma página.
Si inventas IDs estáticos (id="faq-1") y tienes dos instancias del componente FAQ en la misma página, los IDs se repiten — eso rompe la accesibilidad y los lectores de pantalla. useId garantiza un ID único por instancia, sea cual sea el número de instancias.
import { useId } from 'react';

function CampoFormulario({ label, tipo = 'text' }) {
  const id = useId(); // genera: ':r0:', ':r1:', etc. — único por instancia

  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} type={tipo} />
    </div>
  );
}

// Dos instancias — cada una tiene su ID único, no hay conflictos
<CampoFormulario label="Nombre" />
<CampoFormulario label="Email" tipo="email" />

// En acordeones — conecta botón con contenido (aria)
const uid = useId().replace(/:/g, '');
<button aria-controls={`${uid}-panel-${i}`} aria-expanded={abierto[i]}>
<div id={`${uid}-panel-${i}`}>
Custom hooks — use... extraer lógica reutilizable
Cuando la misma lógica con hooks aparece en dos o más componentes. Un custom hook extrae esa lógica en una función reutilizable con su propio estado.
Los custom hooks no son una API especial de React — son simplemente funciones que empiezan por use y pueden llamar a otros hooks. La convención del prefijo es obligatoria: React usa el nombre para aplicar las reglas de hooks (linters, herramientas).
// Custom hook — extrae lógica de fetch reutilizable
function useFetch<T>(url: string) {
  const [datos, setDatos] = useState<T | null>(null);
  const [cargando, setCargando] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    setCargando(true);
    fetch(url)
      .then(r => r.json())
      .then(d => { setDatos(d); setCargando(false); })
      .catch(e => { setError(e.message); setCargando(false); });
  }, [url]);

  return { datos, cargando, error };
}

// Uso — limpio, sin repetir la lógica de fetch
function Apartamentos() {
  const { datos, cargando, error } = useFetch('/api/apartamentos');
  if (cargando) return <Spinner />;
  if (error) return <p>Error: {error}</p>;
  return <Lista items={datos} />;
}
HookPara quéProvoca re-render
useStateDatos que cambian y que la UI debe reflejarSí — al llamar al setter
useReducerComo useState pero con lógica compleja centralizadaSí — al hacer dispatch
useRefReferencia al DOM · Valores que no deben redibujarNo
useEffectSincronizar con sistemas externos, fetch, suscripcionesIndirectamente (si llama setState)
useLayoutEffectMedir/modificar el DOM antes del paint, sin parpadeoIndirectamente
useIdIDs únicos por instancia para accesibilidadNo
useContextLeer datos de un Context sin prop drillingSí — cuando el Context cambia
useCallbackMemorizar una función para no recrearla en cada renderNo
useMemoMemorizar un valor calculado caro de computarNo
El método oficial de la documentación de React para atacar cualquier UI nueva. 5 pasos en orden. La clave: construir primero sin estado, añadir estado solo al final.
  1. 1
    Separa la UI en una jerarquía de componentes
    Dibuja cajas en el boceto. Cada caja que hace una sola cosa es un componente candidato (principio de responsabilidad única). Si un componente crece demasiado, se divide. El resultado es una jerarquía en árbol.
    // Ejemplo: buscador de apartamentos
    App
    ├── BarraBusqueda       ← input de texto + checkbox
    └── TablaApartamentos  ← tabla con datos filtrados
        ├── FilaCategoria  ← fila de encabezado (Playas, Centros...)
        └── FilaApartamento ← una fila por apartamento
  2. 2
    Construye una versión estática primero
    Implementa los componentes con props y datos fijos — sin useState, sin eventos. Solo renderizado. Esto requiere escribir mucho código pero pensar poco (sin interactividad aún). El estado se añade en el paso 3.
    // Versión estática — datos hardcodeados, sin estado
    function App() {
      return <TablaApartamentos apartamentos={DATOS_EJEMPLO} />;
    }
    
    function TablaApartamentos({ apartamentos }) {
      return (
        <table>
          {apartamentos.map(apt => (
            <FilaApartamento key={apt.id} apt={apt} />
          ))}
        </table>
      );
    }
    // Sin useState. Sin onClick. Solo JSX y props.
  3. 3
    Identifica el estado mínimo necesario
    Pregúntate por cada dato: ¿se puede calcular a partir de otra cosa? → no es estado. ¿Es siempre igual (constante)? → no es estado. ¿Viene de un padre como prop? → no es estado. Lo que queda es el estado real.
    // Datos del ejemplo:
    // - Lista de apartamentos     → prop (viene del padre) — NO es estado
    // - Texto de búsqueda         → cambia, no se calcula → SÍ es estado
    // - Checkbox "solo disponibles" → cambia, no se calcula → SÍ es estado
    // - Lista filtrada             → se calcula de las dos anteriores → NO es estado
    
    const [textoBusqueda, setTextoBusqueda] = useState('');
    const [soloDisponibles, setSoloDisponibles] = useState(false);
    
    // Derivado — se calcula en render, no es estado
    const aptsFiltrados = apartamentos
      .filter(a => !soloDisponibles || a.disponible)
      .filter(a => a.nombre.toLowerCase().includes(textoBusqueda.toLowerCase()));
  4. 4
    Decide dónde vive cada estado
    Para cada estado: ¿qué componentes lo necesitan? Sube hasta el ancestro común más cercano. Si no existe un componente apropiado, crea uno nuevo solo para contener ese estado.
    // textoBusqueda y soloDisponibles los necesitan:
    // - BarraBusqueda (para mostrar el valor actual del input)
    // - TablaApartamentos (para filtrar)
    //
    // Ancestro común: App → el estado vive en App
    
    function App() {
      const [textoBusqueda, setTextoBusqueda] = useState('');
      const [soloDisponibles, setSoloDisponibles] = useState(false);
    
      return (
        <>
          <BarraBusqueda texto={textoBusqueda} soloDisp={soloDisponibles} />
          <TablaApartamentos texto={textoBusqueda} soloDisp={soloDisponibles} />
        </>
      );
    }
  5. 5
    Añade el flujo de datos inverso
    Los hijos no pueden modificar el estado del padre directamente. El padre les pasa callbacks como props. El hijo llama al callback cuando el usuario interactúa — el estado cambia en el padre y baja de nuevo al hijo.
    // El padre pasa los setters como callbacks
    function App() {
      const [texto, setTexto] = useState('');
      const [soloDisp, setSoloDisp] = useState(false);
    
      return (
        <>
          <BarraBusqueda
            texto={texto}
            soloDisp={soloDisp}
            onTexto={setTexto}        // ← callback hacia arriba
            onSoloDisp={setSoloDisp}  // ← callback hacia arriba
          />
          <TablaApartamentos texto={texto} soloDisp={soloDisp} />
        </>
      );
    }
    
    // El hijo llama al callback — no modifica estado directamente
    function BarraBusqueda({ texto, soloDisp, onTexto, onSoloDisp }) {
      return (
        <>
          <input value={texto} onChange={e => onTexto(e.target.value)} />
          <input type="checkbox" checked={soloDisp} onChange={e => onSoloDisp(e.target.checked)} />
        </>
      );
    }
PasoQué hacesSin estado todavía
1 · JerarquíaDibuja cajas en el boceto, nombra los componentes
2 · EstáticoImplementa con props y datos fijos, sin eventos
3 · Estado mínimoIdentifica qué cambia que NO es calculable ni propNo (primer useState)
4 · Dónde viveSube el estado hasta el ancestro común que lo necesiteNo
5 · Flujo inversoPasa callbacks de padre a hijo, hijo los llamaNo
La trampa más común: añadir estado demasiado pronto. Los pasos 1 y 2 son estáticos a propósito. Entender primero la estructura de componentes y el flujo de datos hace que decidir el estado en el paso 3 sea obvio.