# UI Patterns Avanzados

Patrones de composición production-ready para widgets de banca, seguros e inversiones.

> **Fuente**: Estos patrones están extraídos de `dynamic-ui/stories/patterns/` del Storybook.

---

## Cómo Usar Esta Documentación

Cuando generes un widget y necesites implementar una sección específica:

1. Identifica el patrón que necesitas (ver tabla abajo)
2. Navega a la sección del patrón
3. Elige la variante apropiada
4. Copia el código base y adapta con tus datos/tipos

### Índice de Patrones

| Patrón | Variantes | Mejor Para |
|--------|-----------|------------|
| [Master-Detail](#master-detail) | Basic, Menu | Listas con panel de detalle |
| [Quick Actions](#quick-actions) | Grid, Compact | Acciones rápidas del dashboard |
| [Card Patterns](#card-patterns) | Insurance, Account | Cards de productos/planes |
| [Dashboard View](#dashboard-view) | Summary Cards | Métricas y resumen |
| [Table Patterns](#table-patterns) | Basic, Hover, Complete | Transacciones, historial |
| [Plan Comparator](#plan-comparator) | Table | Comparación de planes |
| [List Group Patterns](#list-group-patterns) | Accounts, Transactions | Listas financieras |
| [Form Patterns](#form-patterns) | OneColumn, TwoColumns | Formularios |

---

## Master-Detail

### Cuándo Usarlo
- Lista de cuentas bancarias con detalle
- Lista de pólizas de seguro
- Lista de transacciones con vista expandida
- Cualquier UI de "seleccionar de lista → ver detalle"

### Variante: Menu (Recomendada para Banca)

**Archivo fuente**: `MasterDetail.stories.tsx` línea 154

```tsx
import { DListGroup, DListGroupItem, DIcon } from '@dynamic-framework/ui-react';

// Layout container con grid de Bootstrap
<div className="border rounded-2 overflow-hidden grid gap-0">
  {/* Master List - 4 columnas en desktop */}
  <div className="g-col-12 g-col-lg-4 border-end border-opacity-25">
    <DListGroup className="list-group-white">
      <DListGroupItem
        as="button"
        active={isSelected}
        iconEnd="ChevronRight"
        onClick={() => onSelect(id)}
      >
        <div>
          <span className="fw-semibold">Título del item</span>
          <small className="text-muted fw-normal d-block">Subtítulo</small>
        </div>
      </DListGroupItem>
    </DListGroup>
  </div>

  {/* Detail Panel - 8 columnas en desktop */}
  <div className="g-col-12 g-col-lg-8 bg-white p-4">
    {selectedId ? (
      <DetailComponent />
    ) : (
      <div className="text-center text-muted py-5">
        <DIcon icon="MousePointerClick" size="3rem" className="mb-3 opacity-50" />
        <p>Selecciona un elemento para ver el detalle</p>
      </div>
    )}
  </div>
</div>
```

### Props Clave de DListGroupItem

| Prop | Tipo | Descripción |
|------|------|-------------|
| `as="button"` | string | Hace el item clickeable |
| `active` | boolean | Resalta el item seleccionado |
| `iconEnd` | string | Icono al final (ej: "ChevronRight") |
| `iconStart` | string | Icono al inicio |

### Integración con Zustand

```tsx
// store/useUIStore.ts
import { create } from 'zustand';

interface UIState {
  selectedId: string | null;
  setSelectedId: (id: string | null) => void;
}

export const useUIStore = create<UIState>((set) => ({
  selectedId: null,
  setSelectedId: (id) => set({ selectedId: id }),
}));
```

### ⚠️ Contraste en Items Seleccionados

When `DListGroupItem` has `active={true}`, background becomes primary blue. MUST swap inner colors:

| Element | Normal | Selected |
|---------|--------|----------|
| DIcon color | `"primary"` | `"light"` |
| Subtext | `text-muted` | `opacity-75` |
| Danger text | `text-danger` | `text-warning` |
| DBadge | `soft` | `color="light"` no `soft` |

Helper: `getActiveItemStyles(isActive)` returns `{ iconColor, subtextClass, dangerTextClass, badgeTheme }`.

---

## Quick Actions

### Cuándo Usarlo
- Acciones principales del usuario (transferir, pagar, recargar)
- Hero section de dashboard
- Acceso rápido a funcionalidades frecuentes

### Variante: Grid 2x2

Use `DLayout` with `DLayout.Pane cols="6" colsMd="3"`. Each action is a `<button>` with `DIcon hasCircle` + label + description. Data array: `{ id, icon, label, description, color }`.

```tsx
<DLayout gap={3}>
  {quickActions.map((action) => (
    <DLayout.Pane key={action.id} cols="6" colsMd="3">
      <button type="button" className="d-flex flex-column align-items-center p-4 bg-white border rounded w-100"
        style={{ minHeight: '160px' }} onClick={() => onAction(action.id)}>
        <DIcon icon={action.icon} hasCircle size="2.5rem" className={`mb-3 ${action.color}`} />
        <h6 className="mb-1">{action.label}</h6>
        <small className="text-body-secondary">{action.description}</small>
      </button>
    </DLayout.Pane>
  ))}
</DLayout>
```

---

## Card Patterns

### Cuándo Usarlo
- Mostrar planes de seguro
- Productos de inversión
- Tarjetas de crédito/débito
- Ofertas destacadas

### Variante: Insurance Plan

**Archivo fuente**: `CardPatterns.stories.tsx`

```tsx
import { DCard, DIcon, DButton } from '@dynamic-framework/ui-react';

interface InsurancePlan {
  id: string;
  name: string;
  price: number;
  features: { text: string; included: boolean }[];
  highlighted?: boolean;
}

export function InsurancePlanCard({ plan, onSelect }: {
  plan: InsurancePlan;
  onSelect: (id: string) => void;
}) {
  return (
    <DCard
      className={`w-100 ${plan.highlighted ? 'bg-primary text-white' : ''}`}
      style={{ maxWidth: '350px' }}
    >
      <DCard.Body className="text-center">
        <DIcon
          icon={plan.highlighted ? 'Shield' : 'ShieldCheck'}
          size="3rem"
          className={`mb-3 ${plan.highlighted ? 'text-white' : 'text-primary'}`}
        />
        <h5 className="card-title">{plan.name}</h5>
        <p className="h2 my-4">${plan.price}/mo</p>
        <ul className="list-unstyled text-start mb-4">
          {plan.features.map((feature, idx) => (
            <li key={idx} className="mb-2">
              <DIcon
                icon={feature.included ? 'CheckCircle' : 'XCircle'}
                className={feature.included
                  ? (plan.highlighted ? 'text-white' : 'text-success')
                  : 'text-muted'
                }
              />
              <span className={`ms-2 ${!feature.included ? 'text-muted' : ''}`}>
                {feature.text}
              </span>
            </li>
          ))}
        </ul>
        <DButton
          color={plan.highlighted ? 'light' : 'primary'}
          className="w-100"
          text="Seleccionar"
          onClick={() => onSelect(plan.id)}
        />
      </DCard.Body>
    </DCard>
  );
}
```

### Layout Grid para Plans

```tsx
import { CSSProperties } from 'react';

export function InsurancePlansGrid({ plans }: { plans: InsurancePlan[] }) {
  return (
    <div
      className="grid gap-4"
      style={{ '--bs-columns': 3 } as CSSProperties}
    >
      {plans.map((plan) => (
        <InsurancePlanCard
          key={plan.id}
          plan={plan}
          onSelect={handleSelect}
        />
      ))}
    </div>
  );
}
```

---

## Dashboard View

### Cuándo Usarlo
- Página principal de aplicación financiera
- Resumen de situación del cliente
- Vista ejecutiva con métricas

### Variante: Summary Cards

Grid of metric cards (`g-col-12 g-col-md-4`). Each card: title (muted small), value (h3), optional percentage with TrendingUp/Down icon, and category icon.

```tsx
<div className="grid gap-4">
  {summaryData.map((item) => (
    <div key={item.id} className="g-col-12 g-col-md-4">
      <DBox className="p-4 bg-white rounded-3 shadow-sm">
        <div className="d-flex align-items-start justify-content-between">
          <div>
            <p className="text-muted small mb-1">{item.title}</p>
            <h3 className="mb-0">{item.value}</h3>
            {item.percentage && (
              <small className={item.percentage > 0 ? 'text-success' : 'text-danger'}>
                <DIcon icon={item.percentage > 0 ? 'TrendingUp' : 'TrendingDown'} size="0.75rem" />
                {' '}{Math.abs(item.percentage)}%
              </small>
            )}
          </div>
          <DIcon icon={item.icon} size="2rem" className={item.color} />
        </div>
      </DBox>
    </div>
  ))}
</div>
```

---

## Table Patterns

### Cuándo Usarlo
- Lista de transacciones
- Historial de movimientos
- Cualquier dato tabular

### Variante: Basic Table

**Archivo fuente**: `TablePatterns.stories.tsx`

```tsx
// Tabla básica con Bootstrap
<table className="table">
  <thead>
    <tr>
      <th>#</th>
      <th>Descripción</th>
      <th>Fecha</th>
      <th className="text-end">Monto</th>
    </tr>
  </thead>
  <tbody>
    {transactions.map((tx) => (
      <tr key={tx.id}>
        <td>{tx.id}</td>
        <td>{tx.description}</td>
        <td>{tx.date}</td>
        <td className={`text-end ${tx.amount < 0 ? 'text-danger' : 'text-success'}`}>
          {tx.amount < 0 ? '-' : '+'}${Math.abs(tx.amount).toLocaleString()}
        </td>
      </tr>
    ))}
  </tbody>
</table>
```

### Variantes de Estilo de Tabla

| Clase | Efecto |
|-------|--------|
| `table` | Tabla base |
| `table-striped` | Filas alternadas |
| `table-hover` | Hover en filas |
| `table-bordered` | Con bordes |
| `table-sm` | Compacta |
| `table-dark` | Tema oscuro |

### Variante: Table with Selection + Pagination

Use `useItemSelection<T>()` hook for checkbox selection (`isSelectedItem`, `toggleSelectedItem`, `selectedItems`). Add `DPaginator` below table: `<DPaginator current={page} onPageChange={setPage} total={totalPages} />`.

**Loading:** `<table className="table placeholder-wave">` with `<div className="placeholder rounded-1 bg-gray-100 w-100" />` per cell.

---

## Plan Comparator

Use a `<table className="table text-center">` with plan names in `<thead>` and features as rows. For boolean features, render `<DIcon icon="CheckCircle" className="text-success" />` or `<DIcon icon="X" className="text-danger" />`. Highlight the recommended plan column with `bg-primary-subtle`. Add `<DButton>` in `<tfoot>` for plan selection. Data structure:

```tsx
interface PlanFeature {
  name: string;
  free: boolean | string;
  basic: boolean | string;
  pro: boolean | string;
}
// Iterate plans as columns, features as rows
```

---

## List Group Patterns

Use `DListGroup` + `DListGroup.Item` for account lists, transaction history, and settings lists. Each item uses `d-flex justify-content-between` layout with name/subtitle on left and amount/status on right. Use `text-danger`/`text-success` for debit/credit amounts, `DBadge soft` for category tags.

---

## Form Patterns

### Cuándo Usarlo
- Formularios de registro
- Formularios de pago
- Configuración de usuario

### Variante: Two Columns

Use `<div className="grid gap-3">` with `g-col-6` for two-column layout. Wrap in `<DBox className="p-4">` and `<fieldset>`.

```tsx
<div className="grid gap-3">
  <div className="g-col-6"><DInput id="firstName" label="First Name" /></div>
  <div className="g-col-6"><DInput id="lastName" label="Last Name" /></div>
  <div className="g-col-6"><DInput id="email" type="email" label="Email" /></div>
  <div className="g-col-6"><DSelect id="country" label="Country" options={countryOptions} /></div>
  <div className="g-col-12"><DInputSwitch id="notifications" label="Enable notifications" /></div>
  <div className="g-col-12">
    <DButton type="submit" text="Submit" />
    <DButton type="reset" variant="outline" text="Reset" />
  </div>
</div>
```

---

## Referencia Rápida de Clases CSS

Standard Bootstrap 5 utilities. Key classes used in patterns:

- **Grid**: `grid`, `g-col-{n}`, `g-col-{breakpoint}-{n}`, `gap-{n}`
- **Flex**: `d-flex`, `flex-column`, `align-items-center`, `justify-content-between`, `w-100`
- **Text**: `text-center/start/end`, `text-muted`, `text-success/danger/primary`, `fw-bold/fw-semibold`
- **Spacing**: `mb-{n}`, `mt-{n}`, `p-{n}`, `gap-{n}` (0-5)
- **Border**: `border`, `border-end/start`, `rounded-{n}`, `border-opacity-25`

---

## Checklists

**Implementation:** Adapt types to domain, connect Zustand (UI state) + TanStack Query (server state), PascalCase Lucide icons, responsive breakpoints, i18n translations.

**QA Visual:** Selected items legible (swap colors for active), `bg-primary` only on interactive/selected elements, hover/focus/disabled states, mobile layout works, touch targets >= 44x44px.

**Accessibility:** `label`/`aria-label` on inputs, descriptive button text, icon-only buttons need `aria-label`, keyboard navigation, color not sole indicator. See `modyo://docs/widgets/reference-accessibility`.

## Source Files

All patterns from `dynamic-ui/stories/patterns/`: `MasterDetail`, `QuickActionsPatterns`, `CardPatterns`, `DashboardView`, `TablePatterns`, `PlanComparator`, `ListGroupPatterns`, `FormPatterns` (all `.stories.tsx`).
