Con el proyecto creado y Dataverse conectado, es hora de escribir la primera pantalla real: el listado de incidencias. Aquí vamos a crear un componente React que usa el SDK generado para obtener los datos y mostrarlos con filtros funcionales.
Objetivos de aprendizaje
- Crear un componente React funcional que consume el SDK de Dataverse
- Implementar filtros por estado y prioridad
- Gestionar el estado de carga y los errores correctamente
- Entender cómo se estructura una Code App con múltiples componentes
Prompt para el Agente de Código: Componente de listado
Como vimos en módulos anteriores, en lugar de escribir el código desde cero, usamos un agente de inteligencia artificial (como Copilot o Claude) para generar la base. Este es el prompt (instrucción en lenguaje natural) que usamos para pedirle al agente de código que construya nuestro componente de listado:
/* Crea un componente React TypeScript llamado IncidenciasList que: - Use el CrIncidenciaService para obtener todas las incidencias - Tenga filtros por estado (Abierta, En progreso, Resuelta, Cerrada) y prioridad (Baja, Media, Alta, Crítica) - Muestre un spinner durante la carga - Muestre un mensaje de error si la carga falla - Muestre cada incidencia en una tarjeta con: título, estado (con color), prioridad, solicitante y fecha - Tenga un botón "Nueva incidencia" que llame a un prop onNewIncidencia - Al hacer clic en una tarjeta, llame a un prop onSelectIncidencia(id: string) - Use useState y useEffect de React - Esté completamente tipado con TypeScript */
El código del componente resultante
Tras proporcionar ese prompt, el agente genera un código similar a este:
// src/components/IncidenciasList.tsx
import React, { useState, useEffect } from 'react';
import { CrIncidenciaService, CrIncidencia } from '../generated';
import { CrEstado } from '../generated/enums/CrEstado';
import { CrPrioridad } from '../generated/enums/CrPrioridad';
interface IncidenciasListProps {
onNewIncidencia: () => void;
onSelectIncidencia: (id: string) => void;
}
const estadoLabels: Record<CrEstado, string> = {
[CrEstado.Abierta]: 'Abierta',
[CrEstado.EnProgreso]: 'En progreso',
[CrEstado.Resuelta]: 'Resuelta',
[CrEstado.Cerrada]: 'Cerrada',
};
const estadoColors: Record<CrEstado, string> = {
[CrEstado.Abierta]: '#e74c3c',
[CrEstado.EnProgreso]: '#f39c12',
[CrEstado.Resuelta]: '#27ae60',
[CrEstado.Cerrada]: '#95a5a6',
};
const prioridadLabels: Record<CrPrioridad, string> = {
[CrPrioridad.Baja]: 'Baja',
[CrPrioridad.Media]: 'Media',
[CrPrioridad.Alta]: 'Alta',
[CrPrioridad.Critica]: 'Crítica',
};
export const IncidenciasList: React.FC<IncidenciasListProps> = ({
onNewIncidencia,
onSelectIncidencia,
}) => {
const [incidencias, setIncidencias] = useState<CrIncidencia[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [filtroEstado, setFiltroEstado] = useState<CrEstado | 'todos'>('todos');
const [filtroPrioridad, setFiltroPrioridad] = useState<CrPrioridad | 'todos'>('todos');
useEffect(() => {
cargarIncidencias();
}, []);
const cargarIncidencias = async () => {
try {
setLoading(true);
setError(null);
const datos = await CrIncidenciaService.getAll({
orderBy: 'createdon desc',
});
setIncidencias(datos);
} catch (err) {
setError('No se pudieron cargar las incidencias. Por favor, inténtalo de nuevo.');
console.error(err);
} finally {
setLoading(false);
}
};
const incidenciasFiltradas = incidencias.filter((inc) => {
const pasaEstado = filtroEstado === 'todos' || inc.cr_estado === filtroEstado;
const pasaPrioridad = filtroPrioridad === 'todos' || inc.cr_prioridad === filtroPrioridad;
return pasaEstado && pasaPrioridad;
});
if (loading) return <div className="loading">Cargando incidencias...</div>;
if (error) return <div className="error">{error}</div>;
return (
<div className="incidencias-list">
<div className="toolbar">
<h1>Incidencias</h1>
<button onClick={onNewIncidencia} className="btn-primary">
Nueva incidencia
</button>
</div>
<div className="filtros">
<select value={filtroEstado} onChange={(e) => setFiltroEstado(e.target.value as CrEstado | 'todos')}>
<option value="todos">Todos los estados</option>
{Object.entries(estadoLabels).map(([val, label]) => (
<option key={val} value={val}>{label}</option>
))}
</select>
<select value={filtroPrioridad} onChange={(e) => setFiltroPrioridad(e.target.value as CrPrioridad | 'todos')}>
<option value="todos">Todas las prioridades</option>
{Object.entries(prioridadLabels).map(([val, label]) => (
<option key={val} value={val}>{label}</option>
))}
</select>
</div>
<div className="grid">
{incidenciasFiltradas.map((inc) => (
<div key={inc.cr_incidenciaid} className="card" onClick={() => onSelectIncidencia(inc.cr_incidenciaid!)}>
<h3>{inc.cr_titulo}</h3>
<span className="badge" style={{ backgroundColor: estadoColors[inc.cr_estado!] }}>
{estadoLabels[inc.cr_estado!]}
</span>
<span className="prioridad">{prioridadLabels[inc.cr_prioridad!]}</span>
<p>{inc.cr_solicitante}</p>
</div>
))}
</div>
</div>
);
};
filter: "cr_estado eq 1") para que el filtrado ocurra en el servidor.
Puntos clave
- Los agentes de IA son excelentes generando la base del código a partir de un buen prompt.
- Los componentes React usan el SDK generado exactamente igual que cualquier API TypeScript.
- Los enums de Dataverse se usan directamente en los Record types de TypeScript.
- Siempre gestiona los estados de carga y error en componentes que hacen peticiones async.