Resources
11Install
npx skillscat add shirslock/siaj Install via the SkillsCat registry.
SKILL.md
SKILL.md — SIAJ Frontend
Sistema Integral de Asuntos Jurídicos — SOFSA / Trenes Argentinos
Stack: React 18 + TypeScript 5 + Vite 5 + Tailwind v4 + Zustand + Headless UI + Heroicons
Responde: ¿cómo hago X en este proyecto?
Para reglas de negocio y arquitectura → leer CLAUDE.md
Setup rápido
npm install
npm run dev # http://localhost:5173
npx tsc --noEmit # verificar tipos
npm run build # build producción
./deploy.sh # deploy a GH Pages (buildea con /siaj/ y commitea en raíz)Recetas frecuentes
Agregar un ícono nuevo
- Buscar el equivalente en https://heroicons.com (usar outline)
- Agregar import en
src/components/ui/Icon.tsx:import { NuevoIcon } from '@heroicons/react/24/outline' - Agregar en ICON_MAP:
nombre_material_symbol: NuevoIcon, - Usar en cualquier componente:
<Icon name="nombre_material_symbol" size={20} />
Agregar una página nueva
- Crear
src/pages/NombrePagina/NombrePagina.page.tsx - Agregar ruta en
src/App.tsx:<Route path="/ruta" element={<NombrePaginaPage />} /> - Agregar título en
AppLayout.tsx(mapa pathname → título) - Si aparece en sidebar → agregar en
Sidebar.tsxcon rol correcto - Documentar en
src/pages/CLAUDE.md
Agregar un campo al formulario de Alta
- Abrir
src/data/formularios.ts - Encontrar el subtipo (ej: DEMANDA_CIVIL)
- Agregar al array
mesaoabogado:{ id: 'mesa_nuevo', label: 'Label', type: 'text', full: true } - FormularioDinamico.tsx lo renderiza automáticamente
Agregar un catálogo
- Abrir
src/data/catalogos.ts - Agregar el array:
export const NUEVO_CATALOGO: CatalogoItem[] = [ { id: 'CAT_001', label: 'Opción 1' }, ] - Referenciar en el campo con
options: NUEVO_CATALOGO.map(...)
Agregar una acción al store
// 1. Agregar firma en la interfaz
nuevaAccion: (param: string) => void
// 2. Implementar — siempre inmutable con spread
nuevaAccion: (param) => set(s => ({
expedientes: s.expedientes.map(e =>
e.id === param ? { ...e, campo: valor } : e
)
}))Abrir un modal
const [open, setOpen] = useState(false)
<Button onClick={() => setOpen(true)}>Abrir</Button>
<Modal open={open} onClose={() => setOpen(false)}
titulo="Título" size="md"
footer={
<>
<Button variant="secondary" onClick={() => setOpen(false)}>
Cancelar
</Button>
<Button variant="primary" onClick={handleGuardar}>
Guardar
</Button>
</>
}>
{/* contenido */}
</Modal>Mostrar un toast
import { toast } from 'react-toastify'
toast.success('Guardado correctamente.')
toast.error('Error al procesar.')
toast.warn('Cambios pendientes.')
toast.info('Procesando...')Filtros embebidos en tabla
<thead>
<tr className="border-b border-[rgba(0,0,0,0.08)] bg-[#f9f9f9]">
<th className="px-3 py-2.5 text-[10px] font-black uppercase
tracking-widest text-[#4a6a84] text-left">
Columna
</th>
</tr>
<tr className="border-b-2 border-[rgba(0,0,0,0.10)] bg-[#f5f5f5]">
<th className="px-2 py-1.5">
<input className="w-full px-2 py-1.5 text-xs border
border-[rgba(0,0,0,0.15)] rounded-md bg-white
text-[#1b3a57] placeholder-[#a0b0bc]
focus:outline-none focus:border-[#1b3a57]"
placeholder="..." />
</th>
</tr>
</thead>Navegar entre páginas
import { useNavigate } from 'react-router-dom'
import { RUTAS } from '../utils/routing'
const navigate = useNavigate()
navigate(RUTAS.MESA)
navigate(RUTAS.EXPEDIENTE('C-0023/2026'))
navigate(-1)Obtener usuario activo
const { usuarioActivo } = useUIStore()
if (!usuarioActivo) return null
usuarioActivo.rolSistema // 'COORDINADOR' | 'ABOGADO' | etc.
usuarioActivo.rolBD // 'abogado_coordinador' | 'gerente' | etc.
import { puedeReasignar, getNombreCompleto } from '../data/usuarios'
puedeReasignar(usuarioActivo) // → true solo si abogado_coordinador
getNombreCompleto(usuarioActivo) // → 'CASANO, Felix'Checklist antes de entregar
- npx tsc --noEmit sin errores
- npm run build sin errores
- Sin corchetes [nombre] — todos los íconos en Icon.tsx
- Sin tokens v3 (bg-surface, text-on-surface, etc.)
- Sin texto en inglés visible al usuario
- Toasts con toast.* no showToast
- Reglas de negocio de CLAUDE.md respetadas