Product Craft Bible
Sistema y Arquitectura
Inicio Design Systems Sistema y Arquitectura
Design Systems

Sistema y Arquitectura

11 reglas auditoria de productoworkflow interno
18

Sistema y Arquitectura

11 reglas
194

DS docs deben reflejar el producto, no superarlo

Si el design system tiene composiciones más cuidadas que las páginas reales, el DS es vitrina y el producto es la tienda fea. Los docs deben mostrar componentes exactamente como lucen en producción: mismo padding, mismos colores, mismas constraints. Cuando hay gap entre docs y producto, el equipo deja de confiar en el DS porque "así no se ve en la app". La solución no es bajar la calidad del DS, sino subir la del producto hasta que sean 1:1.

auditoria de producto
Preferir
DS Docs
Card Component
Empty state
Producto real
Card Component
Empty state
live from production
DS refleja el producto real: zero gap entre docs y producción. Ambos paneles son identicos porque el DS documenta lo que existe, no lo que aspira.
Evitar
DS Docs
Card Component
Empty state elaborado
Producto real
card plana
No hay nada aquí
DS como vitrina: la realidad diverge. El equipo deja de confiar en los docs porque "así no se ve en la app".
195

z-index: solo tokens, lint contra numéricos

Los z-index numéricos generan guerras de escalación: cada desarrollador suma un número más alto que el anterior hasta llegar a 99999 sin lógica ni jerarquía. Una escala canonica de tokens CSS (--z-dropdown, --z-modal, --z-tooltip, --z-overlay) hace predecible la jerarquía de capas. Combinada con una regla de lint que rechace valores numéricos directos, se elimina la posibilidad de colisiones silenciosas.

auditoria de producto
Preferir
tokens/z-index.css
:root {
  --z-dropdown: 10;
  --z-modal: 50;
  --z-tooltip: 60;
  --z-overlay: 70;
}
Modal.astro
.modal { z-index: var(--z-modal); }
stylelint: no numeric z-index
declaration-property-value-allowed-list:
  z-index: ["/^var\\(--z-/"]
Escala canonica con tokens: predecible y mantenible
Evitar
globals.css
.modal     { z-index: 9999; }
.dropdown  { z-index: 10000; }
.tooltip   { z-index: 99999; }
z-index numéricos: guerra de escalación sin fin
196

Cuando 3+ rondas de fixes fallan: reescribir

Si un archivo acumula más de 3 commits de fixes contradictorios y sigue roto, cada parche nuevo genera regresiones en cascada. Reescribir desde cero, con conocimiento de que patrones fallaron, es más barato que diagnosticar capas de parches superpuestos. La reescritura informada usa los fixes fallidos como spec negativo: sabes exactamente que no funciona, así que el código nuevo evita esas trampas desde el inicio.

auditoria de producto
Preferir
Rewrite: modal.tsx
Después de 3 fixes fallidos, un commit "Rewrite" reemplaza el archivo completo. Antes: 340 líneas de parches acumulados. Después: 120 líneas limpias.
-65%
líneas
0
bugs post-rewrite
3+ rondas fallidas = reescribir desde cero
Evitar
git log --oneline modal.tsx
a3f21cfix: modal z-index
b7e04dfix: modal again
c9a18ffix: modal overlay conflict
d2b33afix: revert modal fix
e5c77bfix: modal final v2
5 fixes contradictorios: el archivo ya es inmantenible
197

Grid > Flex cuando hay absolute children

Cuando un contenedor flex tiene hijos con position:absolute (tooltips, coachmarks, popovers), el sizing intrinseco de flex entra en conflicto con el posicionamiento absoluto: el tooltip se desplaza, empuja siblings o queda cortado. CSS Grid con áreas nombradas resuelve el problema porque los absolute children no participan en el calculo de columnas, y las grid áreas proporcionan anclas explicitas para posicionar elementos flotantes de forma predecible.

auditoria de producto
Preferir
Formato invalido. Revisa el dominio.
Tooltip anclado al grid área, no afecta el flujo.
label input icon
Grid con áreas explicitas: absolute children no rompen el flujo
Evitar
Formato invalido. Revisa el dominio.
El tooltip empuja el icono o se corta por overflow.
Flex + absolute children: layout impredecible
198

Hover rojo = irreversible solamente

El color rojo en hover debe reservarse exclusivamente para acciones irreversibles. Cuando todo tiene hover rojo, el usuario pierde la capacidad de distinguir entre quitar algo del carrito (trivialmente reversible), descartar un borrador (reversible pero con perdida de trabajo) y eliminar una cuenta (irreversible). Un sistema semántico asigna hover neutro a lo reversible, ambar a lo que implica perdida recuperable y rojo solo a lo verdaderamente destructivo.

auditoria de producto
Preferir
Quitar del carrito
reversible
Descartar borrador
con perdida
Eliminar cuenta
irreversible
Rojo solo para irreversible: semántica clara de riesgo
Evitar
Quitar del carrito
reversible
Descartar borrador
reversible
Eliminar cuenta
irreversible
Rojo en todo: el usuario no distingue que es peligroso de verdad
199

Wizard footer mobile: max tap área para primarias

Atras/Siguiente son la acción más frecuente en un wizard. En mobile ambos botones deben ocupar el 100% del ancho disponible con flex:1 y min-height:48px, eliminando toda zona muerta. La primaria (Siguiente) lleva fondo accent y font-weight 600; la secundaria (Atras) es ghost/outlined. Acciones terciarias (Descartar, Guardar borrador) pasan a icon-only 32x32 en viewports estrechos. El status pill se oculta progresivamente bajo 540px.

auditoria de producto
Preferir
Nueva solicitud
Paso 2 de 4
Nombre completo
CURP
Full-width 1:1: máximo tap área para la acción más frecuente
Cada botón ocupa el 50% del ancho, min-height 48px
Evitar
Nueva solicitud
Paso 2 de 4
Nombre completo
CURP
Botones pequeños: la acción más frecuente tiene el tap target más chico
~120px de ancho, zona muerta en el 70% del footer
200

motion-safe para animaciones ambient

Animaciones ambient (orbs, halos, pulse decorativos) llevan clase .motion-safe para bypass del kill global de prefers-reduced-motion. Sin ese gate, animaciones decorativas corren para todos los usuarios, incluyendo quienes experimentan mareo o malestar con movimiento en pantalla. La clase .motion-safe permite que el sistema detenga solo lo disruptivo (parallax, page transitions, scale grande) mientras conserva la vida visual de elementos ambient que no causan malestar.

auditoria de producto
Preferir
Con motion (orbs animados suavemente)
Sin motion (orbs estaticos, visibles)
.motion-safe { animation: float 6s ease infinite }
motion-safe: bypass selectivo para ambient, respetando preferencia
Evitar
Ambient orbs prefers-reduced-motion: reduce ignorado
Animaciones decorativas sin gate: mareo para usuarios sensibles
201

Ripple loops: ramp suave, nunca pop en reset

Los keyframes de ripple que arrancan a opacity: .7 desde scale 1 producen un "pop" visible en cada ciclo. Ramp correcto: 0% opacity 0 > 8% .55 > 40% .25 > 100% 0. El reset de scale ocurre con opacity 0 (invisible). Duración 6s para ritmo calmado.

auditoria de producto
Preferir
Ripple animado (ramp suave):
Reset invisible a opacity 0
Ciclo calmado 6s
Keyframes correctos:
@keyframes ripple {
  0% {
    opacity: 0;
    scale: 1;
  }
  8% { opacity: .55; }
  40% { opacity: .25; }
  100% {
    opacity: 0;
    scale: 2.5;
  }
}
Empieza y termina en opacity 0, el reset es completamente invisible. Ramp natural da sensación de pulso orgánico.
Evitar
Ripple animado (pop en reset):
Flash visible en cada
inicio del ciclo
Keyframes erroneos:
@keyframes ripple {
  0% {
    opacity: .7;
    scale: 1;
  }
  100% {
    opacity: 0;
    scale: 2.5;
  }
}
opacity: .7 al inicio, cuando el ciclo reinicia aparece un "pop" de opacidad visible
202

Conformidad legal LATAM desde día 1

Publicar /legal/privacy, /legal/terms, /legal/cookies como superficie global con footer link. Consentimiento biometrico explicito antes de captura de voz/rostro. Banner de disclosure "Soy un asistente con IA" en chatbot. Sin esto, no cumple LFPDPPP (MX), Ley 172-13 (DOM) ni Ley 1581/2012 (CO).

auditoria de producto
Preferir
MiApp
Asistente Soy un asistente con IA
Hola, puedo ayudarte con tus consultas...
Footer legal + disclosure de IA + consentimiento biometrico explicito: cumple LFPDPPP, Ley 172-13 y Ley 1581
Evitar
MiApp
Contenido de la app...
Incumplimientos:
LFPDPPP (MX)
Hasta MXN 320K por aviso de privacidad ausente
Ley 1581 (CO)
Hasta 2000 SMMLV sin politica de tratamiento
Footer sin enlaces legales: expuesto a sanciones LFPDPPP, Ley 172-13 y Ley 1581/2012
203

Metodologia de auditoria: formato canonico por hallazgo

Cada hallazgo sigue: Problema (que falla) > Razón (causa raiz sistemica, no sintoma) > Solución (patrón/principio, no código) > Output esperado (verificación observable: grep, Lighthouse, comportamiento manual reproducible). Severidades: Critica / Alta / Media / Baja.

auditoria de producto
Preferir
Hallazgo #A-07 Alta
PROBLEMA CTA principal no responde a click durante carga inicial de página RAZÓN z-index del CTA (z:10) queda bajo overlay de loading (z:9999) que persiste 400ms SOLUCIÓN Usar token --z-cta: 70 y eliminar z-index numérico del overlay OUTPUT grep -r "z-index" src/ → 0 valores numéricos
Click en CTA funciona sin esperar
Hallazgo #C-02 Critica
PROBLEMA Modal de eliminación accesible sin autenticación RAZÓN Guard de sesión solo en ruta, no en componente modal SOLUCIÓN Verificar token en onOpen del modal, redirigir si ausente OUTPUT curl -X DELETE /api/resource sin cookie → 401
Modal no renderiza sin sesión activa
Formato canonico: cada hallazgo tiene causa raiz, solución de principio y output verificable
Evitar
Bug #101 sin severidad
"el botón de pago no funciona a veces"
Fix: revisar el onclick. Sin causa raiz
Bug #102 sin severidad
"modal se ve raro en mobile"
arregle el css un poco. Sin output esperado
Sin causa raiz sistemica, sin output verificable, sin severidad: no se puede priorizar ni verificar si el fix funciono.
Hallazgos sin formato: imposibles de priorizar, reproducir o verificar
204

Agentation montado en dev en cada proyecto

Instalar agentation (npm) como devDependency y montar el componente React en modo dev en cada sitio web. Renderizar solo cuando import.meta.env.DEV es true, justo antes del cierre de <body>. Nunca llega a producción. El guard de entorno permite que el tree-shaking de Astro/Vite elimine todo el código de agentation del bundle final, sin dejar rastro en el HTML ni en el JS que recibe el usuario.

workflow interno
Preferir
DEV (localhost:4001)
localhost:4001
Claude
1
Widget visible
PROD (miempresa.com)
miempresa.com
sin widget
Widget ausente
// Layout.astro, CON guard de entorno
const isDev = import.meta.env.DEV;
// Astro: DEV=true solo en astro dev
{isDev && <AgentationDev client:only="react" />}
// Build de prod: no incluye ni una línea de agentation
El guard con import.meta.env.DEV garantiza que Agentation no aparezca en el bundle de producción. Tree-shaking lo elimina completamente.
Guard DEV: Agentation visible solo en localhost, eliminado por tree-shaking en el build de producción
Evitar
https://miempresa.com producción
Agentation
Ask Claude...
1
2
Herramienta dev visible en prod
// Layout.astro, SIN guard de entorno
<AgentationDev client:only="react" />
// Siempre renderiza, en dev Y en prod
Sin import.meta.env.DEV: el widget de Agentation llega a producción y los usuarios del cliente lo ven