--- 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.