Imported from AlanLeonelMaciel/dashboard-react (
AGENTS.md). Install upstream withnpx skills add AlanLeonelMaciel/dashboard-react. Copyright stays with the author.
OpenCode / AI Agent Instructions: CI3 to React Migration
Rol y Objetivo Principal
Actúas como un Frontend Tech Lead experto en React. Tu objetivo es migrar vistas legacy monolíticas escritas en PHP (CodeIgniter 3) con HTML/Bootstrap hacia una arquitectura moderna, modular y escalable basada en React y Tailwind CSS, operando dentro de un entorno de contenedores Docker.
Tech Stack Obligatorio
- Framework: React + Vite.
- Estilos: Tailwind CSS v3 (No utilizar frameworks de componentes pre-estilados como Material UI o Chakra).
- Manejo de Formularios: React Hook Form.
- Validación de Datos: Zod v4.
- Iconografía: Lucide React.
- Enrutamiento: React Router DOM.
Guía de Estilo y UI (SaaS Moderno)
- Layout:
bg-slate-100fondo general,bg-whitetarjetas/paneles. - Bordes y Sombras:
rounded-xl,border border-slate-200,shadow-sm. - Inputs y Selects:
rounded-xl,border-slate-300,placeholder:text-slate-400,focus:outline-none focus:ring-2 focus:ring-primary-500. - Botones:
rounded-lg,text-sm font-semibold. Variantes:- Primary:
bg-primary-600 text-white shadow-sm hover:bg-primary-700 - Secondary:
bg-white text-slate-700 ring-1 ring-inset ring-slate-300 hover:bg-slate-50 - Danger:
bg-red-600 text-white shadow-sm hover:bg-red-500
- Primary:
- Sidebar: Fondo oscuro
bg-slate-900, anchow-60,border-r border-slate-700. Enlace activo:bg-primary-600 text-white. Enlace inactivo:text-slate-400 hover:bg-slate-800 hover:text-white. - Tipografía: Títulos:
font-semibold text-slate-800. Texto secundario:text-sm text-slate-500. - Modal:
rounded-2xl,border-t-4 border-t-primary-600,shadow-xl. - Badges (píldoras):
rounded-full px-3 py-1 text-xs font-semibold. Activo:bg-green-100 text-green-700. Inactivo:bg-slate-100 text-slate-600.
Arquitectura y Estructura de Archivos
src/
├── components/
│ ├── layout/ ← DashboardLayout, Sidebar, Header
│ └── ui/ ← Button, Badge, Modal (genéricos reutilizables)
├── features/
│ ├── home/ ← HomePage.tsx (dashboard con KPIs, gráficos)
│ ├── users/ ← CRUD de usuarios (UsersPage, useUsers, UserTable, etc.)
│ ├── books/ ← módulo autocontenido con components/, hooks/, types/
│ └── <nuevo_modulo>/ ← Se crea bajo demanda con esta estructura:
│ ├── components/ ← sub-componentes visuales
│ ├── hooks/ ← lógica de negocio, estado y datos auxiliares
│ ├── types/ ← interfaces TypeScript
│ └── <PaginaPrincipal>.tsx
├── App.tsx ← Router principal (se actualiza por cada módulo)
├── main.tsx ← Entry point
└── index.css ← Solo directivas Tailwind
Regla clave: Cada módulo es autocontenido en src/features/<nombre>/. No hay src/pages/, src/layouts/ ni src/schemas/ separados.
Project Directory Structure
Rule: Every cohesive module or shared UI primitive, such as data-table, users, or books, MUST be self-contained, and every new module must follow this pattern by default.
Implementation: Inside each feature/module directory, logic must be strictly separated into components/ for UI, hooks/ for business logic and state, and types/ for TypeScript interfaces. Never leave .ts hook files floating in the root of a module alongside .tsx component files.
For shared primitives under src/components/, apply the same pattern when the primitive grows beyond a single file. For example, src/components/data-table/ must keep table UI in components/ and pagination/state helpers in hooks/.
Convención de Organización Reutilizable
src/features/<modulo>/es para lógica y UI específica del dominio. Todo lo que dependa del negocio del módulo vive ahí.src/features/<modulo>/components/es para piezas visuales del módulo, como tablas específicas, formularios, filtros o acciones de fila que solo tengan sentido dentro de ese feature.src/features/<modulo>/hooks/es para estado y lógica del módulo, como carga de datos, filtros, CRUD y reglas de negocio.src/features/<modulo>/types/es para interfaces y tipos del dominio.src/components/ui/es para primitivos genéricos realmente reutilizables, comoModal,ConfirmDialog,ButtonoBadge.src/components/data-table/es para la base compartida de tablas y sus helpers (components/yhooks/), no para lógica de un módulo puntual.- Si un componente empieza a repetirse en varios módulos o a crecer en complejidad, primero se evalúa moverlo a
src/components/ui/o a un submódulo propio bajosrc/components/. - No crear carpetas de propósito ambiguo como
pages/,shared/ocommon/dentro de features salvo que una regla anterior lo justifique explícitamente.
Flujo de Trabajo para Migrar Vistas Legacy
Cuando se te proporcione código de CodeIgniter 3 para migrar, ejecutá estos pasos estrictamente en orden:
Paso 1 — Análisis de Lógica de Negocio
- Extraer todos los campos del formulario legacy, identificar requeridos, tipos de datos y opciones de selects (catálogos de estado, géneros, autores, etc.).
- Identificar restricciones de roles o permisos hardcodeados (ej. solo Admin puede crear editoriales/géneros).
Paso 2 — Instalar Dependencias (si faltan)
Si el proyecto no tiene las dependencias necesarias, instalarlas automáticamente:
# Host (para que el IDE/linter funcione)
npm install <paquete>
# Contenedor Docker (para que el servidor dev funcione)
docker compose exec frontend npm install <paquete>
Paquetes pre-aprobados: react-hook-form, @hookform/resolvers, zod, recharts, lucide-react, react-router-dom.
Paso 3 — Crear el Schema Zod
- Si el módulo existe y tiene types/schemas, extenderlos. Si no, crearlos inline en el componente o en
src/features/<modulo>/types/. - Zod v4: Usar
z.coerce.number()para campos numéricos de formularios HTML. Los inputs DEBEN usar{ valueAsNumber: true }enregister().const schema = z.object({ cantidad: z.coerce.number({ message: 'Debe ser numérico' }).int().positive('Debe ser positivo'), }) // En el input: <input type="number" {...register('cantidad', { valueAsNumber: true })} /> - Para selects requeridos:
z.string().min(1, 'Seleccioná una opción'). - Para strings opcionales:
z.string().optional(). - Para enums:
z.enum([...] as const, { message: '...' }).
Paso 4 — Desarrollar el Componente React
- Usar el diseño de tarjetas:
rounded-xl border border-slate-200 bg-white shadow-sm. - Grid Layout:
grid-cols-1 md:grid-cols-2 gap-6(o3según la cantidad de columnas). - Implementar
useFormde React Hook Form conectado con@hookform/resolvers/zod. - Clases dinámicas para errores:
const inputClass = (error?: string) => `block w-full rounded-xl border ${error ? 'border-red-500' : 'border-slate-300'} bg-white px-3 py-2.5 text-sm shadow-xs placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-primary-500` - Reemplazar
alert()legacy por mensajestext-xs text-red-500debajo de cada input. - Datos mockeados: si no hay API, simular con arrays estáticos o un custom hook con estado local.
- Prop
userRole?: stringpara permisos de botones (ej.userRole === 'Admin').
Paso 5 — Actualizar Sidebar.tsx
- Si el módulo NO existe en el sidebar → crear entrada con menú desplegable (accordion).
- Si el módulo YA existe en el sidebar → agregar el sub-item correspondiente.
- Patrón de dropdown:
import { useState } from 'react' import { NavLink, useLocation } from 'react-router-dom' import { BookOpen, ChevronDown, ChevronUp, List, Plus } from 'lucide-react' const { pathname } = useLocation() const [isOpen, setIsOpen] = useState(pathname.startsWith('/<modulo>')) <div> <button onClick={() => setIsOpen(!isOpen)} className={`flex w-full items-center justify-between rounded-lg px-3 py-2 text-sm font-medium ${ pathname.startsWith('/<modulo>') ? 'bg-primary-600 text-white shadow-sm' : 'text-slate-400 hover:bg-slate-800 hover:text-white' }`}> <span className="flex items-center gap-3"> <BookOpen className="size-5" /> <nombre_modulo> </span> {isOpen ? <ChevronUp className="size-4" /> : <ChevronDown className="size-4" />} </button> {isOpen && ( <div className="ml-4 mt-1 space-y-1 border-l-2 border-slate-700 pl-3"> <NavLink to="/<modulo>" end className={subLinkClass}> // ← end para match exacto <List className="size-4" /> Listado </NavLink> <NavLink to="/<modulo>/new" className={subLinkClass}> <Plus className="size-4" /> Alta de <nombre> </NavLink> </div> )} </div>
Paso 6 — Actualizar App.tsx (Router)
- Importar el componente nuevo.
- Agregar
<Route>envuelto en<DashboardLayout title="...">. - Si el módulo tiene listado + alta, agregar ambas rutas:
<Route path="/<modulo>" element={<DashboardLayout title="<Módulo>"><Listado /></DashboardLayout>} /> <Route path="/<modulo>/new" element={<DashboardLayout title="Alta de <Módulo>"><Formulario /></DashboardLayout>} /> - Los títulos del Header se pasan como prop
titleaDashboardLayout.
Paso 7 — Build y Restart
Siempre al finalizar los cambios:
# Limpiar dist con permisos incorrectos (si falla por EACCES)
docker run --rm -v "$(pwd)":/app alpine rm -rf /app/dist 2>/dev/null
# Build en el host
npm run build
# Restart del contenedor para que tome los cambios
docker compose restart frontend
Si el build falla, corregir los errores y repetir el paso 7. No continuar hasta que compile limpio.
Comandos Docker Útiles
# Instalar dependencias en host + contenedor
npm install <paquete> && docker compose exec frontend npm install <paquete>
# Build dentro del contenedor (alternativa)
docker compose exec frontend npm run build
# Logs del servidor dev
docker compose logs -f frontend
# Shell interactivo
docker compose exec frontend sh
# Restart completo (reconstruye la imagen)
docker compose up -d --build
Reglas Críticas
- No inventes estilos: Usá exclusivamente la paleta
slateyprimarydetailwind.config.js. - No modifiques configuraciones de Docker: El entorno ya está configurado y corriendo.
- Sincronizar dependencias: Siempre instalar en host Y en contenedor Docker.
- Build obligatorio: No dar el cambio por terminado hasta que
npm run buildcompile sin errores. - Restart automático: Siempre ejecutar
docker compose restart frontenddespués del build. - Zod v4: Usar
valueAsNumber: trueen inputs numéricos +z.coerce.number()en el schema. - Sidebar dropdown: Usar
pathname.startsWith('/<modulo>')para mantener el dropdown abierto en sub-rutas. - Sin dependencias extra: No instalar nada fuera de la lista aprobada sin preguntar primero.
Frontend Architecture & UI Standards
1. Shared Data Table Architecture:
- Rule: Never build standalone, isolated tables for new modules.
- Implementation: All list views (e.g., Users, Books, Transport) MUST utilize the shared data table stack.
- Core Primitives: Always use
DataTable.tsxfor the table base,DataTableToolbar.tsxfor the header/search,DataTablePagination.tsxfor footer controls, and theuseTablePagination.tshook for state management. - Flexibility: Inject module-specific filters dynamically via the toolbar's right slot. If bulk actions are required, activate the base table's optional selection API (master checkbox).
2. UI/UX & Styling Guidelines:
- Component Structure: Maintain the modern "card-style" layout for all tables. Enforce generous whitespace, rounded corners, and soft, diffuse drop shadows for a clean depth effect.
- Color & Typography: Primary action buttons and active menu items should use the project's vibrant accent colors (e.g., modern cyan/teal). Status badges must be pill-shaped using soft background opacities with contrasting text.
- Micro-interactions: All table rows must have a global, subtle hover state (ultra-light gray/blue). Action icons (edit, delete) require tooltips and smooth color transitions on hover.
- Edge Cases: Always implement structural Skeleton loaders during data fetching (avoid basic spinners). Use the standardized Empty State component for searches yielding no results or empty lists.