---
name: react-erp-component
description: Crear componentes React para el ERP usando Ant Design 5.x, React 18, y Emotion para estilos. Incluye patrones para listados, formularios, modales y tablas con manejo de estados loading y error. Usar para el frontend ui_smoking.
---
## Regla de Idioma
IMPORTANTE: Este skill DEBE usar estricto español neutro internacional.
- PROHIBIDO voseo argentino: "vos", "sabés", "tenés", "querés", "andás", "hacés", "venís"
- PROHIBIDO modismos chilenos: "cachai", "weón", "bacán", "fome"
- PROHIBIDO modismos mexicanos: "órale", "güey", "chido"
- PROHIBIDO modismos españoles: "tío", "guay", "mola"
- PROHIBIDO modismos colombianos/venezolanos: "parcero", "chévere", "arrecho"
- PROHIBIDO modismos peruanos/ecuatorianos: "pe", "hablador", "causa"
- PROHIBIDO CUALQUIER regionalismo, localismo o modismo de cualquier país
- Usa "tú" con conjugación estándar universal: "tú sabes", "tú tienes", "tú haces", "tú vienes", "tú quieres"
- Español neutro profesional estricto, sin acento ni expresiones regionales
# React ERP Component Creator
Crea componentes React que siguen las convenciones del ERP.
## Reglas Obligatorias
### 1. Importaciones en orden
```javascript
// 1. React
import React, { useState, useEffect } from 'react';
// 2. Librerías de terceros
import { Table, Button, Modal, Form, Input } from 'antd';
import { useNavigate } from 'react-router-dom';
// 3. Componentes propios
import { PageHeader } from '../../components';
// 4. Hooks/Utils
import { useApi } from '../../hooks';
```
### 2. Exportación por defecto
```javascript
// ✅ CORRECTO
const ProductList = () => { ... };
export default ProductList;
// ❌ INCORRECTO
export const ProductList = () => { ... };
```
### 3. Props con PropTypes (opcional pero recomendado)
```javascript
ProductList.propTypes = {
branchId: PropTypes.string.isRequired,
onSelect: PropTypes.func,
};
ProductList.defaultProps = {
onSelect: null,
};
```
### 4. Estados de carga y error
```javascript
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState([]);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await api.get('/products/');
setData(response.data.results);
} catch (err) {
setError(err.message || 'Error al cargar datos');
message.error('Error al cargar productos');
} finally {
setLoading(false);
}
};
```
## Estructura del Componente
### Componente de Lista (Table)
```javascript
import React, { useState, useEffect } from 'react';
import { Table, Button, Space, Card, message } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
import { PageHeader } from '../../components';
import { useApi } from '../../hooks';
const StyledCard = styled(Card)`
margin: 24px;
`;
const ProductList = () => {
const navigate = useNavigate();
const api = useApi();
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
const columns = [
{
title: 'Nombre',
dataIndex: 'name',
key: 'name',
sorter: true,
},
{
title: 'SKU',
dataIndex: 'sku',
key: 'sku',
},
{
title: 'Precio',
dataIndex: 'sale_price',
key: 'sale_price',
render: (price) => `$${price}`,
},
{
title: 'Acciones',
key: 'actions',
render: (_, record) => (
),
},
];
useEffect(() => {
fetchData();
}, [pagination.current, pagination.pageSize]);
const fetchData = async () => {
setLoading(true);
try {
const response = await api.get('/products/', {
params: {
page: pagination.current,
page_size: pagination.pageSize,
},
});
setData(response.data.results);
setPagination(prev => ({
...prev,
total: response.data.count,
}));
} catch (err) {
message.error('Error al cargar productos');
} finally {
setLoading(false);
}
};
const handleEdit = (record) => {
navigate(`/products/${record.id}/edit`);
};
const handleTableChange = (newPagination, filters, sorter) => {
setPagination(newPagination);
};
return (
<>
}
onClick={() => navigate('/products/create')}
>
Nuevo Producto
,
]}
/>
>
);
};
export default ProductList;
```
### Componente Formulario
```javascript
import React, { useEffect } from 'react';
import { Form, Input, Button, Card, message, InputNumber } from 'antd';
import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled';
import { PageHeader, Loading } from '../../components';
import { useApi } from '../../hooks';
const StyledCard = styled(Card)`
max-width: 800px;
margin: 24px auto;
`;
const ProductForm = () => {
const navigate = useNavigate();
const { id } = useParams();
const api = useApi();
const [form] = Form.useForm();
const [loading, setLoading] = React.useState(false);
const [saving, setSaving] = React.useState(false);
const isEditing = !!id;
useEffect(() => {
if (isEditing) {
fetchProduct();
}
}, [id]);
const fetchProduct = async () => {
setLoading(true);
try {
const response = await api.get(`/products/${id}/`);
form.setFieldsValue(response.data);
} catch (err) {
message.error('Error al cargar producto');
} finally {
setLoading(false);
}
};
const handleSubmit = async (values) => {
setSaving(true);
try {
if (isEditing) {
await api.put(`/products/${id}/`, values);
message.success('Producto actualizado');
} else {
await api.post('/products/', values);
message.success('Producto creado');
}
navigate('/products');
} catch (err) {
message.error('Error al guardar');
} finally {
setSaving(false);
}
};
if (loading) return ;
return (
<>
navigate('/products')}
/>
`$ ${value}`}
parser={(value) => value.replace(/\$\s?/g, '')}
/>
>
);
};
export default ProductForm;
```
## Ejemplos Completos
Ver [references/component-examples.md](references/component-examples.md) para componentes reales del proyecto.