3.4 Construyendo la UI: listado de incidencias con React

Construcción del componente React de listado mediante un prompt para un agente IA, incluyendo el código resultante.

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>
  );
};

 

Consejo práctico: Nota cómo el filtrado se hace en el cliente, sobre los datos ya cargados. Para listas largas (más de 500 registros), considera pedirle al agente de IA que pase los filtros como parámetros al servicio de Dataverse usando la sintaxis OData (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.
Inicia sesión e inscríbete para guardar tu progreso.
En este curso
¿Te ha resultado útil?