npm package: @onpe/ui
Description: Librería de componentes UI para ONPE - Componentes e Iconos
Author: ONPE
Latest version: 1.3.45

# 🗳️ ONPE UI

Librería completa de componentes de interfaz de usuario para aplicaciones de la Oficina Nacional de Procesos Electorales (ONPE) del Perú.

## ✨ Características

- 🎨 **Colores oficiales de ONPE** - Paleta de colores institucional
- ⚡ **Tailwind CSS v4** - Framework CSS moderno y eficiente
- 🔧 **TypeScript** - Tipado estático para mejor desarrollo
- 📱 **Responsive** - Diseño adaptable a todos los dispositivos
- 🎯 **Accesible** - Componentes que siguen estándares de accesibilidad
- 📦 **Tree-shakable** - Solo importa lo que necesitas

## 🚀 Instalación

### Instalación Completa de la Librería
```bash
npm install @onpe/ui
```

### Instalación Individual de Componentes (CLI)
```bash
# Instalar la CLI globalmente
npm install -g @onpe/ui

# O usar directamente con npx
npx @onpe/ui add <componente>
```

## 🛡️ Integración Sin Conflictos

**Esta librería está diseñada para funcionar perfectamente en proyectos que ya usan Tailwind CSS, Material UI, Shadcn, o cualquier otro framework CSS sin causar conflictos de estilos.**

### ✨ Características de Compatibilidad

- **Prefijos Únicos**: Todas las clases usan el prefijo `onpe-` para evitar conflictos
- **CSS Compilado**: Se genera un CSS optimizado y minificado sin `@import` de Tailwind
- **Variables CSS en `:root`**: Colores con prefijos únicos (`--onpe-ui-blue`, etc.) disponibles globalmente
- **Sin Reset de Tailwind**: No interfiere con tu configuración existente
- **Compatible con**: Material UI, Shadcn, Chakra UI, Ant Design, Bootstrap, etc.
- **CSP Compatible**: Genera archivos CSS externos para cumplir con Content Security Policy

### 🚀 Instalación Rápida

```bash
npm install @onpe/ui
```

```tsx
// Importar estilos compilados (solo una vez en tu app)
// ⚠️ IMPORTANTE: Esto define las variables CSS en :root
import '@onpe/ui/dist/index.css';

// O usando el export específico
import '@onpe/ui/css';

// Usar componentes
import { Button } from '@onpe/ui/components';

function App() {
  return (
    <div>
      {/* Tu contenido existente con Material UI, Shadcn, etc. */}
      <Button color="blue" title="Botón ONPE" />
    </div>
  );
}
```

### 🎨 Variables CSS en `:root`

**Los colores ONPE se definen automáticamente en `:root` cuando importas el CSS:**

```css
:root {
  --onpe-ui-blue: #003770;
  --onpe-ui-skyblue: #0073cf;
  --onpe-ui-skyblue-light: #69b2e8;
  --onpe-ui-yellow: #ffb81c;
  --onpe-ui-light-skyblue: #aaeff6;
  --onpe-ui-gray: #bcbcbc;
  --onpe-ui-gray-light: #bdbdbd;
  --onpe-ui-gray-extra-light: #f2f2f2;
  --onpe-ui-red: #e3002b;
  --onpe-ui-dark-gray: #4f4f4f;
  --onpe-ui-green: #76bd43;
  --onpe-ui-yellow-light: #FFF1D2;
}
```

**Esto permite:**
- ✅ **Uso directo en CSS del proyecto host**
- ✅ **Sobrescribir colores si es necesario**
- ✅ **Compatibilidad con CSP (Content Security Policy)**
- ✅ **Acceso global a los colores ONPE**

### 💡 Uso de Variables CSS en tu Proyecto

**Puedes usar los colores ONPE directamente en tu CSS:**

```css
/* En tu archivo CSS del proyecto host */
.mi-componente {
  color: var(--onpe-ui-blue);
  background: var(--onpe-ui-skyblue-light);
  border: 2px solid var(--onpe-ui-red);
}

.mi-boton-personalizado {
  background: var(--onpe-ui-green);
  color: white;
  padding: 12px 24px;
  border-radius: 8px;
}
```

**Sobrescribir colores si es necesario:**

```css
/* En tu archivo CSS del proyecto host */
:root {
  /* Cambiar el azul principal */
  --onpe-ui-blue: #1a365d;
  
  /* Cambiar el rojo */
  --onpe-ui-red: #c53030;
}
```

**Uso en componentes React con estilos inline:**

```tsx
function MiComponente() {
  return (
    <div 
      style={{
        color: 'var(--onpe-ui-blue)',
        backgroundColor: 'var(--onpe-ui-skyblue-light)',
        padding: '16px',
        borderRadius: '8px'
      }}
    >
      Contenido con colores ONPE
    </div>
  );
}
```

### 🎯 ¿Cómo Evitamos Conflictos?

1. **Prefijos Únicos**: `bg-blue-500` → `onpe-bg-onpe-ui-blue`
2. **CSS Scoped**: Todos los componentes están aislados
3. **Variables CSS Aisladas**: `--onpe-ui-blue` en lugar de `--blue`
4. **Sin Preflight**: No resetea estilos del proyecto host

## 📖 Uso Básico

### Instalar Componentes con CLI

#### Instalar componentes específicos
```bash
# Componentes
npx @onpe/ui add button
npx @onpe/ui add modal
npx @onpe/ui add portal
npx @onpe/ui add overlay
npx @onpe/ui add show

# Iconos
npx @onpe/ui add icon-close
npx @onpe/ui add icon-check
npx @onpe/ui add icon-warning
npx @onpe/ui add icon-chrome
npx @onpe/ui add icon-firefox
npx @onpe/ui add icon-safari
npx @onpe/ui add icon-edge
npx @onpe/ui add icon-windows
npx @onpe/ui add icon-apple
npx @onpe/ui add icon-android
```

#### Usar componentes instalados individualmente
```tsx
// Después de instalar con CLI
import { Button } from './components/onpe-ui/Button';
import { Modal } from './components/onpe-ui/Modal';
import { IconClose } from './components/onpe-icons/IconClose';
import { useState } from 'react';

function App() {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <div className="p-4">
      <Button 
        color="primary" 
        title="Abrir Modal" 
        onClick={() => setIsOpen(true)} 
      />
      
      <Modal 
        isOpen={isOpen} 
        onClose={() => setIsOpen(false)}
        closeButton={true}
        overlayColor="blue"
      >
        <div className="p-6">
          <h2 className="text-xl font-bold mb-4">Contenido del Modal</h2>
          <p className="mb-4">Este es un ejemplo de modal con contenido.</p>
          <div className="flex items-center gap-2">
            <IconClose className="w-4 h-4" />
            <Button 
              color="green" 
              title="Cerrar" 
              onClick={() => setIsOpen(false)} 
            />
          </div>
        </div>
      </Modal>
    </div>
  );
}
```

#### Configuración requerida para componentes individuales

**1. Instalar Tailwind CSS:**
```bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

**2. Configurar PostCSS para Tailwind v4:**
```javascript
// postcss.config.js
export default {
  plugins: {
    '@tailwindcss/postcss': {},
    autoprefixer: {},
  },
}
```

**3. Crear archivo CSS con configuración Tailwind v4:**
```css
/* onpe-ui.css */
@import "tailwindcss";

@theme {
  --color-onpe-ui-blue: #003770;
  --color-onpe-ui-skyblue: #0073cf;
  --color-onpe-ui-skyblue-light: #69b2e8;
  --color-onpe-ui-yellow: #ffb81c;
  --color-onpe-ui-light-skyblue: #aaeff6;
  --color-onpe-ui-gray: #bcbcbc;
  --color-onpe-ui-gray-light: #bdbdbd;
  --color-onpe-ui-gray-extra-light: #f2f2f2;
  --color-onpe-ui-red: #e3002b;
  --color-onpe-ui-dark-gray: #4f4f4f;
  --color-onpe-ui-green: #76bd43;
  --color-onpe-ui-yellow-light: #FFF1D2;
}

/* Clases personalizadas ONPE */
@utility bg-onpe-ui-blue { background-color: var(--color-onpe-ui-blue); }
@utility text-onpe-ui-blue { color: var(--color-onpe-ui-blue); }
@utility border-onpe-ui-blue { border-color: var(--color-onpe-ui-blue); }
/* ... resto de clases personalizadas */
```

**4. Importar el archivo CSS en tu aplicación:**
```tsx
// En tu archivo principal (index.tsx o App.tsx)
import './onpe-ui.css'; // ← IMPORTANTE: Importar primero
import { Button } from './components/onpe-ui/Button';
```

**5. Para componentes que usan Portal, agregar en public/index.html:**
```html
<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8" />
    <title>Mi App</title>
  </head>
  <body>
    <div id="root"></div>
    <div id="portal"></div>
  </body>
</html>
```

## 🎨 Paleta de Colores ONPE

### Colores Principales
- **Azul Principal**: `#003770` - Color institucional principal
- **Sky Blue**: `#0073cf` - Color secundario
- **Sky Blue Light**: `#69b2e8` - Color claro
- **Light Sky Blue**: `#aaeff6` - Color muy claro

### Colores de Acento
- **Amarillo**: `#ffb81c` - Para alertas y destacados
- **Verde**: `#76bd43` - Para éxito y confirmaciones
- **Rojo**: `#e3002b` - Para errores y advertencias

### Escala de Grises
- **Dark Gray**: `#4f4f4f` - Texto principal
- **Gray**: `#bcbcbc` - Texto secundario
- **Gray Light**: `#bdbdbd` - Texto terciario
- **Gray Extra Light**: `#f2f2f2` - Fondos suaves

## 🔗 Dependencias entre Componentes

### Mapa de Dependencias
```
Modal
├── Portal (requerido)
├── Overlay (requerido)
└── IconClose (requerido)

ModalBrowserIncompatible
├── Modal (requerido)
├── IconWarning (requerido)
├── IconChromeColor (requerido)
├── IconSafariColor (requerido)
├── IconMozillaColor (requerido)
└── IconEdgeColor (requerido)

ModalSystemIncompatible
├── Modal (requerido)
├── IconWarning (requerido)
├── IconWindow (requerido)
├── IconAndroid (requerido)
└── IconApple (requerido)

Portal
└── react-dom (createPortal)

Overlay
└── (sin dependencias externas)

Button
└── (sin dependencias externas)

Show
└── (sin dependencias externas)
```

### Instalación Automática de Dependencias

**Modal** - Instala automáticamente sus dependencias:
```bash
npx @onpe/ui add modal
# Esto instalará automáticamente:
# - Portal.tsx
# - Overlay.tsx
# - IconClose.tsx (si está disponible)
```

**ModalBrowserIncompatible** - Instala automáticamente sus dependencias:
```bash
npx @onpe/ui add modal-browser-incompatible
# Esto instalará automáticamente:
# - Modal.tsx
# - IconWarning.tsx
# - IconChromeColor.tsx
# - IconSafariColor.tsx
# - IconMozillaColor.tsx
# - IconEdgeColor.tsx
```

**ModalSystemIncompatible** - Instala automáticamente sus dependencias:
```bash
npx @onpe/ui add modal-system-incompatible
# Esto instalará automáticamente:
# - Modal.tsx
# - IconWarning.tsx
# - IconWindow.tsx
# - IconAndroid.tsx
# - IconApple.tsx
```

**Otros componentes** - Instalación independiente:
```bash
npx @onpe/ui add button    # Sin dependencias
npx @onpe/ui add portal    # Sin dependencias
npx @onpe/ui add overlay   # Sin dependencias
npx @onpe/ui add show      # Sin dependencias
```

### Estructura de Archivos Después de la Instalación

```
src/
└── components/
    ├── ui/           # shadcn/ui (si está instalado)
    │   ├── button.tsx
    │   └── input.tsx
    ├── onpe-ui/      # ONPE UI - Componentes
    │   ├── Button.tsx
    │   ├── Modal.tsx
    │   ├── Overlay.tsx
    │   ├── Portal.tsx
    │   └── Show.tsx
    └── onpe-icons/   # ONPE UI - Iconos
        ├── IconClose.tsx
        ├── IconCheck.tsx
        ├── IconChrome.tsx
        ├── IconFirefox.tsx
        └── IconWindows.tsx
```

## 🧩 Componentes Disponibles

### Button

Botón versátil con múltiples colores y tamaños.

#### Ejemplo Básico
```tsx
import { Button } from '@onpe/ui/components';

function App() {
  return (
    <div className="space-y-4 p-4">
      <h2 className="text-2xl font-bold">Botones ONPE</h2>
      
      {/* Colores disponibles */}
      <div className="space-y-2">
        <h3 className="text-lg font-semibold">Colores:</h3>
        <div className="flex flex-wrap gap-2">
          <Button color="primary" title="Primario" />
          <Button color="blue" title="Azul" />
          <Button color="skyblue" title="Sky Blue" />
          <Button color="green" title="Verde" />
          <Button color="yellow" title="Amarillo" />
          <Button color="red" title="Rojo" />
        </div>
      </div>

      {/* Tamaños */}
      <div className="space-y-2">
        <h3 className="text-lg font-semibold">Tamaños:</h3>
        <div className="flex items-center gap-2">
          <Button color="primary" title="Pequeño" size="small" />
          <Button color="primary" title="Mediano" size="normal" />
          <Button color="primary" title="Grande" size="large" />
        </div>
      </div>

      {/* Estados */}
      <div className="space-y-2">
        <h3 className="text-lg font-semibold">Estados:</h3>
        <div className="flex gap-2">
          <Button color="primary" title="Normal" />
          <Button color="primary" title="Deshabilitado" disabled />
        </div>
      </div>
    </div>
  );
}
```

#### Ejemplo con Funcionalidad
```tsx
import { Button } from '@onpe/ui/components';
import { useState } from 'react';

function VotingApp() {
  const [voted, setVoted] = useState(false);
  const [loading, setLoading] = useState(false);

  const handleVote = async () => {
    setLoading(true);
    // Simular llamada a API
    await new Promise(resolve => setTimeout(resolve, 2000));
    setVoted(true);
    setLoading(false);
  };

  return (
    <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
      <h2 className="text-xl font-bold mb-4">Sistema de Votación</h2>
      
      {!voted ? (
        <div className="space-y-4">
          <p className="text-gray-600">¿Desea votar por esta opción?</p>
          <Button 
            color="primary" 
            title={loading ? "Procesando..." : "Votar Ahora"}
            onClick={handleVote}
            disabled={loading}
            size="large"
          />
        </div>
      ) : (
        <div className="text-center">
          <p className="text-green-600 font-semibold mb-4">¡Voto registrado exitosamente!</p>
          <Button 
            color="green" 
            title="Ver Resultados"
            onClick={() => setVoted(false)}
          />
        </div>
      )}
    </div>
  );
}
```

### Props del Button

| Prop | Tipo | Default | Descripción |
|------|------|---------|-------------|
| `color` | `'primary' \| 'blue' \| 'skyblue' \| 'skyblue-light' \| 'yellow' \| 'light-skyblue' \| 'gray' \| 'gray-light' \| 'gray-extra-light' \| 'red' \| 'dark-gray' \| 'green' \| 'yellow-light'` | `'primary'` | Color del botón |
| `title` | `string` | - | Texto del botón (requerido) |
| `size` | `'small' \| 'normal' \| 'large'` | `'normal'` | Tamaño del botón |
| `disabled` | `boolean` | `false` | Estado deshabilitado |
| `className` | `string` | - | Clases CSS adicionales |

### Modal

Componente modal para mostrar contenido en overlay.

#### Ejemplo Básico
```tsx
import { Modal } from '@onpe/ui/components';
import { useState } from 'react';

function App() {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <div className="p-4">
      <button 
        onClick={() => setIsOpen(true)}
        className="bg-blue-500 text-white px-4 py-2 rounded"
      >
        Abrir Modal
      </button>
      
      <Modal 
        isOpen={isOpen} 
        onClose={() => setIsOpen(false)}
        closeButton={true}
        overlayColor="blue"
      >
        <div className="p-6">
          <h2 className="text-xl font-bold mb-4">Título del Modal</h2>
          <p className="mb-4">Este es el contenido del modal.</p>
          <button 
            onClick={() => setIsOpen(false)}
            className="bg-gray-500 text-white px-4 py-2 rounded"
          >
            Cerrar
          </button>
        </div>
      </Modal>
    </div>
  );
}
```

#### Ejemplo Avanzado - Modal de Confirmación
```tsx
import { Modal } from '@onpe/ui/components';
import { useState } from 'react';

function DeleteConfirmation() {
  const [showModal, setShowModal] = useState(false);
  const [itemToDelete, setItemToDelete] = useState('');

  const handleDelete = (itemName) => {
    setItemToDelete(itemName);
    setShowModal(true);
  };

  const confirmDelete = () => {
    // Lógica para eliminar el elemento
    console.log(`Eliminando: ${itemToDelete}`);
    setShowModal(false);
    setItemToDelete('');
  };

  return (
    <div className="p-4">
      <div className="space-y-2">
        <button 
          onClick={() => handleDelete('Usuario 1')}
          className="bg-red-500 text-white px-4 py-2 rounded mr-2"
        >
          Eliminar Usuario 1
        </button>
        <button 
          onClick={() => handleDelete('Documento 2')}
          className="bg-red-500 text-white px-4 py-2 rounded mr-2"
        >
          Eliminar Documento 2
        </button>
      </div>

      <Modal 
        isOpen={showModal} 
        onClose={() => setShowModal(false)}
        closeButton={true}
        overlayColor="red"
        closeDisabled={false}
      >
        <div className="p-6 text-center">
          <div className="mb-4">
            <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 mb-4">
              <svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
              </svg>
            </div>
            <h3 className="text-lg font-medium text-gray-900 mb-2">
              Confirmar Eliminación
            </h3>
            <p className="text-sm text-gray-500">
              ¿Está seguro de que desea eliminar <strong>{itemToDelete}</strong>? 
              Esta acción no se puede deshacer.
            </p>
          </div>
          
          <div className="flex justify-center space-x-3">
            <button
              onClick={() => setShowModal(false)}
              className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md text-sm font-medium"
            >
              Cancelar
            </button>
            <button
              onClick={confirmDelete}
              className="bg-red-600 text-white px-4 py-2 rounded-md text-sm font-medium"
            >
              Eliminar
            </button>
          </div>
        </div>
      </Modal>
    </div>
  );
}
```

#### Ejemplo - Modal de Formulario
```tsx
import { Modal } from '@onpe/ui/components';
import { useState } from 'react';

function UserForm() {
  const [isOpen, setIsOpen] = useState(false);
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    phone: ''
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Datos del formulario:', formData);
    setIsOpen(false);
    setFormData({ name: '', email: '', phone: '' });
  };

  return (
    <div className="p-4">
      <button 
        onClick={() => setIsOpen(true)}
        className="bg-green-500 text-white px-4 py-2 rounded"
      >
        Agregar Usuario
      </button>

      <Modal 
        isOpen={isOpen} 
        onClose={() => setIsOpen(false)}
        closeButton={true}
        overlayColor="skyblue"
      >
        <div className="p-6">
          <h2 className="text-xl font-bold mb-4">Nuevo Usuario</h2>
          
          <form onSubmit={handleSubmit} className="space-y-4">
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Nombre Completo
              </label>
              <input
                type="text"
                value={formData.name}
                onChange={(e) => setFormData({...formData, name: e.target.value})}
                className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                required
              />
            </div>
            
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Correo Electrónico
              </label>
              <input
                type="email"
                value={formData.email}
                onChange={(e) => setFormData({...formData, email: e.target.value})}
                className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                required
              />
            </div>
            
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Teléfono
              </label>
              <input
                type="tel"
                value={formData.phone}
                onChange={(e) => setFormData({...formData, phone: e.target.value})}
                className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
              />
            </div>
            
            <div className="flex justify-end space-x-3 pt-4">
              <button
                type="button"
                onClick={() => setIsOpen(false)}
                className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md"
              >
                Cancelar
              </button>
              <button
                type="submit"
                className="bg-blue-600 text-white px-4 py-2 rounded-md"
              >
                Guardar Usuario
              </button>
            </div>
          </form>
        </div>
      </Modal>
    </div>
  );
}
```

### Overlay

Componente overlay para superponer contenido.

```tsx
import { Overlay } from '@onpe/ui/components';

function App() {
  return (
    <Overlay>
      <div>Contenido superpuesto</div>
    </Overlay>
  );
}
```

### Portal

Componente portal para renderizar fuera del DOM padre.

```tsx
import { Portal } from '@onpe/ui/components';

function App() {
  return (
    <Portal>
      <div>Contenido renderizado en portal</div>
    </Portal>
  );
}
```

### Show

Componente condicional para mostrar/ocultar contenido.

```tsx
import { Show } from '@onpe/ui/components';

function App() {
  const [visible, setVisible] = useState(true);
  
  return (
    <Show when={visible}>
      <div>Contenido visible</div>
    </Show>
  );
}
```

### ModalConfirm

Modal de confirmación para acciones importantes con colores dinámicos.

```tsx
import { ModalConfirm } from '@onpe/ui/components';

function App() {
  const [showConfirm, setShowConfirm] = useState(false);
  
  return (
    <div className="space-y-4">
      {/* Modal Azul (Por Defecto) */}
      <ModalConfirm
        isOpen={showConfirm}
        onClose={() => setShowConfirm(false)}
        onConfirm={() => {
          // Acción a confirmar
          setShowConfirm(false);
        }}
        title="Confirmar acción"
        message="¿Estás seguro de realizar esta acción?"
        color="blue" // Por defecto
      />

      {/* Modal Rojo para Advertencias */}
      <ModalConfirm
        isOpen={showConfirm}
        onClose={() => setShowConfirm(false)}
        onConfirm={() => {
          // Acción peligrosa
          setShowConfirm(false);
        }}
        title="Advertencia"
        message="Esta acción es irreversible y no se puede deshacer."
        color="red" // Color rojo para advertencias
        icon="warning"
      />
    </div>
  );
}
```

#### Props del ModalConfirm

| Prop | Tipo | Default | Descripción |
|------|------|---------|-------------|
| `isOpen` | `boolean` | - | Estado de apertura del modal (requerido) |
| `onClose` | `function` | - | Función para cerrar el modal (requerido) |
| `onConfirm` | `function` | - | Función para confirmar la acción (requerido) |
| `title` | `string` | - | Título del modal (requerido) |
| `message` | `string` | - | Mensaje del modal (requerido) |
| `color` | `'blue' \| 'red'` | `'blue'` | Color del icono y título |
| `icon` | `'warning' \| 'success'` | `'warning'` | Tipo de icono a mostrar |
| `confirmText` | `string` | `'Confirmar'` | Texto del botón de confirmación |
| `cancelText` | `string` | `'Cancelar'` | Texto del botón de cancelación |

### ModalLoading

Modal de carga para mostrar estados de procesamiento.

```tsx
import { ModalLoading } from '@onpe/ui/components';

function App() {
  const [loading, setLoading] = useState(false);
  
  return (
    <ModalLoading
      isOpen={loading}
      message="Procesando información..."
    />
  );
}
```

### ModalBrowserIncompatible

Modal mejorado para mostrar cuando el navegador no es compatible con el sistema de votación.

```tsx
import { ModalBrowserIncompatible } from '@onpe/ui/components';

function App() {
  const [showBrowserModal, setShowBrowserModal] = useState(false);
  
  return (
    <ModalBrowserIncompatible
      isOpen={showBrowserModal}
      onClose={() => setShowBrowserModal(false)}
    />
  );
}
```

**Características del modal mejorado:**
- ✅ Mensaje claro sobre incompatibilidad
- ✅ Lista visual de navegadores compatibles con nombres
- ✅ Información sobre seguridad y versiones
- ✅ Diseño responsive y accesible
- ✅ Colores institucionales ONPE

### ModalSystemIncompatible

Modal mejorado para mostrar cuando el sistema operativo no es compatible con ONPEID.

```tsx
import { ModalSystemIncompatible } from '@onpe/ui/components';

function App() {
  const [showSystemModal, setShowSystemModal] = useState(false);
  
  return (
    <ModalSystemIncompatible
      isOpen={showSystemModal}
      onClose={() => setShowSystemModal(false)}
    />
  );
}
```

**Características del modal mejorado:**
- ✅ Información específica sobre ONPEID
- ✅ Lista de sistemas operativos compatibles con versiones mínimas
- ✅ Alternativa de acceso web
- ✅ Información de seguridad sobre fuentes oficiales
- ✅ Diseño intuitivo y profesional

## 🎯 Hooks Disponibles

### useDebounce

Hook para retrasar la ejecución de funciones.

```tsx
import { useDebounce } from '@onpe/ui/hooks';

function SearchComponent() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);
  
  useEffect(() => {
    // Buscar solo después de 500ms sin cambios
    searchAPI(debouncedQuery);
  }, [debouncedQuery]);
  
  return (
    <input 
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Buscar..."
    />
  );
}
```

### useLocalStorage

Hook para manejar localStorage de forma reactiva.

```tsx
import { useLocalStorage } from '@onpe/ui/hooks';

function SettingsComponent() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  
  return (
    <select value={theme} onChange={(e) => setTheme(e.target.value)}>
      <option value="light">Claro</option>
      <option value="dark">Oscuro</option>
    </select>
  );
}
```

## 🎨 Iconos Disponibles

La librería incluye una colección completa de iconos organizados por categorías:

### Iconos de Acciones
- Iconos para acciones comunes (editar, eliminar, guardar, etc.)

### Iconos de Navegadores
- Iconos de navegadores web (Chrome, Firefox, Safari, Edge, etc.)

### Iconos de Sistemas Operativos
- Iconos de sistemas operativos (Windows, macOS, Linux, etc.)

### Iconos ONPE
- Iconos específicos de la institución ONPE

### Logos
- Logotipos oficiales y marcas

```tsx
import { 
  IconChrome, 
  IconFirefox, 
  IconSafari,
  IconWindows,
  IconMacOS 
} from '@onpe/ui/icons';

function App() {
  return (
    <div>
      <IconChrome className="w-6 h-6" />
      <IconFirefox className="w-6 h-6" />
      <IconSafari className="w-6 h-6" />
    </div>
  );
}
```

## 🛠️ Utilidades

### formatDate

Función para formatear fechas según estándares peruanos.

```tsx
import { formatDate } from '@onpe/ui/utils';

const fecha = new Date('2024-04-14');
const fechaFormateada = formatDate(fecha, 'dd/mm/yyyy');
console.log(fechaFormateada); // "14/04/2024"
```

### validateEmail

Función para validar direcciones de correo electrónico.

```tsx
import { validateEmail } from '@onpe/ui/utils';

const email = 'usuario@onpe.gob.pe';
const esValido = validateEmail(email);
console.log(esValido); // true
```

## 📱 Breakpoints Responsive

La librería incluye breakpoints personalizados para ONPE:

- `sm`: 640px
- `md`: 768px  
- `lg`: 1024px
- `2lg`: 1200px
- `xl`: 1280px
- `2xl`: 1536px
- `3xl`: 1650px

```css
/* Ejemplo de uso */
@media (min-width: 1200px) {
  .container {
    max-width: 1200px;
  }
}
```

## 🎨 Clases de Utilidad

### Colores de Texto
```css
.text-onpe-ui-blue          /* Azul principal */
.text-onpe-ui-skyblue       /* Sky blue */
.text-onpe-ui-yellow        /* Amarillo */
.text-onpe-ui-green         /* Verde */
.text-onpe-ui-red           /* Rojo */
.text-onpe-ui-gray          /* Gris */
.text-onpe-ui-dark-gray     /* Gris oscuro */
```

### Colores de Fondo
```css
.bg-onpe-ui-blue            /* Fondo azul */
.bg-onpe-ui-skyblue         /* Fondo sky blue */
.bg-onpe-ui-yellow          /* Fondo amarillo */
.bg-onpe-ui-green           /* Fondo verde */
.bg-onpe-ui-red             /* Fondo rojo */
.bg-onpe-ui-gray            /* Fondo gris */
.bg-onpe-ui-gray-light      /* Fondo gris claro */
.bg-onpe-ui-gray-extra-light /* Fondo gris muy claro */
```

## 🛡️ Compatibilidad con CSP (Content Security Policy)

**La librería es completamente compatible con Content Security Policy estricto.**

### ✅ Características CSP

- **Archivos CSS externos**: No usa estilos inline que violen CSP
- **Sin `'unsafe-inline'`**: Compatible con `style-src 'self'`
- **Variables CSS en `:root`**: Disponibles globalmente sin violar políticas
- **Archivos estáticos**: Todos los recursos se sirven como archivos externos

### 🔧 Configuración CSP Recomendada

```html
<!-- En tu HTML head -->
<meta http-equiv="Content-Security-Policy" 
      content="style-src 'self' https://fonts.googleapis.com; 
               script-src 'self'; 
               img-src 'self' data:;">
```

### 📋 Instrucciones para Proyectos con CSP

**1. Instalar la librería:**
```bash
npm install @onpe/ui@1.2.40
```

**2. Importar CSS (CRÍTICO):**
```tsx
// ✅ CORRECTO - Esto carga el CSS externo
import '@onpe/ui/dist/index.css';
import { ModalConfirm, Button } from '@onpe/ui/components';
```

**3. Verificar que las variables CSS estén disponibles:**
```tsx
// Las variables CSS se definen automáticamente en :root
function MiComponente() {
  return (
    <div style={{ color: 'var(--onpe-ui-blue)' }}>
      Este texto usa el color azul ONPE
    </div>
  );
}
```

### ⚠️ Problemas Comunes con CSP

**Error: "Refused to apply inline style"**
```bash
# ❌ PROBLEMA: Estilos inline bloqueados por CSP
# ✅ SOLUCIÓN: Usar archivos CSS externos (ya implementado)
```

**Error: "Variable CSS not defined"**
```tsx
// ❌ PROBLEMA: No importar el CSS
import { Button } from '@onpe/ui/components';

// ✅ SOLUCIÓN: Importar CSS primero
import '@onpe/ui/dist/index.css';
import { Button } from '@onpe/ui/components';
```

## 📋 Versiones

- **v1.2.40** - Versión actual con CSP compatible
- Compatible con React 16.8+
- TailwindCSS v4
- TypeScript 5.3+
- CSP (Content Security Policy) compatible

## 🔧 Desarrollo

### Requisitos
- Node.js 18+
- npm 9+

### Instalación para desarrollo
```bash
git clone https://github.com/ricardosv46/onpe-ui.git
cd onpe-ui
npm install
```

### Scripts disponibles
```bash
npm run build          # Construir para producción
npm run dev            # Desarrollo con watch mode
npm run storybook      # Ver componentes en Storybook
npm run lint           # Verificar código
npm run lint:fix       # Corregir problemas de linting
npm run type-check     # Verificar tipos TypeScript
```

## 📄 Licencia

MIT © ONPE - Oficina Nacional de Procesos Electorales

## 🤝 Contribuir

1. Fork el proyecto
2. Crea una rama para tu feature (`git checkout -b feature/nueva-funcionalidad`)
3. Commit tus cambios (`git commit -m 'Agregar nueva funcionalidad'`)
4. Push a la rama (`git push origin feature/nueva-funcionalidad`)
5. Abre un Pull Request

## 🚀 Ejemplos Completos de Aplicaciones

### Aplicación de Votación Electrónica
```tsx
import { Button, Modal, Show } from '@onpe/ui/components';
import { useState } from 'react';

function VotingApp() {
  const [currentStep, setCurrentStep] = useState(1);
  const [selectedCandidate, setSelectedCandidate] = useState('');
  const [showConfirmModal, setShowConfirmModal] = useState(false);
  const [voted, setVoted] = useState(false);

  const candidates = [
    { id: '1', name: 'Juan Pérez', party: 'Partido Democrático' },
    { id: '2', name: 'María García', party: 'Partido Progresista' },
    { id: '3', name: 'Carlos López', party: 'Partido Independiente' }
  ];

  const handleVote = () => {
    setVoted(true);
    setShowConfirmModal(false);
    setCurrentStep(4);
  };

  return (
    <div className="min-h-screen bg-gray-50 py-8">
      <div className="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-6">
        <div className="text-center mb-8">
          <h1 className="text-3xl font-bold text-gray-900 mb-2">
            Sistema de Votación ONPE
          </h1>
          <p className="text-gray-600">
            Seleccione su candidato preferido
          </p>
        </div>

        <Show when={currentStep === 1}>
          <div className="space-y-4">
            <h2 className="text-xl font-semibold mb-4">Candidatos Disponibles:</h2>
            {candidates.map((candidate) => (
              <div 
                key={candidate.id}
                className={`p-4 border-2 rounded-lg cursor-pointer transition-colors ${
                  selectedCandidate === candidate.id 
                    ? 'border-blue-500 bg-blue-50' 
                    : 'border-gray-200 hover:border-gray-300'
                }`}
                onClick={() => setSelectedCandidate(candidate.id)}
              >
                <div className="flex items-center justify-between">
                  <div>
                    <h3 className="font-semibold text-lg">{candidate.name}</h3>
                    <p className="text-gray-600">{candidate.party}</p>
                  </div>
                  {selectedCandidate === candidate.id && (
                    <div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
                      <svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
                        <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
                      </svg>
                    </div>
                  )}
                </div>
              </div>
            ))}
            
            <div className="pt-4">
              <Button 
                color="primary" 
                title="Continuar" 
                size="large"
                disabled={!selectedCandidate}
                onClick={() => setCurrentStep(2)}
              />
            </div>
          </div>
        </Show>

        <Show when={currentStep === 2}>
          <div className="text-center">
            <h2 className="text-xl font-semibold mb-4">Confirmar Voto</h2>
            <div className="bg-gray-50 p-4 rounded-lg mb-6">
              <p className="text-gray-600 mb-2">Ha seleccionado:</p>
              <p className="font-semibold text-lg">
                {candidates.find(c => c.id === selectedCandidate)?.name}
              </p>
              <p className="text-gray-600">
                {candidates.find(c => c.id === selectedCandidate)?.party}
              </p>
            </div>
            
            <div className="flex justify-center space-x-4">
              <Button 
                color="gray" 
                title="Volver" 
                onClick={() => setCurrentStep(1)}
              />
              <Button 
                color="primary" 
                title="Confirmar Voto" 
                onClick={() => setShowConfirmModal(true)}
              />
            </div>
          </div>
        </Show>

        <Show when={currentStep === 4}>
          <div className="text-center">
            <div className="mb-6">
              <div className="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-4">
                <svg className="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
                </svg>
              </div>
              <h2 className="text-2xl font-bold text-green-600 mb-2">
                ¡Voto Registrado Exitosamente!
              </h2>
              <p className="text-gray-600">
                Su voto ha sido procesado correctamente. Gracias por participar en la democracia.
              </p>
            </div>
            
            <Button 
              color="green" 
              title="Ver Resultados" 
              size="large"
              onClick={() => window.location.reload()}
            />
          </div>
        </Show>

        {/* Modal de Confirmación */}
        <Modal 
          isOpen={showConfirmModal} 
          onClose={() => setShowConfirmModal(false)}
          closeButton={true}
          overlayColor="blue"
        >
          <div className="p-6 text-center">
            <div className="mb-4">
              <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 mb-4">
                <svg className="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                </svg>
              </div>
              <h3 className="text-lg font-medium text-gray-900 mb-2">
                Confirmar Voto
              </h3>
              <p className="text-sm text-gray-500">
                ¿Está seguro de que desea votar por{' '}
                <strong>{candidates.find(c => c.id === selectedCandidate)?.name}</strong>?
                Esta acción no se puede deshacer.
              </p>
            </div>
            
            <div className="flex justify-center space-x-3">
              <Button
                color="gray"
                title="Cancelar"
                onClick={() => setShowConfirmModal(false)}
              />
              <Button
                color="primary"
                title="Confirmar Voto"
                onClick={handleVote}
              />
            </div>
          </div>
        </Modal>
      </div>
    </div>
  );
}

export default VotingApp;
```

### Dashboard de Administración
```tsx
import { Button, Modal, Show } from '@onpe/ui/components';
import { useState } from 'react';

function AdminDashboard() {
  const [activeTab, setActiveTab] = useState('users');
  const [showUserModal, setShowUserModal] = useState(false);
  const [users, setUsers] = useState([
    { id: 1, name: 'Juan Pérez', email: 'juan@onpe.gob.pe', role: 'Admin' },
    { id: 2, name: 'María García', email: 'maria@onpe.gob.pe', role: 'Usuario' },
    { id: 3, name: 'Carlos López', email: 'carlos@onpe.gob.pe', role: 'Usuario' }
  ]);

  const [newUser, setNewUser] = useState({
    name: '',
    email: '',
    role: 'Usuario'
  });

  const addUser = () => {
    const user = {
      id: users.length + 1,
      ...newUser
    };
    setUsers([...users, user]);
    setNewUser({ name: '', email: '', role: 'Usuario' });
    setShowUserModal(false);
  };

  return (
    <div className="min-h-screen bg-gray-100">
      {/* Header */}
      <header className="bg-white shadow-sm border-b">
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
          <div className="flex justify-between items-center py-4">
            <div>
              <h1 className="text-2xl font-bold text-gray-900">Dashboard ONPE</h1>
              <p className="text-gray-600">Panel de administración del sistema electoral</p>
            </div>
            <div className="flex items-center space-x-4">
              <span className="text-sm text-gray-500">Administrador</span>
              <Button color="primary" title="Cerrar Sesión" size="small" />
            </div>
          </div>
        </div>
      </header>

      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
        {/* Navigation Tabs */}
        <div className="mb-8">
          <nav className="flex space-x-8">
            <button
              onClick={() => setActiveTab('users')}
              className={`py-2 px-1 border-b-2 font-medium text-sm ${
                activeTab === 'users'
                  ? 'border-blue-500 text-blue-600'
                  : 'border-transparent text-gray-500 hover:text-gray-700'
              }`}
            >
              Usuarios
            </button>
            <button
              onClick={() => setActiveTab('elections')}
              className={`py-2 px-1 border-b-2 font-medium text-sm ${
                activeTab === 'elections'
                  ? 'border-blue-500 text-blue-600'
                  : 'border-transparent text-gray-500 hover:text-gray-700'
              }`}
            >
              Elecciones
            </button>
            <button
              onClick={() => setActiveTab('reports')}
              className={`py-2 px-1 border-b-2 font-medium text-sm ${
                activeTab === 'reports'
                  ? 'border-blue-500 text-blue-600'
                  : 'border-transparent text-gray-500 hover:text-gray-700'
              }`}
            >
              Reportes
            </button>
          </nav>
        </div>

        {/* Users Tab */}
        <Show when={activeTab === 'users'}>
          <div className="bg-white rounded-lg shadow">
            <div className="px-6 py-4 border-b border-gray-200">
              <div className="flex justify-between items-center">
                <h2 className="text-lg font-medium text-gray-900">Gestión de Usuarios</h2>
                <Button 
                  color="green" 
                  title="Agregar Usuario" 
                  onClick={() => setShowUserModal(true)}
                />
              </div>
            </div>
            
            <div className="overflow-x-auto">
              <table className="min-w-full divide-y divide-gray-200">
                <thead className="bg-gray-50">
                  <tr>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      Nombre
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      Email
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      Rol
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      Acciones
                    </th>
                  </tr>
                </thead>
                <tbody className="bg-white divide-y divide-gray-200">
                  {users.map((user) => (
                    <tr key={user.id}>
                      <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
                        {user.name}
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                        {user.email}
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap">
                        <span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
                          user.role === 'Admin' 
                            ? 'bg-red-100 text-red-800' 
                            : 'bg-green-100 text-green-800'
                        }`}>
                          {user.role}
                        </span>
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
                        <div className="flex space-x-2">
                          <Button color="skyblue" title="Editar" size="small" />
                          <Button color="red" title="Eliminar" size="small" />
                        </div>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        </Show>

        {/* Elections Tab */}
        <Show when={activeTab === 'elections'}>
          <div className="bg-white rounded-lg shadow p-6">
            <h2 className="text-lg font-medium text-gray-900 mb-4">Gestión de Elecciones</h2>
            <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
              <div className="bg-blue-50 p-4 rounded-lg">
                <h3 className="font-semibold text-blue-900">Elecciones Generales</h3>
                <p className="text-blue-700 text-sm mt-1">14 de abril, 2024</p>
                <Button color="blue" title="Ver Detalles" size="small" />
              </div>
              <div className="bg-green-50 p-4 rounded-lg">
                <h3 className="font-semibold text-green-900">Elecciones Regionales</h3>
                <p className="text-green-700 text-sm mt-1">28 de octubre, 2024</p>
                <Button color="green" title="Ver Detalles" size="small" />
              </div>
              <div className="bg-yellow-50 p-4 rounded-lg">
                <h3 className="font-semibold text-yellow-900">Elecciones Municipales</h3>
                <p className="text-yellow-700 text-sm mt-1">15 de diciembre, 2024</p>
                <Button color="yellow" title="Ver Detalles" size="small" />
              </div>
            </div>
          </div>
        </Show>

        {/* Reports Tab */}
        <Show when={activeTab === 'reports'}>
          <div className="bg-white rounded-lg shadow p-6">
            <h2 className="text-lg font-medium text-gray-900 mb-4">Reportes del Sistema</h2>
            <div className="space-y-4">
              <div className="flex justify-between items-center p-4 bg-gray-50 rounded-lg">
                <div>
                  <h3 className="font-medium">Reporte de Participación Electoral</h3>
                  <p className="text-sm text-gray-600">Generado el 15 de abril, 2024</p>
                </div>
                <Button color="primary" title="Descargar PDF" size="small" />
              </div>
              <div className="flex justify-between items-center p-4 bg-gray-50 rounded-lg">
                <div>
                  <h3 className="font-medium">Estadísticas de Usuarios</h3>
                  <p className="text-sm text-gray-600">Generado el 14 de abril, 2024</p>
                </div>
                <Button color="primary" title="Descargar PDF" size="small" />
              </div>
            </div>
          </div>
        </Show>

        {/* Add User Modal */}
        <Modal 
          isOpen={showUserModal} 
          onClose={() => setShowUserModal(false)}
          closeButton={true}
          overlayColor="skyblue"
        >
          <div className="p-6">
            <h2 className="text-xl font-bold mb-4">Agregar Nuevo Usuario</h2>
            
            <div className="space-y-4">
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Nombre Completo
                </label>
                <input
                  type="text"
                  value={newUser.name}
                  onChange={(e) => setNewUser({...newUser, name: e.target.value})}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                  placeholder="Ingrese el nombre completo"
                />
              </div>
              
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Correo Electrónico
                </label>
                <input
                  type="email"
                  value={newUser.email}
                  onChange={(e) => setNewUser({...newUser, email: e.target.value})}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                  placeholder="usuario@onpe.gob.pe"
                />
              </div>
              
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Rol
                </label>
                <select
                  value={newUser.role}
                  onChange={(e) => setNewUser({...newUser, role: e.target.value})}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                >
                  <option value="Usuario">Usuario</option>
                  <option value="Admin">Administrador</option>
                </select>
              </div>
              
              <div className="flex justify-end space-x-3 pt-4">
                <Button
                  color="gray"
                  title="Cancelar"
                  onClick={() => setShowUserModal(false)}
                />
                <Button
                  color="green"
                  title="Agregar Usuario"
                  onClick={addUser}
                />
              </div>
            </div>
          </div>
        </Modal>
      </div>
    </div>
  );
}

export default AdminDashboard;
```

## 🐛 Solución de Problemas

### Problemas con la CLI

**Error: "Componente no encontrado"**
```bash
# Verificar componentes disponibles
npx @onpe/ui add --help

# Componentes válidos:
# button, modal, overlay, portal, show
```

**Error: "No se pudo descargar el componente"**
```bash
# Verificar conexión a internet
ping github.com

# Verificar que el repositorio esté disponible
curl https://raw.githubusercontent.com/ricardosv46/onpe-ui/main/src/components/Button/Button.tsx
```

**Los estilos no se aplican**
```bash
# Verificar que Tailwind esté instalado
npm list tailwindcss

# Verificar configuración
cat tailwind.config.js
```

**Portal no funciona**
```bash
# Verificar que tengas el elemento portal en HTML
grep -r "id=\"portal\"" public/
```

### Problemas con la Librería Completa

**Error: "Module not found"**
```bash
# Verificar instalación
npm list @onpe/ui

# Reinstalar si es necesario
npm uninstall @onpe/ui
npm install @onpe/ui
```

**Estilos no se cargan**
```tsx
/* Verificar que tengas la importación correcta */
import './onpe-ui.css';
import { Button } from './components/onpe-ui/Button';
```

**Solución: Verificar rutas de importación**
```tsx
// ✅ CORRECTO: Importar archivo CSS personalizado
import './onpe-ui.css';
import { Button } from './components/onpe-ui/Button';
import { IconClose } from './components/onpe-icons/IconClose';

// ❌ INCORRECTO: No importar el archivo CSS
import { Button } from './components/onpe-ui/Button';
```

**Solución: Verificar configuración de bundler**
```javascript
// webpack.config.js
module.exports = {
  resolve: {
    alias: {
      '@onpe/ui': path.resolve(__dirname, 'node_modules/@onpe/ui')
    }
  }
};

// vite.config.js
export default {
  resolve: {
    alias: {
      '@onpe/ui': path.resolve(__dirname, 'node_modules/@onpe/ui')
    }
  }
};
```

**Solución: Verificar orden de importación**
```tsx
// ✅ CORRECTO - Importar estilos ANTES de los componentes
import '@onpe/ui/styles';
import { Button } from '@onpe/ui/components';

// ❌ INCORRECTO - Importar estilos DESPUÉS de los componentes
import { Button } from '@onpe/ui/components';
import '@onpe/ui/styles';
```

### Problemas con Estilos

**Los componentes se ven sin estilos**
```bash
# Verificar que la librería esté instalada
npm list @onpe/ui

# Verificar que los estilos estén en node_modules
ls node_modules/@onpe/ui/dist/
```

**Solución: Verificar importación de estilos**
```tsx
// En tu archivo principal (index.tsx o App.tsx)
import React from 'react';
import ReactDOM from 'react-dom/client';
import '@onpe/ui/styles'; // ← IMPORTANTE: Importar estilos primero
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
```

**Solución: Verificar configuración de CSS**
```css
/* En tu archivo CSS principal */
@import "@onpe/ui/styles";

/* O si usas CSS modules */
@import "~@onpe/ui/styles";
```

**Los colores personalizados no funcionan**
```tsx
// Verificar que estés usando las clases correctas
<div className="bg-onpe-ui-blue text-white p-4">
  Contenido con colores ONPE
</div>

// Verificar que los estilos estén importados
import '@onpe/ui/styles';
```

**Solución: Verificar configuración de Tailwind (para componentes individuales)**
```javascript
// tailwind.config.js
module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {
      colors: {
        'onpe-ui-blue': '#003770',
        'onpe-ui-skyblue': '#0073cf',
        // ... resto de colores
      }
    },
  },
}
```

### Problemas con Storybook

**Error: "Failed to fetch dynamically imported module"**
- Verificar que el archivo `preview.ts` importe correctamente `../src/styles.css`
- Asegurar que Tailwind CSS esté configurado correctamente
- Verificar que PostCSS esté configurado

**Solución: Configuración completa de Storybook**
```typescript
// .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
  stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"],
  framework: {
    name: "@storybook/react-vite",
    options: {},
  },
  viteFinal: async (config) => {
    config.css = {
      ...config.css,
      postcss: {
        plugins: [require("tailwindcss"), require("autoprefixer")],
      },
    };
    return config;
  },
};

export default config;
```

```typescript
// .storybook/preview.ts
import type { Preview } from "@storybook/react";
import "../src/styles.css"; // ← Importar estilos

const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: "^on[A-Z].*" },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
};

export default preview;
```

## 🚀 Guía Rápida de Solución

### ¿Los componentes no se ven con estilos?

**Paso 1: Verificar instalación**
```bash
npm list @onpe/ui
```

**Paso 2: Configurar TailwindCSS**
```bash
# Instalar TailwindCSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

**Paso 3: Crear archivo CSS personalizado**
```css
/* Crear archivo onpe-ui.css */
:root {
  --blue: #003770;
  --skyblue: #0073cf;
  /* ... resto de variables */
}

@theme {
  --color-onpe-ui-blue: var(--blue);
  /* ... resto de colores */
}
```

**Paso 4: Importar archivo CSS**
```tsx
// En tu archivo principal (index.tsx)
import './onpe-ui.css';
import { Button } from './components/onpe-ui/Button';
import { IconClose } from './components/onpe-icons/IconClose';
```

**Paso 5: Verificar orden de importación**
```tsx
// ✅ CORRECTO
import './onpe-ui.css';
import { Button } from './components/onpe-ui/Button';
import { IconClose } from './components/onpe-icons/IconClose';

// ❌ INCORRECTO
import { Button } from './components/onpe-ui/Button';
// Falta importar el archivo CSS
```

### ¿Los colores no funcionan?

**Solución: Usar clases correctas**
```tsx
// ✅ CORRECTO
<div className="bg-onpe-ui-blue text-white p-4">
  Contenido
</div>

// ❌ INCORRECTO
<div className="bg-blue-500 text-white p-4">
  Contenido
</div>
```

### ¿Storybook no funciona?

**Solución: Configuración completa**
```typescript
// .storybook/preview.ts
import "../src/styles.css";

// .storybook/main.ts
viteFinal: async (config) => {
  config.css = {
    ...config.css,
    postcss: {
      plugins: [require("tailwindcss"), require("autoprefixer")],
    },
  };
  return config;
},
```

### ¿CLI no instala componentes?

**Solución: Verificar comandos**
```bash
# ✅ CORRECTO
npx @onpe/ui add button
npx @onpe/ui add modal

# ❌ INCORRECTO
npx @onpe/ui add Button
npx @onpe/ui add Modal
```

### ¿Portal no funciona?

**Solución: Agregar elemento HTML**
```html
<!-- En public/index.html -->
<div id="root"></div>
<div id="portal"></div>
```

## 📞 Soporte

- 📧 Email: desarrollo@onpe.gob.pe
- 🐛 Issues: [GitHub Issues](https://github.com/ricardosv46/onpe-ui/issues)
- 📖 Documentación: [Storybook](https://onpe-ui-components.netlify.app)
- 🔗 Repositorio: [GitHub](https://github.com/ricardosv46/onpe-ui)
- 📦 NPM: [@onpe/ui](https://www.npmjs.com/package/@onpe/ui)

---

**Desarrollado con ❤️ para la democracia peruana** 🇵🇪