> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/contributor/contributor-es/guias-para-colaboradores/estandares-de-ui-web/migration.md).

# Guía de migración

Esta guía cubre el proceso de migración de componentes desde UI1 (`decentraland-ui`) a UI2 (`decentraland-ui2`).

## Cuándo Migrar

Las migraciones pueden comenzar por tres razones válidas:

### 1. Mejora técnica

Deseas migrar un componente por:

* Mejor soporte de theming
* Mejores tipos de TypeScript
* Consistencia con otros componentes de UI2
* Optimización de rendimiento
* Mejoras de accesibilidad

### 2. Actualización de un Componente

* Un componente de UI1 necesita ser actualizado
* Aún no existe un equivalente en UI2
* **Requisito**: Créalo primero en UI2, luego úsalo

### 3. Necesidades del Proyecto

* El componente será usado en un proyecto nuevo o existente
* Hay tiempo disponible para migrarlo correctamente
* Los recursos del proyecto permiten una migración exhaustiva

{% hint style="warning" %}
**No** migres componentes “solo porque.” Cada migración debe tener una justificación comercial o técnica clara.
{% endhint %}

***

## Proceso de Migración

### Paso 1: Planificación

Antes de iniciar la migración:

1. **Identificar dependencias**
   * ¿Qué otros componentes utiliza?
   * ¿Qué proyectos lo usan actualmente?
   * ¿Hay cambios incompatibles planificados?
2. **Revisar uso actual**
   * ¿Cuántos proyectos usan este componente?
   * ¿Qué props se usan con mayor frecuencia?
   * ¿Hay problemas conocidos?
3. **Definir alcance**
   * ¿Será esta una migración 1:1?
   * ¿Hay mejoras planificadas?
   * ¿Cuál es el cronograma?

### Paso 2: Crear Componente UI2

Sigue la [Componentes personalizados](/contributor/contributor-es/guias-para-colaboradores/estandares-de-ui-web/custom-components.md) guía para componentes candidatos de UI2.

**Requisitos:**

* Usar sintaxis de objeto para styled-components
* Usar solo valores del theme (no valores arbitrarios)
* Agregar historias completas en Storybook
* Escribir pruebas completas
* Documentar todas las props y comportamientos

**Estructura de ejemplo:**

```
ui2/src/components/Button/
├── Button.tsx
├── Button.styles.ts
├── Button.stories.tsx
├── Button.test.tsx
├── types.ts
├── index.ts
└── README.md
```

### Paso 3: Mantener Compatibilidad

El componente UI2 **DEBE** debe exponer las mismas props y comportamientos que la versión UI1.

#### Mismas Props

```tsx
// UI1 Button
interface ButtonProps {
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
  onClick?: () => void;
  disabled?: boolean;
  children: React.ReactNode;
}

// UI2 Button - DEBE soportar las mismas props
interface ButtonProps {
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
  onClick?: () => void;
  disabled?: boolean;
  children: React.ReactNode;
  // Se pueden agregar nuevas props opcionales
  variant?: 'text' | 'outlined' | 'contained';
}
```

#### Cambios Compatibles hacia Atrás

Si necesitas cambiar o agregar props:

1. **Primero**, agrega las nuevas props a UI1 como opcionales
2. **Luego**, migra a los consumidores de UI1 para usar las nuevas props
3. **Finalmente**, crea el componente UI2 con la nueva API

```tsx
// Paso 1: Agregar prop opcional a UI1
interface ButtonProps {
  primary?: boolean;
  // Nueva prop opcional
  variant?: 'primary' | 'secondary';
}

// Paso 2: Actualizar implementación de UI1
export function Button({ primary, variant = primary ? 'primary' : 'secondary' }: ButtonProps) {
  // Usar variant en lugar de primary internamente
}

// Paso 3: Crear UI2 con la nueva API
interface ButtonProps {
  // variant ahora es la prop principal
  variant?: 'primary' | 'secondary';
  // Mantener primary por compatibilidad, marcar como obsoleta
  /** @deprecated Use variant instead */
  primary?: boolean;
}
```

### Paso 4: Deprecar el Componente UI1

Agregar un aviso de deprecación al componente UI1:

````tsx
/**
 * @deprecated Este componente ha sido migrado a UI2.
 * Importar desde 'decentraland-ui2' en su lugar:
 * 
 * ```tsx
 * import { Button } from 'decentraland-ui2';
 * ```
 * 
 * Ver la guía de migración: https://docs.decentraland.org/contributor-guides/web-ui-standards/migration
 */
export function Button(props: ButtonProps) {
  // ... implementación existente
}
````

### Paso 5: Adopción Gradual

No fuerces la migración inmediata. Permite una adopción gradual:

1. **Publicar** componente UI2
2. **Documentar** ruta de migración
3. **Actualizar** proyectos nuevos para usar UI2
4. **Migrar** proyectos existentes de forma oportunista
5. **Planificar** eventual eliminación de UI1 (con aviso)

***

## Ejemplos de Migración

### Ejemplo 1: Componente Simple

Migrando un básico `Card` componente:

#### Versión UI1

```tsx
// decentraland-ui/src/components/Card/Card.tsx
import React from 'react';
import './Card.css';

export interface CardProps {
  className?: string;
  children: React.ReactNode;
}

export function Card({ className, children }: CardProps) {
  return (
    <div className={`dcl-card ${className || ''}`}>
      {children}
    </div>
  );
}
```

#### Versión UI2

```tsx
// decentraland-ui2/src/components/Card/Card.tsx
import { styled } from '@mui/material/styles';

export interface CardProps {
  className?: string;
  children: React.ReactNode;
}

const StyledCard = styled('div')(({ theme }) => ({
  backgroundColor: theme.palette.background.paper,
  borderRadius: theme.shape.borderRadius,
  padding: theme.spacing(2),
  boxShadow: theme.shadows[1],
  
  [theme.breakpoints.down('sm')]: {
    padding: theme.spacing(1),
  },
}));

export function Card({ className, children }: CardProps) {
  return (
    <StyledCard className={className}>
      {children}
    </StyledCard>
  );
}
```

### Ejemplo 2: Componente con Variantes

Migrando un `Button` con variantes:

#### Versión UI1

```tsx
// UI1
import './Button.css';

interface ButtonProps {
  primary?: boolean;
  secondary?: boolean;
  size?: 'small' | 'medium' | 'large';
}

export function Button({ primary, secondary, size = 'medium', ...props }: ButtonProps) {
  const classes = [
    'dcl-button',
    primary && 'primary',
    secondary && 'secondary',
    `size-${size}`,
  ].filter(Boolean).join(' ');
  
  return <button className={classes} {...props} />;
}
```

#### Versión UI2

```tsx
// UI2
import { styled } from '@mui/material/styles';

interface ButtonProps {
  /** @deprecated Use variant="contained" instead */
  primary?: boolean;
  /** @deprecated Use variant="outlined" instead */
  secondary?: boolean;
  variant?: 'text' | 'outlined' | 'contained';
  size?: 'small' | 'medium' | 'large';
}

const StyledButton = styled('button')<ButtonProps>(({ theme, variant = 'contained', size = 'medium' }) => {
  const sizes = {
    small: theme.spacing(0.5, 1),
    medium: theme.spacing(1, 2),
    large: theme.spacing(1.5, 3),
  };
  
  const variants = {
    text: {
      backgroundColor: 'transparent',
      color: theme.palette.primary.main,
    },
    outlined: {
      backgroundColor: 'transparent',
      color: theme.palette.primary.main,
      border: `1px solid ${theme.palette.primary.main}`,
    },
    contained: {
      backgroundColor: theme.palette.primary.main,
      color: theme.palette.primary.contrastText,
    },
  };
  
  return {
    padding: sizes[size],
    borderRadius: theme.shape.borderRadius,
    border: 'none',
    cursor: 'pointer',
    ...variants[variant],
    
    '&:hover': {
      opacity: 0.9,
    },
    
    '&:disabled': {
      opacity: 0.5,
      cursor: 'not-allowed',
    },
  };
});

export function Button({ 
  primary, 
  secondary, 
  variant, 
  ...props 
}: ButtonProps) {
  // Manejar props obsoletas
  const actualVariant = variant || 
    (primary ? 'contained' : secondary ? 'outlined' : 'text');
  
  return <StyledButton variant={actualVariant} {...props} />;
}
```

***

## Lista de Verificación de Migración

Usa esta lista de verificación para cada migración de componente:

### Fase de Planificación

* [ ] Identificar todos los proyectos que usan el componente
* [ ] Documentar props y comportamientos actuales
* [ ] Definir alcance y cronograma de la migración
* [ ] Obtener aprobación de las partes interesadas

### Fase de Implementación

* [ ] Crear componente UI2 siguiendo los estándares
* [ ] Mantener compatibilidad de props
* [ ] Usar sintaxis de objeto para estilos
* [ ] Usar solo valores del theme
* [ ] Implementar todos los estados (idle, hover, focus, disabled, error)
* [ ] Agregar historias completas en Storybook
* [ ] Escribir pruebas unitarias
* [ ] Documentar características de accesibilidad

### Fase de Deprecación

* [ ] Agregar aviso de deprecación al componente UI1
* [ ] Actualizar la documentación de UI1
* [ ] Crear guía de migración para consumidores
* [ ] Publicar componente UI2

### Fase de Adopción

* [ ] Actualizar proyectos nuevos para usar UI2
* [ ] Crear PRs de migración para proyectos existentes
* [ ] Monitorear por problemas
* [ ] Recopilar feedback
* [ ] Planificar cronograma de eliminación de UI1

***

## Patrones Comunes de Migración

### De CSS a Styled Components

```tsx
// UI1: archivo CSS
.dcl-card {
  background: #fff;
  padding: 16px;
  border-radius: 8px;
}

// UI2: componente styled
const Card = styled('div')(({ theme }) => ({
  backgroundColor: theme.palette.background.paper,
  padding: theme.spacing(2),
  borderRadius: theme.shape.borderRadius,
}));
```

### Nombres de Clase a Props

```tsx
// UI1: Variantes basadas en clases
<Button className={primary ? 'primary' : 'secondary'} />

// UI2: Variantes basadas en props
<Button variant={primary ? 'contained' : 'outlined'} />
```

### Valores Fijos al Theme

```tsx
// UI1: Valores fijos
const styles = {
  color: '#333',
  fontSize: '14px',
  padding: '8px 16px',
};

// UI2: Valores del theme
const Component = styled('div')(({ theme }) => ({
  color: theme.palette.text.primary,
  fontSize: theme.typography.body2.fontSize,
  padding: theme.spacing(1, 2),
}));
```

***

## Cambios Incompatibles

A veces los cambios incompatibles son necesarios. Manéjalos con cuidado:

### Cuando los Cambios Incompatibles son Aceptables

* Correcciones de seguridad
* Bugs críticos
* Actualizaciones de versión mayor
* Eliminar características obsoletas (con aviso)

### Cómo Manejar Cambios Incompatibles

1. **Anunciar con anticipación** - Comunicar los cambios con suficiente antelación
2. **Proporcionar ruta de migración** - Documentar cómo actualizar
3. **Incremento de versión** - Seguir versionado semántico
4. **Periodo de deprecación** - Dar tiempo para migrar
5. **Codemods** - Proveer herramientas de migración automatizadas si es posible

### Ejemplo: Eliminar Props Obsoletas

```tsx
// Versión 1.0: Introducir nueva API, deprecar la antigua
interface ButtonProps {
  /** @deprecated Use variant="contained" instead */
  primary?: boolean;
  variant?: 'text' | 'outlined' | 'contained';
}

// Versión 1.5: Avisar sobre la eliminación
interface ButtonProps {
  /** @deprecated Will be removed in 2.0. Use variant instead */
  primary?: boolean;
  variant?: 'text' | 'outlined' | 'contained';
}

// Versión 2.0: Eliminar prop obsoleta
interface ButtonProps {
  variant?: 'text' | 'outlined' | 'contained';
}
```

***

## Pruebas de Migración

Asegúrate de que los componentes migrados funcionen correctamente:

### Pruebas de Regresión Visual

Comparar los componentes UI1 y UI2 visualmente:

```tsx
// Historia de Storybook para comparación
export const ComparisonStory: Story = {
  render: () => (
    <div style={{ display: 'flex', gap: '2rem' }}>
      <div>
        <h3>UI1</h3>
        <UI1Button primary>Click Me</UI1Button>
      </div>
      <div>
        <h3>UI2</h3>
        <UI2Button variant="contained">Click Me</UI2Button>
      </div>
    </div>
  ),
};
```

### Pruebas de Comportamiento

Asegurar que las props funcionen de la misma manera:

```tsx
describe('Button migration', () => {
  it('should handle primary prop (deprecated) the same as variant="contained"', () => {
    const { container: ui1 } = render(<UI1Button primary>Test</UI1Button>);
    const { container: ui2 } = render(<UI2Button primary>Test</UI2Button>);
    
    // Comparar la salida renderizada
    expect(ui1.textContent).toBe(ui2.textContent);
  });
});
```

***

## Actualizaciones de Documentación

Después de la migración, actualiza la documentación:

### Actualizar Docs del Componente UI1

```markdown
# Button (UI1 - Obsoleto)

> ⚠️ **Este componente ha sido migrado a UI2.**
> Consulta la [documentación del Button en UI2](../ui2/button) para la nueva versión.

Este componente está obsoleto y será eliminado en una versión futura.
Por favor migra a UI2.

## Guía de Migración

Consulta [Migration Guide](./migration) para más detalles.
```

### Crear Docs del Componente UI2

```markdown
# Button (UI2)

Componente de botón moderno con soporte completo de theme.

## Migración desde UI1

Si estás migrando desde UI1:

- prop `primary` → `variant="contained"`
- prop `secondary` → `variant="outlined"`
- clases CSS → styled-components

Consulta la [Migration Guide](./migration) completa para más detalles.
```

***

## Siguientes pasos

* Revisar [Componentes personalizados](/contributor/contributor-es/guias-para-colaboradores/estandares-de-ui-web/custom-components.md) para crear componentes UI2
* Ver [Estilado y Theming](/contributor/contributor-es/guias-para-colaboradores/estandares-de-ui-web/styling-and-theming.md) para estándares de estilado
* Comprobar [Resumen del proceso](/contributor/contributor-es/guias-para-colaboradores/estandares-de-ui-web/process-overview.md) para el flujo de trabajo completo


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.decentraland.org/contributor/contributor-es/guias-para-colaboradores/estandares-de-ui-web/migration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
