Cómo transformar las respuestas de API en informes de Excel en C#

Cómo transformar las respuestas de API en informes de Excel en C#

En el mundo actual impulsado por los datos, las empresas dependen en gran medida de las API REST para el intercambio de datos. Sin embargo, muchos interesados prefieren consumir estos datos en formato Excel para análisis e informes. Esta guía demuestra cómo construir una aplicación completa en C# que:

  1. Se conecta a API REST
  2. Recupera datos de respuesta en JSON
  3. Convierte los datos en informes de Excel con formato profesional
  4. Agrega visualización con gráficos y tablas

Valor Empresarial

Esta solución ofrece un valor significativo al:

  • Automatizar los procesos de extracción de datos manuales y creación de informes
  • Asegurar la precisión de los datos al eliminar la copia manual
  • Proporcionar un formato de informe consistente
  • Permitir la generación programada de informes

Guía de Implementación Paso a Paso

Paso 1: Crear una Nueva Aplicación en C#

Comienza creando una nueva aplicación de consola:

dotnet new console -n ApiToExcelReporter
cd ApiToExcelReporter

Paso 2: Instalar Paquetes Requeridos

Agrega los paquetes de NuGet necesarios:

dotnet add package Aspose.Cells
dotnet add package Newtonsoft.Json
dotnet add package RestSharp

Paso 3: Implementar el Cliente de la API REST

Crea un servicio para la comunicación con la API:

using RestSharp;
using Newtonsoft.Json;
using System;
using System.Threading.Tasks;

public class ApiService
{
    private readonly RestClient _client;
    
    public ApiService(string baseUrl)
    {
        _client = new RestClient(baseUrl);
    }
    
    public async Task<T> GetApiDataAsync<T>(string endpoint, string apiKey = null)
    {
        var request = new RestRequest(endpoint, Method.Get);
        
        // Agregar autenticación si es necesario
        if (!string.IsNullOrEmpty(apiKey))
        {
            request.AddHeader("Authorization", $"Bearer {apiKey}");
        }
        
        var response = await _client.ExecuteAsync(request);
        
        if (response.IsSuccessful)
        {
            return JsonConvert.DeserializeObject<T>(response.Content);
        }
        
        throw new Exception($"La llamada a la API falló: {response.ErrorMessage}");
    }
}

Paso 4: Procesar Datos de Respuesta JSON

Agrega una clase para procesar los datos JSON:

using Newtonsoft.Json.Linq;
using System.Collections.Generic;

public class DataProcessor
{
    public List<Dictionary<string, object>> FlattenJsonData(string jsonData)
    {
        var results = new List<Dictionary<string, object>>();
        JToken token = JToken.Parse(jsonData);
        
        // Manejar diferentes estructuras JSON
        if (token is JArray array)
        {
            foreach (var item in array)
            {
                results.Add(FlattenObject(item));
            }
        }
        else if (token is JObject obj)
        {
            // Para datos anidados como {"data": [{...}, {...}]}
            var dataToken = obj["data"] as JArray;
            if (dataToken != null)
            {
                foreach (var item in dataToken)
                {
                    results.Add(FlattenObject(item));
                }
            }
            else
            {
                results.Add(FlattenObject(obj));
            }
        }
        
        return results;
    }
    
    private Dictionary<string, object> FlattenObject(JToken token)
    {
        var result = new Dictionary<string, object>();
        FillDictionary(result, token, "");
        return result;
    }
    
    private void FillDictionary(Dictionary<string, object> dict, JToken token, string prefix)
    {
        switch (token.Type)
        {
            case JTokenType.Object:
                foreach (var prop in token.Children<JProperty>())
                {
                    FillDictionary(dict, prop.Value, 
                        string.IsNullOrEmpty(prefix) ? prop.Name : $"{prefix}.{prop.Name}");
                }
                break;
                
            case JTokenType.Array:
                int index = 0;
                foreach (var item in token.Children())
                {
                    FillDictionary(dict, item, $"{prefix}[{index}]");
                    index++;
                }
                break;
                
            default:
                dict[prefix] = ((JValue)token).Value;
                break;
        }
    }
}

Paso 5: Convertir a Excel con Aspose.Cells

Implementa la conversión a Excel:

using Aspose.Cells;
using Aspose.Cells.Utility;
using System;
using System.Collections.Generic;
using System.IO;

public class ExcelReportGenerator
{
    public void GenerateReport(string jsonData, string outputPath)
    {
        // Crear un nuevo libro de trabajo
        Workbook workbook = new Workbook();
        Worksheet sheet = workbook.Worksheets[0];
        
        // Configurar opciones de diseño JSON
        JsonLayoutOptions options = new JsonLayoutOptions
        {
            ArrayAsTable = true,
            ConvertNumericOrDate = true,
            IgnoreNull = true
        };
        
        // Hacer que los encabezados sean negritas
        options.TitleStyle = new CellsFactory().CreateStyle();
        options.TitleStyle.Font.IsBold = true;
        
        // Importar datos JSON
        JsonUtility.ImportData(jsonData, sheet.Cells, 0, 0, options);
        
        // Ajustar automáticamente las columnas
        sheet.AutoFitColumns();
        
        // Guardar el libro de trabajo
        workbook.Save(outputPath);
    }
    
    public void GenerateReportFromObjects(List<Dictionary<string, object>> data, string outputPath)
    {
        Workbook workbook = new Workbook();
        Worksheet sheet = workbook.Worksheets[0];
        
        // Agregar encabezados si hay datos
        if (data.Count > 0)
        {
            int col = 0;
            foreach (var key in data[0].Keys)
            {
                // Agregar encabezado con estilo
                Cell cell = sheet.Cells[0, col];
                cell.PutValue(key);
                
                Style style = cell.GetStyle();
                style.Font.IsBold = true;
                cell.SetStyle(style);
                
                col++;
            }
            
            // Agregar filas de datos
            for (int row = 0; row < data.Count; row++)
            {
                col = 0;
                foreach (var value in data[row].Values)
                {
                    sheet.Cells[row + 1, col].PutValue(value);
                    col++;
                }
            }
        }
        
        // Formatear como una tabla
        if (data.Count > 0)
        {
            int lastRow = data.Count;
            int lastCol = data[0].Count - 1;
            
            var tableRange = sheet.Cells.CreateRange(0, 0, lastRow + 1, lastCol + 1);
            sheet.ListObjects.Add(tableRange, "DataTable");
        }
        
        // Ajustar automáticamente las columnas
        sheet.AutoFitColumns();
        
        // Guardar el libro de trabajo
        workbook.Save(outputPath);
    }
}

Paso 6: Agregar Formato Profesional

Mejora la salida de Excel con un estilo profesional:

public void ApplyProfessionalFormatting(Workbook workbook)
{
    Worksheet sheet = workbook.Worksheets[0];
    
    // Crear un estilo para el título
    Style titleStyle = workbook.CreateStyle();
    titleStyle.Font.Size = 14;
    titleStyle.Font.IsBold = true;
    titleStyle.HorizontalAlignment = TextAlignmentType.Center;
    
    // Agregar un título al informe
    sheet.Cells.Merge(0, 0, 1, sheet.Cells.MaxColumn + 1);
    Cell titleCell = sheet.Cells[0, 0];
    titleCell.PutValue("Informe de Datos de API - " + DateTime.Now.ToString("yyyy-MM-dd"));
    titleCell.SetStyle(titleStyle);
    
    // Insertar una fila en blanco después del título
    sheet.Cells.InsertRow(1);
    
    // Aplicar colores alternos a las filas de datos
    Style evenRowStyle = workbook.CreateStyle();
    evenRowStyle.ForegroundColor = Color.FromArgb(240, 240, 240);
    evenRowStyle.Pattern = BackgroundType.Solid;
    
    int dataStartRow = 3; // Contando el título y la fila en blanco
    int lastRow = sheet.Cells.MaxRow;
    
    for (int row = dataStartRow; row <= lastRow; row += 2)
    {
        for (int col = 0; col <= sheet.Cells.MaxColumn; col++)
        {
            sheet.Cells[row, col].SetStyle(evenRowStyle);
        }
    }
    
    // Agregar encabezado y pie de página
    sheet.PageSetup.SetHeader(1, "&\"Arial,Bold\"&14Informe de API");
    sheet.PageSetup.SetFooter(1, "Página &P de &N");
    
    // Configurar opciones de impresión
    sheet.PageSetup.Orientation = PageOrientationType.Landscape;
    sheet.PageSetup.FitToPagesWide = 1;
    sheet.PageSetup.TopMargin = 0.5;
    sheet.PageSetup.LeftMargin = 0.5;
    sheet.PageSetup.RightMargin = 0.5;
    sheet.PageSetup.BottomMargin = 0.5;
}

Paso 7: Agregar Visualización de Datos

Mejora el informe con gráficos:

public void AddChartVisualization(Workbook workbook, int dataColumn)
{
    Worksheet sheet = workbook.Worksheets[0];
    Worksheet chartSheet = workbook.Worksheets.Add("Análisis de Gráficos");
    
    // Obtener el rango de datos (saltar la fila de encabezado)
    int lastRow = sheet.Cells.MaxRow;
    int nameColumn = 0; // Suponiendo que la primera columna tiene nombres/categorías
    
    // Agregar un gráfico
    int chartIndex = chartSheet.Charts.Add(ChartType.Column, 2, 2, 20, 10);
    Chart chart = chartSheet.Charts[chartIndex];
    
    // Establecer el rango de datos para el gráfico
    chart.NSeries.Add($"Sheet1!B2:B{lastRow + 1}", true);
    chart.NSeries.CategoryData = $"Sheet1!A2:A{lastRow + 1}";
    
    // Establecer el título del gráfico y otras propiedades
    chart.Title.Text = "Análisis de Datos";
    chart.Legend.Position = LegendPositionType.Bottom;
    
    // Personalización adicional del gráfico
    chart.NSeries[0].Area.ForegroundColor = Color.FromArgb(79, 129, 189);
    chart.PlotArea.Area.ForegroundColor = Color.White;
    
    // Agregar etiquetas de datos
    chart.NSeries[0].DataLabels.IsValueShown = true;
    chart.NSeries[0].DataLabels.Position = DataLabelPositionType.OutsideEnd;
}

Paso 8: Integrar Todo

Crea el programa principal que une todo:

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            // Configurar el endpoint de la API
            string baseUrl = "https://api.example.com";
            string endpoint = "/data/endpoint";
            string apiKey = "tu-clave-api"; // Si es necesario
            
            // Conectar a la API y obtener datos
            Console.WriteLine("Conectando a la API...");
            var apiService = new ApiService(baseUrl);
            var jsonData = await apiService.GetApiDataAsync<string>(endpoint, apiKey);
            
            Console.WriteLine("Datos recuperados con éxito");
            
            // Generar informe en Excel
            Console.WriteLine("Generando informe en Excel...");
            var reportGenerator = new ExcelReportGenerator();
            
            // Opción 1: Conversión directa de JSON a Excel
            string outputPath = "InformeApi_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".xlsx";
            reportGenerator.GenerateReport(jsonData, outputPath);
            
            // Opción 2: Procesar JSON y crear informe mejorado
            // var processor = new DataProcessor();
            // var processedData = processor.FlattenJsonData(jsonData);
            // reportGenerator.GenerateReportFromObjects(processedData, outputPath);
            
            Console.WriteLine($"Informe guardado en {outputPath}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

Manejo de Diferentes Estructuras de Respuesta de API

Las API REST devuelven datos en varios formatos. Aquí se explica cómo manejar estructuras comunes:

1. Array de Objetos

[
  { "id": 1, "name": "Producto A", "price": 29.99 },
  { "id": 2, "name": "Producto B", "price": 49.99 }
]

Para esta estructura, utiliza:

JsonLayoutOptions options = new JsonLayoutOptions();
options.ArrayAsTable = true;
JsonUtility.ImportData(jsonData, sheet.Cells, 0, 0, options);

2. Objeto Anidado con Array de Datos

{
  "status": "success",
  "data": [
    { "id": 1, "name": "Producto A", "price": 29.99 },
    { "id": 2, "name": "Producto B", "price": 49.99 }
  ]
}

Usa la clase DataProcessor para extraer el array “data”, o:

// Analizar con Newtonsoft.Json
JObject jsonObj = JObject.Parse(jsonData);
JArray dataArray = (JArray)jsonObj["data"];

// Convertir a cadena e importar
string dataArrayJson = dataArray.ToString();
JsonUtility.ImportData(dataArrayJson, sheet.Cells, 0, 0, options);

3. Arrays y Objetos Anidados

{
  "categories": [
    {
      "name": "Electrónica",
      "products": [
        { "id": 1, "name": "Portátil", "price": 999.99 },
        { "id": 2, "name": "Teléfono", "price": 699.99 }
      ]
    },
    {
      "name": "Libros",
      "products": [
        { "id": 3, "name": "Novela", "price": 14.99 }
      ]
    }
  ]
}

Para estructuras complejas, crea múltiples hojas de trabajo:

// Analizar el JSON
JObject root = JObject.Parse(jsonData);
JArray categories = (JArray)root["categories"];

// Crear una hoja de trabajo para cada categoría
foreach (var category in categories)
{
    string categoryName = category["name"].ToString();
    Worksheet sheet = workbook.Worksheets.Add(categoryName);
    
    // Obtener e importar el array de productos
    JArray products = (JArray)category["products"];
    JsonUtility.ImportData(products.ToString(), sheet.Cells, 0, 0, options);
}

Llevándolo al Siguiente Nivel: Informes Programados

Para automatizar la generación de informes, agrega capacidades de programación:

// Instalar paquete de Programador de Tareas
// dotnet add package TaskScheduler

using Microsoft.Win32.TaskScheduler;

public void ScheduleDailyReportGeneration(string appPath)
{
    using (TaskService ts = new TaskService())
    {
        // Crear una nueva tarea
        TaskDefinition td = ts.NewTask();
        td.RegistrationInfo.Description = "Generación Diaria de Informe de Datos de API";
        
        // Crear un disparador que se activará diariamente a las 7 a.m.
        td.Triggers.Add(new DailyTrigger { StartBoundary = DateTime.Today.AddHours(7) });
        
        // Crear una acción que ejecutará la aplicación
        td.Actions.Add(new ExecAction(appPath));
        
        // Registrar la tarea en la carpeta raíz
        ts.RootFolder.RegisterTaskDefinition("InformeDiarioApi", td);
    }
}

Características Avanzadas a Considerar

  1. Entrega por correo electrónico - Enviar automáticamente informes por correo electrónico
  2. Integración de múltiples API - Combinar datos de múltiples API
  3. Informes basados en plantillas - Usar plantillas de Excel para una marca consistente
  4. Creación de paneles - Generar paneles interactivos en Excel
  5. Seguimiento y reporte de errores - Registrar problemas e informar sobre éxito/fallo

Siguiendo esta guía, has creado una robusta aplicación en C# que automatiza el proceso de recuperación de datos de API y los convierte en informes de Excel profesionales, ahorrando tiempo, asegurando precisión y entregando valor empresarial.

 Español