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.
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.
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.
Componentes copiables
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.
/api/snippets/colors.md/api/snippets/colors.mobile.md/api/snippets/colors/color-tokens-css.md/api/snippets/colors/color-swatch.md: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);
}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>
);
}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.
Componentes copiables
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.
/api/snippets/typography.md/api/snippets/typography.mobile.md/api/snippets/typography/typography-scale.mdDisplay
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>03 · Logo
# Logo — Especificación completa
## 1. Propósito y contexto
La marca primaria de Farmatodo está formada por **isotipo** (cuatro pétalos
inscritos en cuadrado) + **logotipo** (wordmark "farmatodo"). Aparece en
cabeceras de producto, splash screens, recibos, firmas de email, materiales
impresos y cualquier punto de contacto donde la marca deba identificarse.
> Regla de oro: el logo es **inalterable**. No se reconstruye, no se separa,
> no se rota, no se redibuja. Se usa siempre desde los archivos oficiales.
## 2. Versiones oficiales
| ID | Archivo | Cuándo usar |
|---|---|---|
| Horizontal · Color | `farmatodo-horizontal.png` | Cabeceras digitales, firmas |
| Vertical · Color | `farmatodo-vertical.png` | Espacios cuadrados, avatares grandes |
| Horizontal · Blanco | `farmatodo-horizontal-white.svg` | Sobre azul brand o negro |
Import canónico (Vite + React):
```tsx
import horizontal from "@/assets/farmatodo-horizontal.png";
import vertical from "@/assets/farmatodo-vertical.png";
import horizontalWhite from "@/assets/farmatodo-horizontal-white.svg";
```
## 3. Construcción
- **Wordmark**: "farmatodo" en Gotham Bold, todo en minúsculas, tracking
ajustado (-0.5%).
- **Isotipo**: cuatro pétalos inscritos en cuadrado redondeado (radio 12% del
lado). Pétalos en azules brand (`#002858`, `#0040B9`, `#1465FF`).
- **Relación isotipo:wordmark**: la altura del isotipo = altura de la "x" del
wordmark × 2.6.
## 4. Área de seguridad (clear space)
La unidad **X** = altura de un pétalo del isotipo.
| Margen | Mínimo | Recomendado |
|---|---|---|
| Top | 1X | 2X |
| Right | 1X | 2X |
| Bottom | 1X | 2X |
| Left | 1X | 2X |
Dentro del área de seguridad **no puede haber** texto, imagen, borde, badge
ni cualquier otro elemento visual.
## 5. Tamaños mínimos
| Medio | Mínimo legible | Recomendado |
|---|---|---|
| Pantalla (digital) | 80px de ancho · 24px de alto | 120px / 32px |
| Impreso | 20mm de ancho · 8mm de alto | 30mm / 12mm |
| Favicon | usar isotipo 32×32 | 64×64 |
## 6. Cromática · versiones de color
| Versión | Fondo | Logo | Token fondo |
|---|---|---|---|
| Positivo · Azul | Blanco | Color original | `#FFFFFF` |
| Negativo · Blanco | Azul Farmatodo | Blanco | `#002858` |
| Una tinta · Blanco | Negro | Blanco | `#000000` |
| Una tinta · Azul | Cualquier claro | `#002858` | — |
Nunca usar una tinta de marca distinta (rojo, verde, gris) sin aprobación.
## 7. Do / Don't (con ejemplo)
**DO**
- Usar sobre fondos planos con contraste AA (≥ 4.5:1 contra el wordmark).
- Mantener proporciones — escalar siempre con shift en herramientas vectoriales.
- Combinar isotipo + wordmark íntegros.
**DON'T**
- ❌ Estirar, rotar, inclinar.
- ❌ Cambiar tipografía del wordmark.
- ❌ Aplicar gradientes, sombras, glow, outline.
- ❌ Encerrar en marcos o badges.
- ❌ Colocar sobre imágenes con bajo contraste o textura ruidosa.
- ❌ Reconstruir desde cero o usar versiones antiguas.
## 8. Accesibilidad
- `alt="Farmatodo"` cuando el logo identifique la marca.
- `alt=""` (decorativo) cuando aparece junto a texto que ya menciona Farmatodo.
- Versión SVG preferida: escala sin pérdida y permite `role="img"` + `<title>`.
## 9. Snippet de uso
```tsx
// Cabecera oscura
<header className="bg-[#002858] flex items-center px-6 h-14">
<img src={horizontalWhite} alt="Farmatodo" className="h-6" />
</header>
// Cabecera clara
<header className="bg-background flex items-center px-6 h-14 border-b border-border">
<img src={horizontal} alt="Farmatodo" className="h-6" />
</header>
```
## 10. QA Checklist
- [ ] Versión correcta para el fondo (positivo / negativo / una tinta).
- [ ] Área de seguridad ≥ 1X en todos los lados.
- [ ] Tamaño mínimo cumplido (80px digital / 20mm impreso).
- [ ] Sin filtros, sombras ni efectos.
- [ ] Proporciones originales (sin distorsión).
- [ ] `alt` correcto según contexto (informativo vs decorativo).
- [ ] Contraste AA contra fondo.
Componentes copiables
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.
/api/snippets/logo.md/api/snippets/logo.mobile.md/api/snippets/logo/logo-import.mdimport logoWhite from "@/assets/farmatodo-horizontal-white.svg";
export function BrandHeader() {
return (
<div
className="px-6 py-4 flex items-center"
style={{ backgroundColor: "#0040B9" }}
>
<img src={logoWhite} alt="Farmatodo" className="h-6" />
</div>
);
}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.
Componentes copiables
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.
/api/snippets/iconography.md/api/snippets/iconography.mobile.md/api/snippets/iconography/icon-usage.mdimport { 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>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.
Componentes copiables
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.
/api/snippets/product.md/api/snippets/product.mobile.md/api/snippets/product/btn-primary.md/api/snippets/product/btn-secondary.md/api/snippets/product/product-card.md/api/snippets/product/input-field.md/api/snippets/product/mobile-tab-bar.md/api/snippets/product/mobile-bottom-sheet.md<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><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>
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>
);
}<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>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>
);
}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>
);
}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`).
Componentes copiables
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.
/api/snippets/data-viz.md/api/snippets/data-viz.mobile.md/api/snippets/data-viz/kpi-card.md/api/snippets/data-viz/kpi-card-spark.md/api/snippets/data-viz/bar-mini.md<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>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>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>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).
Componentes copiables
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.
/api/snippets/ai-components.md/api/snippets/ai-components.mobile.md/api/snippets/ai-components/ai-spark.md/api/snippets/ai-components/ai-input.md/api/snippets/ai-components/ai-search.md/api/snippets/ai-components/ai-bubbles.md/api/snippets/ai-components/ai-suggestion.md/api/snippets/ai-components/ai-stepper.md/api/snippets/ai-components/ai-task-header.mdimport { Sparkles } from "lucide-react";
export function AiSpark({ className = "w-4 h-4" }: { className?: string }) {
return (
<Sparkles
className={className}
strokeWidth={2}
style={{ color: "#1465FF" }}
/>
);
}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>
);
}<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>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>
);
}<div className="flex flex-wrap gap-2"> <SuggestionChip>Resumir pedido</SuggestionChip> <SuggestionChip>Buscar alternativa</SuggestionChip> <SuggestionChip>Cambiar dirección</SuggestionChip> </div>
<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"
/>Reabastecer Acetaminofén en sede Chapinero
<AgentTaskHeader title="Reabastecer Acetaminofén en sede Chapinero" status="En progreso · paso 2 de 4" />
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.
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
src/components/product/EmployeeHierarchy.tsx+1 depssrc/components/product/SpreadsheetGrid.tsx+1 depssrc/components/product/KanbanBoard.tsx+1 depssrc/components/product/HiringKanban.tsx+1 depssrc/components/product/TimelineGantt.tsxsrc/components/product/CalendarView.tsx+1 depssrc/components/product/RoadmapQuarters.tsxsrc/components/product/SprintPlanner.tsxAI
src/components/ai/AiOnboardingForm.tsx+2 depssrc/components/ai/AiDenseForm.tsx+2 depssrc/components/ai/FieldFeedbackGallery.tsx+2 depssrc/components/ai/index.tsx+1 depssrc/components/ai/tokens.tsCharts
src/components/charts/GoalScorecard.tsxsrc/components/charts/SmartLabel.tsxBrand · sistema
src/components/brand/PageHeader.tsxsrc/components/brand/ClickupSidebar.tsx+4 depssrc/components/brand/SidebarSearch.tsx+2 depssrc/components/brand/AgentSpec.tsxsrc/components/brand/ComponentFrame.tsx+1 depssrc/components/brand/CodeSnippet.tsx+1 depssrc/components/brand/CopyableBlock.tsx+1 depssrc/components/brand/Swatch.tsx