FarmatodoBrand Manual
Agent bundle · Markdown

Farmatodo Brand Manual — todas las secciones

Esta página está optimizada para agentes (LLMs, asistentes y herramientas automatizadas). Cada sección está renderizada como Markdown plano dentro de bloques <pre> para que pueda ser copiada o scrapeada sin pérdida de formato. Usa los anclas (#slug) para enlazar a una sección concreta.

Section · principles

00 · Principios de UI

# Principios de UI — Especificación completa

## 1. Marco
6 principios fundamentales que guían toda decisión de diseño. Cada
componente del sistema debe poder justificarse a través de al menos 2 de
ellos. Cuando hay conflicto, el orden de precedencia es:
**P4 > P1 > P2 > P3 > P5 > P6**.

---

## P1 · Claridad sobre densidad
**Aplica a**: Scorecards, Tipografía, Tablas, Dashboards.

Espacio negativo > información extra. Si un dato no es accionable, ocultarlo.
Cards con padding mínimo 24px. Tablas con altura de fila ≥ 40px. Una métrica
por scorecard, no múltiples.

**Test**: ¿Puedes eliminar el 30% del contenido sin perder función? Hazlo.

---

## P2 · Jerarquía con tipografía, no con color
**Aplica a**: Tipografía, Color, Componentes.

Usar peso (300/400/500/700) y tamaño antes que tono. El color es funcional
(estado, brand), no jerárquico. Un solo color de texto por bloque con
variaciones `foreground` / `muted-foreground`.

**Test**: Si imprimes la pantalla en B/N, ¿la jerarquía sigue siendo clara?

---

## P3 · Color con intención
**Aplica a**: Color, Data viz, Estados.

**Un acento por vista.** AI siempre `#1465FF`. CTAs primarios `#0040B9`.
Estados semánticos solo cuando hay estado real. No usar color para decorar.

**Test**: ¿Cada uso de color tiene un propósito comunicativo? Si no → quitar.

---

## P4 · AI como copiloto, no protagonista
**Aplica a**: AI components, Agent demo.

AiSpark acompaña, nunca domina. AI se integra al flujo existente, no lo
reemplaza. Confirmación obligatoria antes de mutar datos. Razonamiento
visible (no caja negra).

**Test**: ¿El usuario mantiene el control? ¿Puede entender qué hizo el agente?

---

## P5 · Iconografía consistente
**Aplica a**: Iconografía, Componentes.

Lucide stroke 1.75 en todo el sistema. Pétalos en múltiplos de 15°. AiSpark
exclusivo para AI. No mezclar familias de iconos.

**Test**: ¿Todos los iconos del flujo vienen del mismo set con el mismo stroke?

---

## P6 · Composición con espacio
**Aplica a**: Logo, Producto, Layouts.

Áreas de seguridad respetadas. Padding generoso (24px+ en cards). Ritmo
modular (escala 4px). Separación entre secciones ≥ 48px.

**Test**: ¿Todo elemento tiene aire suficiente? ¿El ritmo es consistente?

---

## 2. Aplicación cruzada
| Componente | Principios principales |
|---|---|
| Scorecard | P1, P2, P6 |
| Botón primario | P3, P5, P6 |
| AgentBubble | P4, P3, P5 |
| ConfirmCard | P4, P3, P1 |
| Sidebar | P1, P5, P6 |
| Tabla | P1, P2, P6 |
| Logo | P5, P6 |
| Data viz | P1, P2, P3 |

## 3. Anti-patrones (rompen los principios)
- ❌ Color para indicar jerarquía (rompe P2).
- ❌ AI omnipresente con sparkles decorativos (rompe P4).
- ❌ Mezclar iconos de Lucide + Heroicons (rompe P5).
- ❌ Cards sin padding interno (rompe P6).
- ❌ 5 series de color sin orden (rompe P1, P3).
- ❌ Mutaciones AI sin confirmación (rompe P4).

## 4. Decisión guiada
Cuando dudes sobre una decisión de diseño:
1. ¿Cuál es el principio dominante para este contexto?
2. ¿Mi solución lo refuerza o lo viola?
3. Si lo viola, ¿hay un principio de mayor precedencia que lo justifique?
4. Si no → cambiar la solución.

## 5. QA Checklist
- [ ] Cada componente respeta al menos 2 principios.
- [ ] Decisiones de color justificables por P2/P3.
- [ ] Decisiones AI justificables por P4.
- [ ] Sin anti-patrones documentados.
- [ ] Jerarquía sigue funcionando en B/N.
Section · colors

01 · Color

# Color — Especificación completa

## 1. Filosofía
Color con intención: **un acento por vista**. La jerarquía la define la
tipografía y el espacio, no el color. Los azules son el ADN de marca; los
estados son funcionales y no deben competir con la identidad.

## 2. Paleta de marca
| Token | Hex | OKLCH (referencia) | Uso primario |
|---|---|---|---|
| Azul Farmatodo | `#002858` | oklch(0.262 0.094 256) | Color principal, headers, fondos brand, CTAs sobre claro |
| Azul Profundo  | `#0040B9` | oklch(0.418 0.197 263) | CTAs primarios, énfasis tipográfico, links |
| Azul Eléctrico | `#1465FF` | oklch(0.572 0.220 263) | **Reservado para AI** — sparkle, AiSpark, highlights agénticos |
| Azul Cielo     | `#7B8FE6` | oklch(0.660 0.103 269) | Estado *waiting*, decorativo suave, ilustración |

## 3. Estados semánticos
| Estado | Hex | Uso | Pareja texto |
|---|---|---|---|
| Éxito  | `#27AE60` | Confirmaciones, badges *done* | Blanco o `#0E5F33` |
| Aviso  | `#F39C12` | Tareas en progreso, alertas no críticas | `#5C3B00` |
| Error  | `#C2410C` | Errores, validaciones fallidas, destructivo (naranja, **no rojo**) | Blanco |
| Info   | `#1465FF` | Mensajes informativos AI | Blanco |

## 4. Neutros (tokens semánticos)
Definidos en `src/styles.css` en formato `oklch`:
```css
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --border: oklch(0.922 0 0);
  --primary: oklch(0.262 0.094 256);   /* #002858 */
  --primary-foreground: oklch(0.985 0 0);
}
```

Mapeo a Tailwind: `bg-background`, `text-foreground`, `text-muted-foreground`,
`border-border`, `bg-primary`, `text-primary-foreground`.

## 5. Reglas de combinación
1. Máximo **2 azules brand** por composición.
2. `#1465FF` solo en componentes AI (AiSpark, agent bubble eyebrow,
   highlights de "investigar en chat").
3. Estados (success/warn/error) **no** se mezclan entre sí en un mismo bloque.
4. Gradientes solo en hero/marketing, jamás en UI funcional.

## 6. Contraste mínimo (WCAG)
| Elemento | Ratio mínimo |
|---|---|
| Texto cuerpo (≤ 18px) | 4.5:1 (AA) |
| Texto grande (≥ 18px bold o 24px) | 3:1 (AA) |
| Componentes UI (bordes, iconos) | 3:1 |
| Texto sobre estado | 4.5:1 |

Verificar con herramientas como `npx @tailwindcss/contrast` o Stark.

## 7. Do / Don't
**DO**
- Usar tokens semánticos (`bg-primary`) en lugar de hex literales.
- Reservar `#1465FF` para AI.
- Usar `#0040B9` para CTAs primarios sobre fondo claro.

**DON'T**
- ❌ Hex literales en componentes (excepto specs documentales y tokens hard-coded).
- ❌ Más de 2 azules en un bloque.
- ❌ Rojo para errores o destructivo — usamos **naranja** (`#C2410C` / `#FB923C`).
- ❌ Verde para series neutrales en data viz (reservado para deltas positivos).
- ❌ Texto `text-muted-foreground` sobre `bg-muted` (contraste insuficiente).

## 8. Snippet — definir un nuevo token
```css
/* src/styles.css */
:root {
  --ai-glow: oklch(0.572 0.220 263 / 0.12);
}
@theme inline {
  --color-ai-glow: var(--ai-glow);
}
/* Uso: bg-ai-glow */
```

## 9. QA Checklist
- [ ] Todos los textos pasan AA contra su fondo.
- [ ] AiSpark / acentos AI usan exclusivamente `#1465FF`.
- [ ] CTAs primarios usan `#0040B9` (no `#002858`).
- [ ] Sin hex literales en componentes nuevos (usar tokens).
- [ ] Estados semánticos respetan paleta (no inventar tonos).
- [ ] Estados de error/destructivo en **naranja** (`#C2410C`), nunca rojo.
Snippets · 2

Componentes copiables

Endpoints públicos · usar desde otro proyecto

Cada URL devuelve Markdown plano (mismo código que ves arriba). Pega la URL en otro agente / proyecto para que la haga fetch e inserte el snippet sin remixar este proyecto.

colors · todo · desktop
colors · todo · mobile
Tokens de color (CSS)
Swatch component
Tokens de color (CSS)
Variables semánticas en src/styles.css. Usa siempre `bg-primary` / `text-primary` en vez de hex.
primary
accent
primary/10
foreground
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.18 0.02 250);

  --primary: oklch(0.45 0.19 260);          /* #0040B9 */
  --primary-foreground: oklch(1 0 0);

  --accent: oklch(0.62 0.20 260);           /* #1465FF */
  --accent-foreground: oklch(1 0 0);

  --muted: oklch(0.96 0.005 260);
  --muted-foreground: oklch(0.45 0.01 260);
  --border: oklch(0.92 0.005 260);
}
Swatch component
Primary
#0040B9
Accent
#1465FF
Soft
#E6EEFB
export function Swatch({ hex, name }: { hex: string; name: string }) {
  return (
    <div className="space-y-2">
      <div
        className="w-full aspect-square rounded-xl border border-border"
        style={{ backgroundColor: hex }}
      />
      <div>
        <div className="text-sm font-medium">{name}</div>
        <div className="font-mono text-[10px] text-muted-foreground">{hex}</div>
      </div>
    </div>
  );
}
Section · typography

02 · Tipografía

# Tipografía — Especificación completa

## 1. Familias
| Familia | Pesos | Ubicación | Uso |
|---|---|---|---|
| **Gotham** | 300, 400, 500, 700, 900 | self-hosted `/public/fonts/Gotham-*.woff2` | Display + UI |
| **JetBrains Mono** | 400, 600 | Google Fonts | Eyebrows, metadatos, código, tabular nums |

```css
/* src/styles.css */
@font-face { font-family: "Gotham"; src: url("/fonts/Gotham-Light.woff2") format("woff2");
  font-weight: 300; font-display: swap; }
/* …Book 400, Medium 500, Bold 700, Black 900 */

@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&display=swap");
```

## 2. Escala tipográfica
| Token | Tamaño | Line-height | Peso típico | Uso |
|---|---|---|---|---|
| Display XL | 60–96px (`text-5xl` → `text-7xl`) | 0.95 | 300 | H1 portada |
| Display L  | 36–48px (`text-3xl` → `text-4xl`) | 1.05 | 300 / 400 | H1 sección |
| Display M  | 24–30px (`text-2xl` → `text-3xl`) | 1.1 | 400 | H2 |
| Title      | 18–20px (`text-lg` → `text-xl`) | 1.3 | 500 | Card titles |
| Body Lg    | 17–18px (`text-[17px]`) | 1.6 | 400 | Intro / lead |
| Body       | 15–16px (`text-base`) | 1.6 | 400 | Texto base |
| Body Sm    | 13–14px (`text-sm`) | 1.5 | 400 | UI secundaria |
| Caption    | 12px (`text-xs`) | 1.4 | 400 | Notas, ayudas |
| Tag        | 11–12px sentence-case, sin tracking extra | 1.3 | 500 | Etiqueta de sección o categoría |

## 3. Pesos · cuándo usar cada uno
- **300 Light** → cifras grandes, displays. Da aire y elegancia.
- **400 Book** → caballo de batalla del cuerpo de texto.
- **500 Medium** → títulos de card, navegación activa, énfasis sutil.
- **700 Bold** → CTAs, etiquetas de estado, énfasis fuerte.
- **900 Black** → reservado para titulares enfáticos. **Nunca para párrafos**.

## 4. Reglas tipográficas
1. **Jerarquía con peso/tamaño, no con color.** Un solo color de texto por bloque
   con variaciones `text-foreground` / `text-muted-foreground`.
2. **Sin eyebrows mono UPPERCASE.** Para etiquetar secciones usamos **texto normal + tags**:
   un `<span>` corto en sentence-case (peso 500, sin `uppercase`, sin `tracking` extendido),
   acompañado opcionalmente de un `<Tag>` (chip pill con border) cuando haya categoría.
3. **Líneas largas** ≤ 75 caracteres (`max-w-2xl` aprox).
4. **Tabular nums** para números en tablas: `font-variant-numeric: tabular-nums`.
5. **No mezclar** más de 2 pesos por bloque.
6. **No usar UPPERCASE** ni `tracking-[0.14em]` para etiquetar — son texto normal.
7. **Italic** solo para citas o énfasis breve. No para titulares.

## 5. Pairing canónico
```tsx
<div>
  {/* Texto normal + tag opcional. NO uppercase mono con tracking. */}
  <div className="flex items-center gap-2 text-xs text-muted-foreground">
    <span className="font-medium text-foreground">Sección 03</span>
    <span className="px-2 py-0.5 rounded-full border border-border text-[11px]">Producto</span>
  </div>
  <h2 className="font-display text-3xl mt-2">Título de sección</h2>
  <p className="text-sm text-muted-foreground mt-3 leading-relaxed max-w-xl">
    Subtítulo o descripción en body sm con muted foreground.
  </p>
</div>
```

## 6. Do / Don't
**DO**
- Display 300 para cifras grandes (`font-display text-5xl font-light`).
- Etiquetar con texto normal sentence-case + tag (chip) opcional al lado.
- Tabular nums en métricas.

**DON'T**
- ❌ Fuentes del sistema o fallback decorativo.
- ❌ Más de 2 pesos por bloque.
- ❌ Eyebrows en mono UPPERCASE con `tracking-[0.14em]` — abandonado en favor de texto + tag.
- ❌ UPPERCASE en párrafos.
- ❌ Italic en titulares.
- ❌ Line-height < 1.4 en body.

## 7. Accesibilidad
- Tamaño mínimo legible: 12px (solo metadatos).
- Body siempre ≥ 14px.
- Contraste 4.5:1 mínimo (ver Color spec).
- No justificar texto largo (causa rivers).

## 8. QA Checklist
- [ ] Display usa Gotham 300/400.
- [ ] Etiquetas de sección con texto normal + tag (sin UPPERCASE/tracking mono).
- [ ] Líneas de texto ≤ 75 caracteres.
- [ ] Máximo 2 pesos por bloque.
- [ ] Body ≥ 14px.
- [ ] Tabular nums en tablas/scorecards.
Snippets · 1

Componentes copiables

Endpoints públicos · usar desde otro proyecto

Cada URL devuelve Markdown plano (mismo código que ves arriba). Pega la URL en otro agente / proyecto para que la haga fetch e inserte el snippet sin remixar este proyecto.

typography · todo · desktop
typography · todo · mobile
Escala tipográfica
Escala tipográfica

Display

H1

H2

Body

Caption

Eyebrow

<div className="space-y-4">
  <h1 className="text-5xl font-semibold tracking-tight">Display · 48/56</h1>
  <h2 className="text-3xl font-semibold tracking-tight">H1 · 30/38</h2>
  <h3 className="text-xl font-medium">H2 · 20/28</h3>
  <p className="text-base">Body · 16/24</p>
  <p className="text-sm text-muted-foreground">Caption · 14/20</p>
  <p className="text-[11px] font-medium">
    Eyebrow · mono 11
  </p>
</div>
Section · iconography

04 · Iconografía

# Iconografía — Especificación completa

## 1. Dos sistemas paralelos
La marca usa **dos** sistemas de iconografía con propósitos distintos:

### A. Pétalos (decorativos / brand)
ADN visual de Farmatodo. Forma orgánica de gota inscrita en cuadrado. Tres
tonos azules generan profundidad sin perder identidad.

| Pétalo | Hex | Archivo |
|---|---|---|
| 1 — Azul Farmatodo | `#002858` | `@/assets/petalo-1.svg` |
| 2 — Azul Profundo  | `#0040B9` | `@/assets/petalo-2.svg` |
| 3 — Azul Eléctrico | `#1465FF` | `@/assets/petalo-3.svg` |

### B. Iconos UI (funcionales)
Set único: **`lucide-react`** (con algunas excepciones documentadas en
`@radix-ui/react-icons` para iconos abstractos).

```tsx
import { Search, Bell, ArrowRight } from "lucide-react";
<Search className="w-4 h-4" strokeWidth={1.75} />
```

## 2. Reglas — Pétalos
- Rotación en **múltiplos de 15°** (0°, 15°, 30°, 45°, …).
- Composición libre: máximo 3 pétalos por agrupación visual.
- Sobre fondos brand → versión blanca (`brightness-0 invert`).
- Pueden solaparse parcialmente (overlap 20–40% del ancho).
- **Nunca**: gradientes internos, sombras, outline, ni colores fuera de la paleta.

## 3. Reglas — Iconos UI
| Atributo | Valor |
|---|---|
| Set | lucide-react |
| Stroke width | **1.75** (default brand) |
| Tamaño base | 16px (`w-4 h-4`) |
| Tamaño táctil | 24px en hit area de 40×40px mínimo |
| Color por defecto | `currentColor` (heredan del padre) |
| Estilo | Outline (no relleno) salvo estados activos |

### Tamaños canónicos
| Contexto | Tamaño | Clase |
|---|---|---|
| Inline en texto | 12–14px | `w-3 h-3` / `w-3.5 h-3.5` |
| UI estándar | 16px | `w-4 h-4` |
| Botones grandes | 20px | `w-5 h-5` |
| Hero / iconografía decorativa | 32–48px | `w-8 h-8` / `w-12 h-12` |

## 4. AiSpark — caso especial
```tsx
import { Sparkles } from "lucide-react";
export function AiSpark({ className = "w-4 h-4" }) {
  return <Sparkles className={className} strokeWidth={2} style={{ color: "#1465FF" }} />;
}
```
Reservado **exclusivamente** para componentes AI. No usar como icono decorativo.

## 5. Do / Don't
**DO**
- Combinar máximo 3 pétalos por composición libre.
- Usar pétalos en blanco sobre azul brand.
- Mantener stroke 1.75 consistente.
- `aria-label` en iconos sin texto acompañante.

**DON'T**
- ❌ Mezclar familias (lucide + heroicons + tabler).
- ❌ Rellenar pétalos con gradientes.
- ❌ Iconos rellenos para acciones (solo para estados activos).
- ❌ AiSpark en contextos no-AI.
- ❌ Stroke distinto a 1.75 (rompe consistencia visual).

## 6. Snippet — botón con icono
```tsx
<button className="inline-flex items-center gap-2 px-3 py-2 rounded-md hover:bg-muted">
  <Search className="w-4 h-4" strokeWidth={1.75} />
  <span className="text-sm">Buscar</span>
</button>
```

## 7. Accesibilidad
- Iconos decorativos: `aria-hidden="true"`.
- Iconos accionables sin texto: `aria-label="Descripción acción"`.
- Hit area mínima 44×44px en mobile (puede ser mayor que el visual).

## 8. QA Checklist
- [ ] Iconos lucide stroke 1.75.
- [ ] Pétalos en tonos definidos, rotación múltiplo de 15°.
- [ ] AiSpark solo en componentes AI.
- [ ] Sin mezcla de sets.
- [ ] aria-label en iconos sin texto.
- [ ] Hit area ≥ 44px en mobile.
Snippets · 1

Componentes copiables

Endpoints públicos · usar desde otro proyecto

Cada URL devuelve Markdown plano (mismo código que ves arriba). Pega la URL en otro agente / proyecto para que la haga fetch e inserte el snippet sin remixar este proyecto.

iconography · todo · desktop
iconography · todo · mobile
Uso de iconos (lucide-react)
Uso de iconos (lucide-react)
Stroke 1.75, tamaños 16/20/24. No mezclar familias. Siempre con label accesible.
import { Search, ShoppingCart, Heart } from "lucide-react";

<button aria-label="Buscar" className="p-2 rounded-lg hover:bg-muted">
  <Search className="w-5 h-5" strokeWidth={1.75} />
</button>
Section · product

05 · Producto (UI Components)

# Producto (UI Components) — Especificación completa

## 1. Tokens base

### Espaciado (escala 4px)
| Token | px | Clase |
|---|---|---|
| 0.5 | 2 | `gap-0.5` |
| 1 | 4 | `gap-1` |
| 2 | 8 | `gap-2` |
| 3 | 12 | `gap-3` |
| 4 | 16 | `gap-4` |
| 6 | 24 | `gap-6` |
| 8 | 32 | `gap-8` |
| 12 | 48 | `gap-12` |

### Radios
| Token | Valor | Uso |
|---|---|---|
| `rounded-md` | 6px | Inputs, chips, botones pequeños |
| `rounded-lg` | 8px | Botones estándar |
| `rounded-xl` | 12px | CTAs, badges |
| `rounded-2xl` | 16px | Cards, contenedores |
| `rounded-full` | 9999px | Burbujas chat, avatares |

### Sombras
- Solo para elevación crítica (popover, modal).
- Default: `shadow-sm` para cards interactivas.
- Brand glow: `box-shadow: 0 8px 22px -8px <accent>` para CTAs destacados.

## 2. Anatomía de componentes

### Card
```tsx
<div className="border border-border rounded-2xl bg-background p-6">
  <div className="flex items-center gap-2 text-xs text-muted-foreground">
    <span className="font-medium text-foreground">Sección</span>
    <span className="px-2 py-0.5 rounded-full border border-border text-[11px]">Tag</span>
  </div>
  <h3 className="font-display text-xl mt-2">Título</h3>
  <p className="text-sm text-muted-foreground mt-2">Descripción</p>
</div>
```

### Botón primario
```tsx
<button
  className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium text-white hover:opacity-90 transition-opacity"
  style={{ backgroundColor: "#0040B9" }}
>
  Acción primaria
</button>
```

### Botón secundario
```tsx
<button className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm border border-border bg-background hover:bg-muted">
  Acción secundaria
</button>
```

### Botón destructivo
```tsx
<button className="px-4 py-2.5 rounded-xl text-sm text-white" style={{ backgroundColor: "#C2410C" }}>
  Eliminar
</button>
```

> Destructivo en **naranja** (`#C2410C`), nunca rojo. Aplica también a estados error/danger.

### Input
```tsx
<input
  className="h-10 w-full rounded-md border-2 border-border bg-background px-3 text-sm transition-[border-color,box-shadow] duration-150 hover:border-foreground/40 focus:outline-none focus-visible:border-ring focus-visible:ring-[6px] focus-visible:ring-ring/25 focus-visible:bg-background"
  placeholder="Buscar…"
/>
```

**Foco prominente (regla del sistema):**
- Borde base `border-2` (stroke grueso).
- En `:focus-visible`: `border-ring` (azul brand) + halo `ring-[6px] ring-ring/25`.
- Transición suave `transition-[border-color,box-shadow] duration-150`.
- Hover intermedio: `hover:border-foreground/40`.
- Aplica a `Input`, `Textarea`, `SelectTrigger` y wrappers `focus-within:` custom.

### Badge / Chip
```tsx
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs border border-border bg-background">
  Etiqueta
</span>
```

## 3. Estados (canónicos)
| Estado | Tratamiento visual | Snippet |
|---|---|---|
| Hover  | `hover:bg-muted` o `hover:opacity-90` | — |
| Active | `bg-muted font-medium` | — |
| Focus (fields) | `border-2` + `border-ring` + halo `ring-[6px] ring-ring/25` | `focus-visible:border-ring focus-visible:ring-[6px] focus-visible:ring-ring/25` |
| Focus (botones/links) | Ring 2px brand + offset 2px | `focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2` |
| Disabled | `opacity-50 pointer-events-none` + `cursor-not-allowed` | `disabled:opacity-50 disabled:pointer-events-none` |
| Loading | Loader2 animado + texto "Cargando" | `<Loader2 className="w-4 h-4 animate-spin" />` |
| Error | Border naranja (`#FB923C`) + mensaje contextual debajo (ver `FieldFeedbackGallery`) | — |

## 4. Patrones de layout

### Sección con eyebrow lateral
```tsx
<section className="px-6 lg:px-10 py-12 border-b border-border">
  <div className="grid lg:grid-cols-[140px_1fr] gap-6 lg:gap-10">
    <div className="text-xs text-muted-foreground sticky top-8">
      <span className="font-medium text-foreground">5.1</span> Bloque
    </div>
    <div>
      <h2 className="font-display text-2xl lg:text-3xl mb-8">Título</h2>
      {/* contenido */}
    </div>
  </div>
</section>
```

### Grid de cards
- Mobile: 1 col
- Tablet: 2 cols (`md:grid-cols-2`)
- Desktop: 3 cols (`lg:grid-cols-3`)
- Gap: `gap-6` (24px)

## 5. Reglas
1. Padding interno mínimo de cards: **24px** (`p-6`).
2. Separación entre secciones: **48px** (`py-12` o más).
3. Bordes `rounded-2xl` para contenedores, `rounded-xl` para CTAs.
4. Máximo 2 niveles de anidamiento de cards.
5. Sombras solo para elevación crítica (popover, modal, dropdown).
6. Una acción primaria por vista — el resto son secundarias o link.

## 6. Do / Don't
**DO**
- Tokens semánticos para color (`bg-primary`, `text-foreground`).
- Foco visible siempre.
- Touch target ≥ 44px en mobile.

**DON'T**
- ❌ Mezclar radios sin propósito (`rounded-md` + `rounded-3xl` en un mismo grupo).
- ❌ Anidar más de 2 cards.
- ❌ Sombras decorativas.
- ❌ Múltiples CTAs primarios en una vista.

## 7. Accesibilidad
- Contraste 4.5:1 cuerpo, 3:1 grande.
- Focus ring visible — fields con `border-2` + halo 6px; botones/links con ring 2px + offset 2px.
- Áreas táctiles ≥ 44×44px en mobile.
- Iconos sin texto → `aria-label`.
- Estados disabled comunicados por más que opacidad (`aria-disabled`, cursor).
- Modales con `role="dialog"` + `aria-modal="true"` + focus trap.

## 8. QA Checklist
- [ ] Estados hover / active / focus / disabled visibles.
- [ ] Padding interno ≥ 24px en cards.
- [ ] Foco en fields con `border-2` + halo `ring-[6px] ring/25` (regla prominente).
- [ ] Touch target ≥ 44px.
- [ ] Una sola acción primaria por vista.
- [ ] Sin sombras decorativas.
- [ ] Tokens semánticos (sin hex literales).
- [ ] Errores y destructivos en **naranja** (`#C2410C` / `#FB923C`), no rojo.
- [ ] Sin eyebrows mono UPPERCASE — usar texto normal + tag.
Snippets · 6

Componentes copiables

Endpoints públicos · usar desde otro proyecto

Cada URL devuelve Markdown plano (mismo código que ves arriba). Pega la URL en otro agente / proyecto para que la haga fetch e inserte el snippet sin remixar este proyecto.

product · todo · desktop
product · todo · mobile
Botón Primary
Botón Secondary
Product Card
Input field
Tab bar (mobile)
Bottom sheet (mobile)
Botón Primary
<button
  className="inline-flex items-center justify-center gap-2 h-10 px-5 rounded-full text-sm font-medium text-white transition-colors hover:opacity-90"
  style={{ backgroundColor: "#0040B9" }}
>
  Agregar al carrito
</button>
Botón Secondary
<button className="inline-flex items-center justify-center h-10 px-5 rounded-full text-sm font-medium border border-border bg-background hover:bg-muted">
  Ver más
</button>
Product Card
Marca
Acetaminofén 500 mg · 24 tabletas
$ 12.900
import { Plus, Heart } from "lucide-react";

export function ProductCard() {
  return (
    <div className="border border-border rounded-2xl p-4 bg-background w-56">
      <div className="aspect-square bg-muted rounded-xl mb-3 relative">
        <button
          aria-label="Favorito"
          className="absolute top-2 right-2 w-8 h-8 rounded-full bg-background/80 flex items-center justify-center"
        >
          <Heart className="w-4 h-4" />
        </button>
      </div>
      <div className="text-xs text-muted-foreground">Marca</div>
      <div className="text-sm font-medium leading-tight mb-2">
        Acetaminofén 500 mg · 24 tabletas
      </div>
      <div className="flex items-center justify-between">
        <span className="text-base font-semibold">$ 12.900</span>
        <button
          className="w-8 h-8 rounded-full flex items-center justify-center text-white"
          style={{ backgroundColor: "#0040B9" }}
        >
          <Plus className="w-4 h-4" />
        </button>
      </div>
    </div>
  );
}
Input field
<label className="block">
  <span className="text-sm font-medium block mb-1.5">Email</span>
  <input
    type="email"
    placeholder="tu@correo.com"
    className="w-full h-10 px-3 rounded-lg border border-border bg-background text-sm outline-none focus:border-foreground/30"
  />
</label>
Tab bar (mobile)
Navegación inferior fija para apps mobile. 4–5 destinos máximo, ítem activo con color primario.
import { Home, Search, ScanLine, Heart, User } from "lucide-react";

export function MobileTabBar({ active = "home" }: { active?: string }) {
  const items = [
    { id: "home", label: "Inicio", icon: Home },
    { id: "search", label: "Buscar", icon: Search },
    { id: "scan", label: "Escanear", icon: ScanLine },
    { id: "fav", label: "Favoritos", icon: Heart },
    { id: "me", label: "Yo", icon: User },
  ];
  return (
    <nav className="fixed inset-x-0 bottom-0 grid grid-cols-5 bg-background border-t border-border pb-[env(safe-area-inset-bottom)]">
      {items.map(({ id, label, icon: Icon }) => {
        const on = id === active;
        return (
          <button
            key={id}
            className="flex flex-col items-center justify-center gap-1 py-2.5"
            style={{ color: on ? "#0040B9" : undefined }}
          >
            <Icon className="w-5 h-5" strokeWidth={on ? 2 : 1.75} />
            <span className="text-[10px] font-medium">{label}</span>
          </button>
        );
      })}
    </nav>
  );
}
Bottom sheet (mobile)
Hoja inferior para detalle de producto o acciones secundarias. Handle visible, esquinas superiores 24px.
Acetaminofén 500 mg
Caja x 24 tabletas · Genérico
export function BottomSheet({ children }: { children: React.ReactNode }) {
  return (
    <div className="fixed inset-x-0 bottom-0 bg-background rounded-t-3xl border-t border-border shadow-2xl pb-[max(1rem,env(safe-area-inset-bottom))]">
      <div className="flex justify-center pt-2 pb-1">
        <span className="w-10 h-1 rounded-full bg-muted-foreground/30" />
      </div>
      <div className="px-5 pt-2 pb-4">{children}</div>
    </div>
  );
}
Section · data-viz

06 · Data Viz

# Data Viz — Especificación completa

## 1. Principios
1. **Claridad sobre densidad** — espacio negativo > información extra.
2. **Color con intención** — una serie, un tono. Color para diferenciar, no decorar.
3. **Etiquetas siempre legibles** — ≥ 11px, contraste AA.
4. **Tabular numbers** en métricas.
5. **Cero ≠ vacío** — usar `—` para faltante, `0` solo cuando sea real.
6. **Orden con intención** — descendente por valor por defecto, salvo orden temporal.

## 2. Paleta de series
| Serie | Hex | Uso |
|---|---|---|
| Principal | `#0040B9` | Serie destacada |
| Secundaria | `#1465FF` | Comparativo principal |
| Terciaria | `#7B8FE6` | Tercera serie |
| Cuaternaria | `#94A3B8` | Comparativo neutro / benchmark |
| Positivo | `#27AE60` | Delta positivo |
| Negativo | `#C2410C` (texto) · `#FB923C` (relleno) | Delta negativo — **naranja, no rojo** |
| Neutro | `#94A3B8` | Sin cambio |

Máximo **4 series** por gráfico. Más → usar small multiples.

## 3. Componentes

### Scorecard
```tsx
<div className="border border-border rounded-2xl p-6">
  <div className="text-xs text-muted-foreground font-medium">Ventas mes</div>
  <div className="font-display text-5xl font-light mt-3 tabular-nums">
    $124.530
  </div>
  <div className="flex items-center gap-1.5 mt-2 text-xs">
    <ArrowUp className="w-3 h-3" style={{ color: "#27AE60" }} />
    <span style={{ color: "#27AE60" }}>+12,4%</span>
    <span className="text-muted-foreground">vs mes anterior</span>
  </div>
</div>
```

### Filter chip clicable
- Trigger: chip con label + ChevronDown.
- Click → dropdown absoluto (`z-20`) con click-outside-close.
- ChevronDown rota 180° cuando está abierto.
- Selección actualiza label + cierra dropdown.

### Tabla
- Header: `bg-muted/50`, label en texto normal sentence-case (sin eyebrow mono UPPERCASE).
- Rows: hover `bg-muted/30`, separador `border-t border-border`.
- Números alineados a la derecha + `tabular-nums`.
- Sticky header en tablas largas.

### Tabla con barras inline (`BarTable`)
Tabla cuyas celdas numéricas se acompañan de una barra proporcional al valor
máximo de la columna. Útil para comparar magnitudes entre categorías sin salir
del flujo de lectura tabular.

**Anatomía de celda** (`InlineBarCell`):
- Layout: `flex items-center gap-3 min-w-[160px]`.
- Número: `w-20 text-right tabular-nums` (ancho fijo para alinear columna).
- Barra: `flex-1` h-5 (visualmente h-2 con `-my-1.5`) renderizada con
  Recharts `<BarChart layout="vertical">` apilado (valor + restante a opacidad
  0.12) → tooltip nativo al hover con label de fila + métrica + valor formateado.
  `isAnimationActive={false}` para no animar al scroll.

**Alineación obligatoria header ↔ celda ↔ totals**:
Headers (`<th>`) y la fila de totales (`<tfoot>`) deben replicar el mismo
layout `flex items-center gap-3 min-w-[160px]` con `<span className="w-20 text-right">`
para el label/total y un `<span className="flex-1" aria-hidden />` como spacer.
Sin esto, los números no quedan visualmente debajo de su header.

**Variantes obligatorias** (siempre documentar ambas):
| Variante | Prop | Cuándo |
|---|---|---|
| Sin totales | `<BarTable />` (default) | Comparativa pura entre filas |
| Con total acumulado | `<BarTable showTotals />` | Cuando la suma por columna aporta contexto (ventas, unidades, etc.) |

`showTotals` renderiza `<tfoot>` con fila "Total" — separador
`border-t-2 border-foreground/20 bg-muted/40`, texto en mono uppercase pequeño
para diferenciar de filas de datos, suma con `toLocaleString()` y
`tabular-nums`. La nota inferior (footer del card) debe cambiar según el prop:
"Footer con total acumulado…" vs "Sin totales — comparativa pura…".

**Regla general — nada de SVG estático para datos**:
Cualquier mini-gráfico que represente datos reales (sparklines, barras inline,
demos de espaciado de etiquetas) debe usar Recharts con `Tooltip` activo. Los
SVG inline sólo se permiten para anotaciones puramente decorativas o cotas de
medida (ver `CotaBadge` en demos de espaciado: badge HTML absoluto sobre un
chart Recharts real).

## 4. Anatomía de gráficos

### Ejes
- Línea: `#94A3B8` 1px.
- Gridlines: `#94A3B8` con `opacity-30`, solo horizontales.
- Labels: 11px, `text-muted-foreground`.
- Tick marks: cada N para evitar saturación.

### Tooltip
```tsx
<div className="rounded-lg border border-border bg-background shadow-md px-3 py-2 text-xs">
  <div className="font-medium">Marzo 2026</div>
  <div className="flex items-center gap-2 mt-1">
    <span className="w-2 h-2 rounded-full" style={{ backgroundColor: "#0040B9" }} />
    <span>Ventas:</span>
    <span className="font-mono tabular-nums">$124.530</span>
  </div>
</div>
```

### Leyenda
- Posición: arriba-derecha o abajo del gráfico.
- Dot 8px del color de la serie + label 12px.
- Click en leyenda → toggle visibilidad (opcional).

## 5. Tipos de gráfico — cuándo usar
| Tipo | Cuándo |
|---|---|
| Línea | Tendencia temporal continua |
| Barra vertical | Comparación entre categorías (≤ 12) |
| Barra horizontal | Categorías con label largo o muchas (>12) |
| Área apilada | Composición a lo largo del tiempo |
| Donut | Composición parte/todo (≤ 5 segmentos) — preferir barras |
| Scatter | Correlación entre 2 variables |
| Sparkline | Tendencia inline en tabla/scorecard |

**Evitar**: pie con > 5 segmentos, 3D, eje Y truncado sin indicarlo.

## 6. Deltas y comparativos
```tsx
function Delta({ value }: { value: number }) {
  const Icon = value > 0 ? ArrowUp : value < 0 ? ArrowDown : Minus;
  const color = value > 0 ? "#27AE60" : value < 0 ? "#C2410C" : "#94A3B8";
  const sign = value > 0 ? "+" : "";
  return (
    <span className="inline-flex items-center gap-1 text-xs font-medium tabular-nums" style={{ color }}>
      <Icon className="w-3 h-3" />
      {sign}{value}%
    </span>
  );
}
```

## 7. Do / Don't
**DO**
- Filtros clicables con dropdown.
- Deltas con flecha + signo + color.
- Tooltip con valor exacto al hover.
- Tabular nums en cifras.
- Ordenar descendente por defecto.
- **Etiquetas siempre on-chart** para dar contexto del valor (con guía de espaciado).
- Usar `SmartLabelList` para resolver colisiones (shrink → hide por prioridad).
- En charts con 2+ series, `MultiSeriesLabelList` con jerarquía: solo serie principal + Δ pill.
- Documentar **ambas** variantes de `BarTable`: con totales y sin totales.
- Alinear headers, celdas y totals con el mismo layout (`w-20 text-right` + spacer `flex-1`).

**DON'T**
- ❌ Más de 4 series por gráfico.
- ❌ 3D, sombras decorativas, gradientes innecesarios.
- ❌ Rojo para deltas negativos — usamos **naranja** (`#C2410C`).
- ❌ Verde para series neutrales (reservar para deltas positivos).
- ❌ Eje Y truncado sin indicador (`~` o break).
- ❌ Pie con > 5 segmentos.
- ❌ Filtros decorativos no clicables.
- ❌ Etiquetas que se solapan — siempre pasar por `SmartLabelList`.
- ❌ Etiquetas en todas las series cuando hay 2+ líneas — solo principal + Δ vs comparativo.
- ❌ Tabla con barras sin alinear header sobre la columna numérica.
- ❌ Mostrar totales acumulados cuando la suma no tiene sentido (ratios, %, promedios).

## 7.b. Etiquetas en charts (reglas)

**Regla de no-colisión** (`SmartLabelList`):
1. Cada label calcula su bounding box aproximado.
2. Sistema de prioridad (mayor valor = mayor prioridad).
3. Antes de ocultar, intenta **shrink**: `fontSize -2px`, `offset -4px`.
4. Si sigue colisionando → ocultar el de menor prioridad.
5. Drop-in replacement de `<LabelList>` de Recharts.

**Multi-series con jerarquía** (`MultiSeriesLabelList`): cuando hay 2+ líneas/series:
| Modo | Comportamiento |
|---|---|
| `primary-only` | Solo etiqueta la serie principal |
| `primary-with-delta` (default) | Principal + pill Δ% vs `compareKey` |
| `endpoints-only` | Solo primer y último punto |

Pill Δ: verde si principal > comparativo, **naranja** (`#FB923C`) si no llega, gris si igual.
Comparativos en línea **dashed** sin etiqueta.

**Espaciado de etiquetas** (guía):
- Distancia mínima entre label y punto: 6px.
- Padding horizontal entre labels adyacentes: 8px mínimo.
- Margen `top` del chart: ≥ 26px (≥ 48px si hay pill Δ encima).
- Font label: 11–12px, peso 500, `tabular-nums`.

## 8. Accesibilidad
- Color **+** texto: nunca solo color para distinguir series.
- Patterns/dashes alternativos para daltonismo.
- Tabla equivalente disponible para screen readers.
- Alt text descriptivo en gráficos exportados como imagen.

## 9. QA Checklist
- [ ] Filtros responden a click (no decorativos).
- [ ] Deltas con signo, color y flecha.
- [ ] Ejes legibles en mobile (≥ 11px).
- [ ] Tabular nums en métricas y tablas.
- [ ] Máximo 4 series por gráfico.
- [ ] Tooltip con valor exacto.
- [ ] Cero distinguido de faltante.
- [ ] Etiquetas on-chart con `SmartLabelList` (sin colisiones).
- [ ] Charts con 2+ series usan `MultiSeriesLabelList` (jerarquía visual).
- [ ] Deltas negativos en **naranja** (`#C2410C`), no rojo.
- [ ] Sin eyebrows mono UPPERCASE en headers de tabla / scorecards.
- [ ] `BarTable` mostrada en sus dos variantes (con / sin `showTotals`).
- [ ] Headers y totals de `BarTable` alineados sobre la columna numérica (`w-20 text-right`).
Snippets · 3

Componentes copiables

Endpoints públicos · usar desde otro proyecto

Cada URL devuelve Markdown plano (mismo código que ves arriba). Pega la URL en otro agente / proyecto para que la haga fetch e inserte el snippet sin remixar este proyecto.

data-viz · todo · desktop
data-viz · todo · mobile
KPI Card
KPI Card + Mini Bar Chart
Mini Bar Chart (Recharts)
KPI Card
Ventas · hoy
$ 4.82M
+12,4% vs. ayer
<div className="border border-border rounded-2xl p-5 bg-background w-56">
  <div className="text-[11px] font-medium text-muted-foreground">
    Ventas · hoy
  </div>
  <div className="text-3xl font-semibold mt-2">$ 4.82M</div>
  <div className="text-xs mt-1" style={{ color: "#1B7A3E" }}>
    +12,4% vs. ayer
  </div>
</div>
KPI Card + Mini Bar Chart
Ventas · 7 días
$ 4.82M
+12,4% vs. semana ant.
import { ResponsiveContainer, BarChart, Bar, Tooltip, XAxis, YAxis } from "recharts";

const trend = [
  { name: "L", v: 12 }, { name: "M", v: 28 }, { name: "X", v: 18 },
  { name: "J", v: 36 }, { name: "V", v: 22 }, { name: "S", v: 40 }, { name: "D", v: 30 },
];

<div className="border border-border rounded-2xl p-5 bg-background w-64">
  <div className="text-[11px] font-medium text-muted-foreground">
    Ventas · 7 días
  </div>
  <div className="flex items-end justify-between gap-3 mt-2">
    <div>
      <div className="text-3xl font-semibold tabular-nums">$ 4.82M</div>
      <div className="text-xs mt-1" style={{ color: "#1B7A3E" }}>
        +12,4% vs. semana ant.
      </div>
    </div>
    <div className="w-24 h-12 shrink-0">
      <ResponsiveContainer width="100%" height="100%">
        <BarChart data={trend} margin={{ top: 2, right: 0, left: 0, bottom: 0 }}>
          <XAxis dataKey="name" hide />
          <YAxis hide />
          <Tooltip
            cursor={{ fill: "#0040B910" }}
            contentStyle={{
              background: "#fff",
              border: "1px solid #E2E8F0",
              borderRadius: 8,
              fontSize: 12,
              padding: "4px 8px",
            }}
          />
          <Bar dataKey="v" fill="#0040B9" radius={[2, 2, 0, 0]} />
        </BarChart>
      </ResponsiveContainer>
    </div>
  </div>
</div>
Mini Bar Chart (Recharts)
import { ResponsiveContainer, BarChart, Bar, Tooltip, XAxis, YAxis } from "recharts";

const data = [
  { name: "L", v: 12 }, { name: "M", v: 28 }, { name: "X", v: 18 },
  { name: "J", v: 36 }, { name: "V", v: 22 }, { name: "S", v: 40 }, { name: "D", v: 30 },
];

<div className="w-40 h-16">
  <ResponsiveContainer width="100%" height="100%">
    <BarChart data={data} margin={{ top: 2, right: 2, left: 2, bottom: 2 }}>
      <XAxis dataKey="name" hide />
      <YAxis hide />
      <Tooltip
        cursor={{ fill: "#0040B910" }}
        contentStyle={{
          background: "#fff",
          border: "1px solid #E2E8F0",
          borderRadius: 8,
          fontSize: 12,
          padding: "4px 8px",
        }}
      />
      <Bar dataKey="v" fill="#0040B9" radius={[3, 3, 0, 0]} />
    </BarChart>
  </ResponsiveContainer>
</div>
Section · ai-components

07 · AI Components

# AI Components — Especificación completa

## 1. Filosofía
**AI como copiloto, no protagonista.** El sparkle azul (`#1465FF`) es la firma
de marca AI. Burbujas redondeadas y tonos brand diferencian acción humana,
agente y sistema. Las experiencias agénticas siempre permiten cancelar y
confirman antes de mutar datos.

## 2. AiSpark (marca AI)
```tsx
import { Sparkles } from "lucide-react";
export function AiSpark({ className = "w-4 h-4" }: { className?: string }) {
  return <Sparkles className={className} strokeWidth={2} style={{ color: "#1465FF" }} />;
}
```

| Tamaño | Clase | Uso |
|---|---|---|
| 16px | `w-4 h-4` | Inline en texto, eyebrow de burbuja |
| 24px | `w-6 h-6` | Headers de bloque AI |
| 40px | `w-10 h-10` | Hero, splash agente |

## 3. Burbujas de chat

### UserBubble
```tsx
<div className="flex justify-end">
  <div className="max-w-[80%] px-5 py-3 rounded-full text-sm text-white" style={{ backgroundColor: "#0040B9" }}>
    {children}
  </div>
</div>
```
- Fondo: `#0040B9` (Azul Profundo).
- Border radius: `rounded-full` (pill).
- Alineada a la derecha.

### AgentBubble
```tsx
<div className="space-y-2 max-w-[80%]">
  <div className="flex items-center gap-2 px-1">
    <AiSpark className="w-3 h-3" />
    <span className="text-xs text-muted-foreground font-medium">{agent}</span>
  </div>
  <div className="px-5 py-3 rounded-2xl text-sm bg-muted/60 text-foreground">
    {children}
  </div>
</div>
```
- Fondo: `bg-muted/60`.
- Border radius: `rounded-2xl` (no pill).
- Header obligatorio: AiSpark + nombre del agente en **texto normal** (no mono UPPERCASE).

## 4. Stepper (borrador agéntico)
Estados:
| Estado | Visual |
|---|---|
| `done` | Check azul `#0040B9` strokeWidth 3 |
| `current` | Dot azul lleno + label en bold |
| `todo` | Círculo con border, label muted |
| `ready` | Check con halo, indica listo para confirmar |

## 5. AgentThinking (razonamiento)
- Lista de pasos colapsable.
- Cada paso: status (`done` / `doing` / `todo`), label, detail opcional, duration.
- `doing` muestra `Loader2` animado en `#1465FF`.
- Modo `collapsed` cuando termina, mostrar resumen.

## 6. Confirmación (diálogos finales)

### ConfirmCard
- Stroke 3px en color accent (`accent + opacity 40`).
- Halo blur (`blur-3xl`, `accent + opacity 10`).
- Label de tono en **texto normal** (sentence-case): "Confirmación" o "Atención" (destructivo).
- Icono central en círculo `3px solid accent`, fondo halo.
- Botón confirmar con `box-shadow: 0 8px 20px -8px <accent>`.
- `onCancel` y `onConfirm` requeridos.

### CelebrationCard
- ConfettiBg animado (7 dots).
- RocketIcon en círculo accent.
- Título display 3xl.
- CTA primario con glow.

### Tonos
| Tone | Accent | Eyebrow |
|---|---|---|
| default | `#0040B9` | "Confirmación" |
| destructive | `#C2410C` (naranja, **no rojo**) | "Atención" |

## 7. Sidebar AI

### Estructura
```
SidebarShell (280px)
├── SidebarBrand (workspace + agent)
├── NewChatButton
├── SidebarSection "Navegación"
│   └── SidebarNavItem × N
├── SidebarDivider
├── SidebarSection "Acciones rápidas"
│   └── QuickAction × N
├── SidebarDivider
├── SidebarSection "Tarea actual"
│   └── TaskProgressCard
├── SidebarDivider
├── SidebarSection "Conversaciones"
│   └── ChatRow × N
└── AgentStatusCard (footer)
```

### Tokens del sidebar
- Ancho: `280px`.
- Label de grupo: texto normal sentence-case, `text-xs text-muted-foreground font-medium` (sin UPPERCASE / tracking mono).
- Divisor: `border-t border-border` con margen `mx-4`.
- Items: padding `px-3 py-2`, hover `bg-muted/50`, active `bg-muted font-medium`.
- Status dots: 6px (`w-1.5 h-1.5`).
  - `in_progress` `#F39C12`
  - `waiting` `#7B8FE6`
  - `done` `#27AE60`
  - `draft` `#94A3B8`

## 8. ContextualSummary ("Lo que está pasando")
- Header con AiSpark + label en texto normal sentence-case (sin UPPERCASE).
- Bullets con tono (`warn` `#F39C12` / `info` `#1465FF` / `ok` `#27AE60`).
- Términos investigables como `<button>` con `underline decoration-dotted decoration-[2px] underline-offset-[6px]`.
- Subrayado **gris** `#CBD5E1` → descriptivo (entidad, contexto).
- Subrayado **azul** `#1465FF` → métrica o delta clave.
- Hint inferior con flecha → "Toca cualquier subrayado para investigar en chat".

```tsx
<ContextualSummary
  items={[
    { tone: "warn", parts: [
      { text: "10 personas están de baja en " },
      { text: "3 tiendas", hl: "muted", bold: true },
      { text: " (Medellín, Recoleta, La Plata)." },
    ]},
  ]}
  onInvestigate={(text) => openChatWith(text)}
/>
```

## 9. Streaming AI (implementación)
- Llamadas vía edge function — **nunca** desde cliente.
- API key en backend (`LOVABLE_API_KEY`).
- SSE token-by-token, parsear línea a línea, manejar CRLF y `[DONE]`.
- Manejar 429 (rate limit) y 402 (créditos) con toast claro.
- Markdown rendering en respuestas (`react-markdown`).
- Enviar **toda** la conversación en cada request (memoria).

## 10. Do / Don't
**DO**
- AiSpark en todo elemento operado por agente.
- Streaming para respuestas largas.
- Confirmación obligatoria antes de mutaciones.
- Permitir cancelar pasos del agente.
- Mostrar pasos de razonamiento colapsables.
- Feedback de fields contextual (`FieldFeedbackGallery`): info / warn / error / success
  con copy útil específico al campo, no genérico.
- Foco prominente en fields del flujo AI: `border-2` + halo `ring-[6px] ring-ring/25`.

**DON'T**
- ❌ AI como elemento dominante visual (es copiloto).
- ❌ Exponer prompts o API keys en cliente.
- ❌ Llamar al modelo directamente desde el browser.
- ❌ Mezclar AiSpark con CTAs primarios sin contexto AI.
- ❌ Mutaciones sin confirmación explícita.
- ❌ Mensajes de error genéricos ("Campo inválido") — siempre contextual al input.
- ❌ Estados destructivos en rojo — usamos **naranja** (`#C2410C` / `#FB923C`).
- ❌ Eyebrows mono UPPERCASE en headers de bubbles / sidebar / confirm.

## 11. Accesibilidad
- Burbujas con `role="log"` + `aria-live="polite"`.
- Loaders con `aria-busy="true"`.
- Streaming visible para screen readers cuando termina cada token.
- Botones de confirmación con label descriptivo (no solo "OK").

## 12. QA Checklist
- [ ] AiSpark consistente en todos los componentes AI.
- [ ] Streaming sin saltos ni texto duplicado.
- [ ] Estados 429 / 402 con toast amigable.
- [ ] Confirmación antes de mutar datos.
- [ ] Cancelable en cualquier paso.
- [ ] Memoria conversacional completa enviada al modelo.
- [ ] Markdown renderizado en respuestas.
- [ ] Confirmación destructiva en **naranja** (`#C2410C`), no rojo.
- [ ] Sin eyebrows mono UPPERCASE — texto normal en todos los headers AI.
- [ ] Feedback de fields contextual y útil (no genérico).
Snippets · 7

Componentes copiables

Endpoints públicos · usar desde otro proyecto

Cada URL devuelve Markdown plano (mismo código que ves arriba). Pega la URL en otro agente / proyecto para que la haga fetch e inserte el snippet sin remixar este proyecto.

ai-components · todo · desktop
ai-components · todo · mobile
AiSpark (mark)
AiInput
AiSearchBar
Chat bubbles (User + Agent)
Suggestion chips
AgentStepper
AgentTaskHeader
AiSpark (mark)
import { Sparkles } from "lucide-react";

export function AiSpark({ className = "w-4 h-4" }: { className?: string }) {
  return (
    <Sparkles
      className={className}
      strokeWidth={2}
      style={{ color: "#1465FF" }}
    />
  );
}
AiInput
import { ArrowUp, Sparkles } from "lucide-react";

export function AiInput({ placeholder = "Pregunta o pide algo al agente…" }) {
  return (
    <div className="flex items-center gap-3 border border-border rounded-2xl bg-background px-4 py-3">
      <Sparkles className="w-4 h-4 shrink-0" style={{ color: "#1465FF" }} />
      <input
        className="flex-1 bg-transparent text-sm placeholder:text-muted-foreground outline-none"
        placeholder={placeholder}
      />
      <button
        className="w-9 h-9 rounded-xl flex items-center justify-center text-white"
        style={{ backgroundColor: "#1465FF" }}
        aria-label="Enviar"
      >
        <ArrowUp className="w-4 h-4" />
      </button>
    </div>
  );
}
AiSearchBar
<div className="flex items-center gap-3 border border-border rounded-full bg-background px-4 py-2 max-w-xl">
  <AiSpark className="w-3.5 h-3.5" />
  <input
    className="flex-1 bg-transparent text-sm placeholder:text-muted-foreground outline-none"
    placeholder="Busca, pregunta o escribe un atajo…"
  />
</div>
Chat bubbles (User + Agent)
¿Cuántas tabletas trae?
Pharma One
El blíster trae 24 tabletas de 500 mg.
export function UserBubble({ children }) {
  return (
    <div className="flex justify-end">
      <div
        className="max-w-[80%] px-5 py-3 rounded-full text-sm text-white"
        style={{ backgroundColor: "#0040B9" }}
      >
        {children}
      </div>
    </div>
  );
}

export function AgentBubble({ agent = "People One", children }) {
  return (
    <div className="space-y-2 max-w-[80%]">
      <div className="flex items-center gap-2 px-1">
        <AiSpark className="w-3 h-3" />
        <span className="font-mono text-[10px] text-muted-foreground">
          {agent}
        </span>
      </div>
      <div className="px-5 py-3 rounded-2xl text-sm bg-muted/60 text-foreground">
        {children}
      </div>
    </div>
  );
}
Suggestion chips
<div className="flex flex-wrap gap-2">
  <SuggestionChip>Resumir pedido</SuggestionChip>
  <SuggestionChip>Buscar alternativa</SuggestionChip>
  <SuggestionChip>Cambiar dirección</SuggestionChip>
</div>
AgentStepper
Borrador · Traslado
Falta info
Origen———
Producto———
3Destino———
4Fecha
<AgentStepper
  title="Borrador · Traslado"
  steps={[
    { label: "Origen", status: "done" },
    { label: "Producto", status: "done" },
    { label: "Destino", status: "current" },
    { label: "Fecha", status: "todo" },
  ]}
  hint="Falta info"
  hintTone="warn"
/>
AgentTaskHeader

Reabastecer Acetaminofén en sede Chapinero

En progreso · paso 2 de 4
<AgentTaskHeader
  title="Reabastecer Acetaminofén en sede Chapinero"
  status="En progreso · paso 2 de 4"
/>
Section · agent-demo

08 · Agent Demo

# Agent Demo — Especificación completa

## 1. Propósito
Demostración integrada del flujo agéntico end-to-end. Combina sidebar AI,
chat, razonamiento, borrador con stepper, diff de impacto y confirmación.
Sirve como referencia de implementación para construir flujos similares.

## 2. Flujo canónico (6 pasos)
1. **Intent** — usuario envía instrucción en lenguaje natural via `AiInput`.
2. **Razonamiento** — agente muestra `AgentThinking` con pasos visibles.
3. **Borrador** — `AgentStepper` con campos editables (Empleado, Destino, Vigencia, Confirmar).
4. **Impacto** — `DiffCard` muestra qué cambia (sale de / entra a, deltas).
5. **Confirmación** — `ConfirmCard` con tono según destructividad.
6. **Éxito** — `CelebrationCard` confirma y ofrece próxima acción.

## 3. Componentes obligatorios
| Componente | Rol |
|---|---|
| `AgentTaskHeader` | Título de la tarea en curso |
| `AgentThinking` | Pasos de razonamiento visibles |
| `AgentStepper` | Borrador con estado por campo |
| `DiffCard` | Resumen de impacto antes/después |
| `RelationRow` | Personas relacionadas (encargado, etc.) |
| `ConfirmCta` | Botón principal con shortcut ⌘↵ |
| `ConfirmCard` | Diálogo de confirmación |
| `CelebrationCard` | Diálogo de éxito |

## 4. Layout (3 columnas)
```
┌─────────────┬──────────────────────┬──────────────┐
│  Sidebar    │  Chat / Razonamiento │  Borrador    │
│  (280px)    │  (flex-1)            │  (380px)     │
│             │                      │              │
│  Nav        │  AgentThinking       │  Stepper     │
│  Acciones   │  Bubbles             │  DiffCard    │
│  Convers.   │  AiInput             │  ConfirmCta  │
└─────────────┴──────────────────────┴──────────────┘
```

## 5. Reglas
1. Cada paso del agente debe ser **cancelable**.
2. ETA visible cuando aplique (ej. "~30s").
3. Toda acción **destructiva** pasa por `ConfirmCard` con `tone="destructive"`.
4. Estados de error con retry visible.
5. Chat persiste durante la sesión (no se borra al confirmar).
6. Borrador se actualiza en vivo conforme el agente extrae datos.
7. Shortcut `⌘↵` confirma cuando `ready === true`.

## 6. Estados de la tarea
| Estado | Indicador | Acción permitida |
|---|---|---|
| `gathering` | AgentThinking activo | Cancelar |
| `drafting` | Stepper en `current` | Editar campos |
| `ready` | Stepper en `ready` | Confirmar / Editar |
| `confirming` | ConfirmCard abierta | Confirmar / Cancelar |
| `success` | CelebrationCard | Cerrar / Próxima acción |
| `error` | Toast + estado anterior | Retry / Cancelar |

## 7. Snippet — hook de progreso
```tsx
function useThinkingProgress(ready: boolean) {
  const [index, setIndex] = useState(0);
  useEffect(() => {
    if (!ready) return;
    setIndex(0);
    const id = setInterval(() => {
      setIndex((i) => (i >= STEPS.length ? (clearInterval(id), i) : i + 1));
    }, 900);
    return () => clearInterval(id);
  }, [ready]);
  return STEPS.map((s, i) => ({
    ...s,
    status: i < index ? "done" : i === index ? "doing" : "todo",
  }));
}
```

## 8. Do / Don't
**DO**
- Mostrar razonamiento (transparencia).
- Permitir editar el borrador antes de confirmar.
- Confirmar mutaciones siempre.

**DON'T**
- ❌ Ejecutar acciones sin confirmación.
- ❌ Ocultar pasos de razonamiento (caja negra).
- ❌ Bloquear edición durante el borrador.
- ❌ Cerrar el chat al confirmar.

## 9. QA Checklist
- [ ] Flujo completo en ≤ 4 pantallas.
- [ ] Confirmación obligatoria antes de mutar.
- [ ] Estado de error con retry.
- [ ] Cancelable en cualquier paso.
- [ ] Shortcut ⌘↵ funcional.
- [ ] Borrador se actualiza en vivo.
- [ ] CelebrationCard ofrece próxima acción.
Section · full-components

09 · Componentes completos (.tsx)

Bundle copia el componente y todas sus dependencias locales (hooks, utilidades, primitivas de UI) en un único bloque multi-archivo, así se preservan todos los subcomponentes, selectores e interacciones. .tsx copia sólo el archivo de entrada. URL entrega el endpoint público para hacer fetch desde otro proyecto.

Producto

EmployeeHierarchy
Jerarquía recursiva con métricas y tag de equipo.
src/components/product/EmployeeHierarchy.tsx+1 deps
SpreadsheetGrid
Grid editable tipo Excel con dropdowns y tags.
src/components/product/SpreadsheetGrid.tsx+1 deps
KanbanBoard
Kanban responsive con drag & drop.
src/components/product/KanbanBoard.tsx+1 deps
HiringKanban
Kanban especializado para recruiting.
src/components/product/HiringKanban.tsx+1 deps
TimelineGantt
Gantt con dependencias y % de avance.
src/components/product/TimelineGantt.tsx
CalendarView
Vista calendario mensual.
src/components/product/CalendarView.tsx+1 deps
RoadmapQuarters
Roadmap por trimestres con hitos.
src/components/product/RoadmapQuarters.tsx
SprintPlanner
Capacity planner por sprint.
src/components/product/SprintPlanner.tsx

AI

AiOnboardingForm
Formulario conversacional guiado por IA.
src/components/ai/AiOnboardingForm.tsx+2 deps
AiDenseForm
Formulario denso con asistencia IA.
src/components/ai/AiDenseForm.tsx+2 deps
FieldFeedbackGallery
Galería de estados de feedback de campo.
src/components/ai/FieldFeedbackGallery.tsx+2 deps
ai/index (átomos)
Bubbles, inputs, chips, steppers, spark.
src/components/ai/index.tsx+1 deps
ai/tokens
Tokens hex de la paleta AI.
src/components/ai/tokens.ts

Charts

GoalScorecard
Scorecard meta vs. progreso.
src/components/charts/GoalScorecard.tsx
SmartLabel
Labels inteligentes para Recharts.
src/components/charts/SmartLabel.tsx

Brand · sistema

PageHeader
Header de página + Section helper.
src/components/brand/PageHeader.tsx
ClickupSidebar
Sidebar dual ClickUp-style.
src/components/brand/ClickupSidebar.tsx+4 deps
SidebarSearch
Búsqueda semántica del sidebar.
src/components/brand/SidebarSearch.tsx+2 deps
AgentSpec
Bloque de spec en markdown copiable.
src/components/brand/AgentSpec.tsx
ComponentFrame
Wrapper con botón 'Copiar componente'.
src/components/brand/ComponentFrame.tsx+1 deps
CodeSnippet
Preview + código copiable lado a lado.
src/components/brand/CodeSnippet.tsx+1 deps
CopyableBlock
Wrapper inline para data-viz snippets.
src/components/brand/CopyableBlock.tsx+1 deps
Swatch
Swatch de color con hex.
src/components/brand/Swatch.tsx