# Design System — EMI / Falck > Sistema de diseño derivado de la identidad visual de **EMI (Grupo Falck)**. > Toda la implementación CSS sigue la metodología **BEM** (`block__element--modifier`) > y un enfoque **mobile-first responsive**. --- ## 1. Filosofía de diseño EMI es una marca de **atención médica de urgencia 24/7**. El sistema visual debe transmitir: | Atributo | Cómo se traduce en UI | | --------------- | ------------------------------------------------------------------ | | **Confianza** | Tipografía sólida, alto contraste, espaciado generoso | | **Urgencia** | Rojo coral como acento dominante en CTAs | | **Profesional** | Vinotinto profundo (burgundy) como color institucional | | **Cercanía** | Bordes redondeados (`pill` shape en botones), iconografía amable | | **Accesible** | Contrastes AA/AAA, áreas táctiles ≥ 44×44px, foco visible | **Regla de oro**: el rojo coral se usa con moderación, **solo para acciones primarias**. El vinotinto domina secciones institucionales. El blanco respira. --- ## 2. Paleta de colores ### 2.1. Colores de marca (Brand) | Token | HEX | RGB | Uso | | ------------------------ | --------- | ---------------- | ---------------------------------------------- | | `--color-brand-primary` | `#E63946` | `230, 57, 70` | CTAs principales, botones "Conoce más" | | `--color-brand-primary-hover` | `#C92A3B` | `201, 42, 59` | Hover state del CTA primario | | `--color-brand-primary-active` | `#A8202F` | `168, 32, 47` | Active/pressed state | | `--color-brand-secondary` | `#6B1F2A` | `107, 31, 42` | Vinotinto institucional, top bar, fondos | | `--color-brand-secondary-dark` | `#4D1620` | `77, 22, 32` | Variante oscura del vinotinto | | `--color-brand-accent` | `#D32F2F` | `211, 47, 47` | Rojo del logo, detalles iconográficos | ### 2.2. Colores de soporte (Support) | Token | HEX | Uso | | ---------------------- | --------- | ------------------------------------------------ | | `--color-success` | `#25D366` | Botón WhatsApp, estados completados | | `--color-success-dark` | `#1EA952` | Hover del verde | | `--color-warning` | `#F59E0B` | Alertas no críticas | | `--color-danger` | `#DC2626` | Errores, eliminaciones, validaciones falladas | | `--color-info` | `#0EA5E9` | Mensajes informativos | ### 2.3. Neutrales (Scale) | Token | HEX | Uso típico | | ------------------ | --------- | ----------------------------------------- | | `--color-white` | `#FFFFFF` | Fondos de cards, textos sobre vinotinto | | `--color-gray-50` | `#F9FAFB` | Fondo de página, secciones suaves | | `--color-gray-100` | `#F3F4F6` | Hovers sutiles, dividers | | `--color-gray-200` | `#E5E7EB` | Bordes de inputs, separadores | | `--color-gray-300` | `#D1D5DB` | Bordes activos, placeholders | | `--color-gray-400` | `#9CA3AF` | Texto deshabilitado, iconos secundarios | | `--color-gray-500` | `#6B7280` | Texto secundario, labels | | `--color-gray-600` | `#4B5563` | Texto de cuerpo | | `--color-gray-700` | `#374151` | Texto principal en fondos claros | | `--color-gray-800` | `#1F2937` | Headings sobre blanco | | `--color-gray-900` | `#111827` | Texto de máximo contraste | ### 2.4. Tokens semánticos > Estos tokens **referencian** los anteriores. Los componentes consumen estos, no los HEX directos. ```css :root { /* Backgrounds */ --bg-page: var(--color-gray-50); --bg-surface: var(--color-white); --bg-elevated: var(--color-white); --bg-inverse: var(--color-brand-secondary); --bg-overlay: rgba(17, 24, 39, 0.6); /* Text */ --text-primary: var(--color-gray-900); --text-secondary: var(--color-gray-600); --text-muted: var(--color-gray-400); --text-inverse: var(--color-white); --text-link: var(--color-brand-primary); --text-link-hover: var(--color-brand-primary-hover); /* Borders */ --border-subtle: var(--color-gray-200); --border-default: var(--color-gray-300); --border-strong: var(--color-gray-400); --border-focus: var(--color-brand-primary); /* States */ --state-error: var(--color-danger); --state-success: var(--color-success); --state-warning: var(--color-warning); } ``` ### 2.5. Dark mode (opcional, recomendado) ```css @media (prefers-color-scheme: dark) { :root { --bg-page: #0F0A0B; --bg-surface: #1A1214; --bg-elevated: #241719; --text-primary: #F5F5F5; --text-secondary: #B8B8B8; --border-subtle: #2D1F22; /* El rojo y vinotinto se mantienen, son colores de marca */ } } ``` --- ## 3. Tipografía ### 3.1. Stack de fuentes ```css :root { /* Display: para titulares grandes (Hero) — peso fuerte, condensada */ --font-display: 'Sora', 'Plus Jakarta Sans', system-ui, sans-serif; /* Body: legibilidad para párrafos y UI */ --font-body: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif; /* Mono: código, números tabulares */ --font-mono: 'JetBrains Mono', 'Fira Code', Menlo, monospace; } ``` > **Nota**: Sora se usa porque tiene el peso visual robusto que se observa en los titulares de EMI ("Somos atención médica…"). Si se requiere licencia comercial sin Google Fonts, alternativa: **Manrope** o **Plus Jakarta Sans**. ### 3.2. Escala tipográfica (mobile-first, escala fluida) | Token | Mobile | Desktop | Uso | | ---------------- | ------ | ------- | ---------------------------------- | | `--fs-xs` | 12px | 12px | Captions, labels pequeños | | `--fs-sm` | 14px | 14px | Texto secundario, helper text | | `--fs-base` | 16px | 16px | Cuerpo de texto (default) | | `--fs-md` | 18px | 18px | Subtítulos, lead paragraph | | `--fs-lg` | 20px | 24px | H4, destacados | | `--fs-xl` | 24px | 32px | H3 | | `--fs-2xl` | 30px | 40px | H2 (ej: "Nuestros planes") | | `--fs-3xl` | 36px | 56px | H1 hero (ej: "Somos atención…") | | `--fs-4xl` | 44px | 72px | Display, posters | ```css :root { --fs-xs: 0.75rem; --fs-sm: 0.875rem; --fs-base: 1rem; --fs-md: 1.125rem; --fs-lg: clamp(1.25rem, 1rem + 1vw, 1.5rem); --fs-xl: clamp(1.5rem, 1.2rem + 1.5vw, 2rem); --fs-2xl: clamp(1.875rem, 1.5rem + 2vw, 2.5rem); --fs-3xl: clamp(2.25rem, 1.8rem + 3vw, 3.5rem); --fs-4xl: clamp(2.75rem, 2rem + 4vw, 4.5rem); } ``` ### 3.3. Pesos ```css :root { --fw-regular: 400; --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700; --fw-extrabold: 800; /* Para hero titles */ } ``` ### 3.4. Line heights ```css :root { --lh-tight: 1.1; /* Headings grandes */ --lh-snug: 1.25; /* Headings medianos */ --lh-normal: 1.5; /* Body */ --lh-relaxed: 1.65; /* Párrafos largos */ } ``` ### 3.5. Letter spacing ```css :root { --tracking-tighter: -0.04em; /* Hero titles */ --tracking-tight: -0.02em; /* H2-H3 */ --tracking-normal: 0; /* Body */ --tracking-wide: 0.05em; /* Eyebrows, uppercase labels */ } ``` --- ## 4. Espaciado (Spacing Scale) Sistema basado en **múltiplos de 4px**, alineado con buenas prácticas de Material/Tailwind: ```css :root { --space-0: 0; --space-1: 0.25rem; /* 4px */ --space-2: 0.5rem; /* 8px */ --space-3: 0.75rem; /* 12px */ --space-4: 1rem; /* 16px */ --space-5: 1.25rem; /* 20px */ --space-6: 1.5rem; /* 24px */ --space-8: 2rem; /* 32px */ --space-10: 2.5rem; /* 40px */ --space-12: 3rem; /* 48px */ --space-16: 4rem; /* 64px */ --space-20: 5rem; /* 80px */ --space-24: 6rem; /* 96px */ --space-32: 8rem; /* 128px */ } ``` **Regla de aplicación**: - Espaciado **dentro** de un componente (padding interno): `--space-2` a `--space-6`. - Espaciado **entre** componentes: `--space-8` a `--space-16`. - Espaciado **entre secciones**: `--space-20` a `--space-32`. --- ## 5. Radios de borde ```css :root { --radius-none: 0; --radius-sm: 4px; --radius-md: 8px; --radius-lg: 12px; --radius-xl: 16px; --radius-2xl: 24px; --radius-pill: 9999px; /* Botones tipo "Conoce más" */ --radius-full: 50%; } ``` > **Identidad EMI**: los CTAs primarios usan `--radius-pill` (forma de cápsula). Es un sello distintivo de la marca y debe respetarse. --- ## 6. Sombras ```css :root { --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05); --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); /* Sombra de marca (tinte rojo sutil) para CTAs elevados */ --shadow-brand: 0 10px 25px -5px rgba(230, 57, 70, 0.35); } ``` --- ## 7. Breakpoints responsive (Mobile-first) ```css :root { --bp-xs: 360px; /* Mobile pequeño */ --bp-sm: 480px; /* Mobile estándar */ --bp-md: 768px; /* Tablet */ --bp-lg: 1024px; /* Desktop pequeño */ --bp-xl: 1280px; /* Desktop estándar */ --bp-2xl: 1536px; /* Desktop grande */ } ``` ### Convención de uso (mobile-first SIEMPRE): ```scss .task-list { // Default: mobile display: grid; grid-template-columns: 1fr; gap: var(--space-4); // Tablet en adelante @media (min-width: 768px) { grid-template-columns: repeat(2, 1fr); gap: var(--space-6); } // Desktop en adelante @media (min-width: 1024px) { grid-template-columns: repeat(3, 1fr); gap: var(--space-8); } } ``` **Regla estricta**: nunca usar `max-width` como base. Siempre escalar **de pequeño a grande**. ### Container widths ```css :root { --container-sm: 640px; --container-md: 768px; --container-lg: 1024px; --container-xl: 1200px; --container-2xl: 1440px; } .container { width: 100%; max-width: var(--container-xl); margin-inline: auto; padding-inline: var(--space-4); @media (min-width: 768px) { padding-inline: var(--space-8); } } ``` --- ## 8. Z-index Scale ```css :root { --z-base: 0; --z-dropdown: 100; --z-sticky: 200; --z-fixed: 300; --z-modal-backdrop: 400; --z-modal: 500; --z-popover: 600; --z-tooltip: 700; --z-toast: 800; --z-floating-cta: 900; /* Botón flotante WhatsApp */ } ``` --- ## 9. Transiciones y animaciones ```css :root { --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1); --transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1); /* Easings nombrados */ --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); --ease-out: cubic-bezier(0, 0, 0.2, 1); --ease-in: cubic-bezier(0.4, 0, 1, 1); --ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); } ``` **Accesibilidad**: respetar siempre `prefers-reduced-motion`: ```css @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } } ``` --- ## 10. Metodología BEM — Convenciones obligatorias ### 10.1. Estructura ``` .block { } /* Bloque independiente */ .block__element { } /* Parte interna del bloque */ .block--modifier { } /* Variante del bloque */ .block__element--modifier { } /* Variante de un elemento */ ``` ### 10.2. Reglas - **No anidar selectores** en CSS más de 1 nivel (el nombre BEM ya da la jerarquía). - **No usar selectores de tipo** (`div`, `span`) dentro de un bloque BEM. - **Nombres en kebab-case**: `task-card__action-button--disabled`. - **Un componente = un bloque**: si reutilizas algo, conviértelo en su propio bloque. - **Modifiers booleanos**: `--active`, `--disabled`, `--loading`. - **Modifiers de valor**: `--size-lg`, `--variant-primary`. ### 10.3. Ejemplo correcto ```html

Revisar contratos

Completado

Lorem ipsum…

``` ```scss .task-card { background-color: var(--bg-surface); border-radius: var(--radius-lg); padding: var(--space-4); box-shadow: var(--shadow-sm); transition: box-shadow var(--transition-base); &--completed { opacity: 0.7; border-left: 4px solid var(--color-success); } &:hover { box-shadow: var(--shadow-md); } } .task-card__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-3); } .task-card__title { font-family: var(--font-display); font-size: var(--fs-lg); font-weight: var(--fw-semibold); color: var(--text-primary); margin: 0; } .task-card__badge { font-size: var(--fs-xs); padding: var(--space-1) var(--space-2); border-radius: var(--radius-pill); &--success { background-color: var(--color-success); color: var(--color-white); } } .task-card__description { font-size: var(--fs-sm); color: var(--text-secondary); line-height: var(--lh-normal); } .task-card__footer { display: flex; gap: var(--space-2); margin-top: var(--space-4); @media (min-width: 768px) { justify-content: flex-end; } } .task-card__action { padding: var(--space-2) var(--space-4); border-radius: var(--radius-md); font-size: var(--fs-sm); font-weight: var(--fw-medium); cursor: pointer; transition: background-color var(--transition-fast); &--edit { background-color: var(--color-brand-primary); color: var(--color-white); &:hover { background-color: var(--color-brand-primary-hover); } } &--delete { background-color: transparent; color: var(--color-danger); border: 1px solid var(--color-danger); &:hover { background-color: var(--color-danger); color: var(--color-white); } } } ``` ### 10.4. Lo que NUNCA debe hacerse ```scss /* ❌ MAL: anidamiento excesivo */ .task-card { .header { .title { ... } } } /* ❌ MAL: selector de tipo */ .task-card h3 { ... } /* ❌ MAL: doble guion bajo encadenado */ .task-card__header__title { ... } /* ❌ MAL: modifier sin bloque padre */ .--disabled { ... } ``` --- ## 11. Componentes base (referencia de marca EMI) ### 11.1. Botón primario (CTA "Conoce más" / "Afíliate en línea") ```html ``` ```scss .btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); font-family: var(--font-body); font-weight: var(--fw-semibold); border: none; cursor: pointer; transition: all var(--transition-base); text-decoration: none; min-height: 44px; /* Accesibilidad: área táctil mínima */ &:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; } &:disabled { opacity: 0.5; cursor: not-allowed; } /* Variantes */ &--primary { background-color: var(--color-brand-primary); color: var(--color-white); border-radius: var(--radius-pill); box-shadow: var(--shadow-brand); &:hover:not(:disabled) { background-color: var(--color-brand-primary-hover); transform: translateY(-1px); } } &--outline { background-color: transparent; color: var(--color-brand-primary); border: 2px solid var(--color-brand-primary); border-radius: var(--radius-pill); &:hover:not(:disabled) { background-color: var(--color-brand-primary); color: var(--color-white); } } &--whatsapp { background-color: var(--color-success); color: var(--color-white); border-radius: var(--radius-pill); &:hover:not(:disabled) { background-color: var(--color-success-dark); } } /* Tamaños */ &--sm { padding: var(--space-2) var(--space-4); font-size: var(--fs-sm); } &--md { padding: var(--space-3) var(--space-6); font-size: var(--fs-base); } &--lg { padding: var(--space-4) var(--space-8); font-size: var(--fs-md); } } ``` ### 11.2. Input de formulario ```html
Este campo es obligatorio
``` ```scss .form-field { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); &--error { .form-field__input { border-color: var(--state-error); } } &__label { font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-secondary); } &__input { padding: var(--space-3) var(--space-4); font-size: var(--fs-base); font-family: var(--font-body); border: 1px solid var(--border-default); border-radius: var(--radius-md); background-color: var(--bg-surface); color: var(--text-primary); transition: border-color var(--transition-fast); min-height: 44px; &:focus { outline: none; border-color: var(--border-focus); box-shadow: 0 0 0 3px rgba(230, 57, 70, 0.15); } } &__error { font-size: var(--fs-xs); color: var(--state-error); } } ``` ### 11.3. Card ```scss .card { background-color: var(--bg-surface); border-radius: var(--radius-lg); padding: var(--space-4); box-shadow: var(--shadow-sm); @media (min-width: 768px) { padding: var(--space-6); } &--elevated { box-shadow: var(--shadow-lg); } &--bordered { border: 1px solid var(--border-subtle); box-shadow: none; } &--inverse { background-color: var(--bg-inverse); color: var(--text-inverse); } } ``` --- ## 12. Accesibilidad (no negociable) - **Contraste mínimo AA**: 4.5:1 para texto normal, 3:1 para texto grande. - **Foco visible**: usar `:focus-visible`, nunca remover el outline sin reemplazarlo. - **Áreas táctiles**: mínimo 44×44 px en cualquier elemento interactivo. - **Roles ARIA**: usar `role`, `aria-label`, `aria-describedby` cuando el elemento no sea semántico nativo. - **Skip links**: ``. - **Lang en el HTML**: ``. - **Imágenes**: siempre con `alt` descriptivo o `alt=""` si son decorativas. --- ## 13. Checklist antes de hacer commit - [ ] Todos los colores vienen de tokens CSS, **nunca hex directos** en componentes. - [ ] Clases siguen BEM (`block__element--modifier`). - [ ] Sin selectores anidados más allá de 1 nivel. - [ ] Mobile-first: el CSS por defecto es para mobile, los `@media` escalan hacia arriba. - [ ] Botones interactivos tienen `:hover`, `:focus-visible`, `:disabled`. - [ ] Áreas táctiles ≥ 44px de altura. - [ ] `prefers-reduced-motion` respetado en animaciones. - [ ] Contraste verificado con WebAIM Contrast Checker. - [ ] Layout probado en 360px, 768px, 1024px, 1440px. - [ ] Sin `!important` salvo en utilidades. --- ## 14. Estructura recomendada de archivos SCSS ``` src/styles/ ├── abstracts/ │ ├── _variables.scss # CSS custom properties (tokens) │ ├── _mixins.scss # Mixins (responsive, focus, etc.) │ └── _functions.scss ├── base/ │ ├── _reset.scss # Normalize / reset moderno │ ├── _typography.scss # Estilos base de h1-h6, p, etc. │ └── _global.scss # html, body, * ├── components/ │ ├── _button.scss │ ├── _card.scss │ ├── _form-field.scss │ └── _task-card.scss ├── layouts/ │ ├── _header.scss │ ├── _footer.scss │ └── _container.scss ├── utilities/ │ └── _helpers.scss # .sr-only, .visually-hidden, etc. └── main.scss # Punto de entrada ``` --- ## 15. Mixin útil para media queries ```scss // abstracts/_mixins.scss @mixin respond-to($breakpoint) { @if $breakpoint == sm { @media (min-width: 480px) { @content; } } @else if $breakpoint == md { @media (min-width: 768px) { @content; } } @else if $breakpoint == lg { @media (min-width: 1024px) { @content; } } @else if $breakpoint == xl { @media (min-width: 1280px) { @content; } } } // Uso: .task-list { grid-template-columns: 1fr; @include respond-to(md) { grid-template-columns: repeat(2, 1fr); } @include respond-to(lg) { grid-template-columns: repeat(3, 1fr); } } ``` --- **Última actualización**: 2026-05-13 **Mantenedor**: equipo de frontend **Versión**: 1.0.0