Esta documentación está en fase de desarrollo y puede contener errores.

SDK .NET y API

Conecta, lee, agrega y edita Dinaup desde C# con modelos tipados de tu propia estructura. Recetario de código para copiar.

El SDK .NET te da modelos fuertemente tipados, generados a partir de tu estructura, para operar Dinaup desde C#. Esta página es un recetario: copia el bloque que necesitas.

Antes de empezar

  • Instala los dos paquetes NuGet. El base más el modelo tipado de tu organización:

    dotnet add package Dinaup
    dotnet add package Demoup.MyDinaup

    Dinaup trae el cliente (conexión, informes, archivos, anotaciones, WriteOperations). El paquete *.MyDinaup trae las clases con los nombres reales de tus secciones (APIVentasC, ProductosES, etc.), generadas a partir de tu esquema. Para un modelo neutro válido en varias empresas, usa ReadyToGo.MyDinaup en su lugar.

  • Añade los using:

    using Dinaup;                                   // cliente, WriteOperation, VaultData
    using DemoUp.MyDinaup.Reports.FuncionalidadD;   // informes tipados de tu modelo

    Las clases de informe (API…C) y de sección (…ES) viven en el espacio de nombres de tu paquete MyDinaup, ajusta la categoría (VentasD, ImpuestosD…) a la sección que uses.

  • Consigue las tres credenciales. ConnectAsync pide endPoint, publicKey y secretKey. Salen de una clave API que creas en Dinaup, vinculada a un usuario: la clave hereda sus permisos. El endPoint es https://api.dinaup.com/v2/{tu-codigo}. Crea un usuario específico para la integración y dale acceso solo a las secciones que toca.

    Cómo crear la clave: Claves API.

La clave secreta solo se muestra una vez. Guárdala en un gestor de secretos o en el Vault, nunca en el JavaScript de una web.

Conecta

Conexión directa con las tres credenciales:

var client = await DinaupClientC.ConnectAsync(
    endPoint: "https://api.dinaup.com/v2/tu-codigo",
    publicKey: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    secretKey: "tu-secret-key-aqui"
);

Con Vault, las únicas credenciales que pones en variables de entorno son las del propio Vault, el resto vive cifrado dentro:

var vault = new VaultData(
    Environment.GetEnvironmentVariable("VAULT_URL"),
    Environment.GetEnvironmentVariable("VAULT_PASSWORD")
);
vault.Initialize();

var client = await DinaupClientC.ConnectAsync(
    endPoint: vault.Read("dinaup.endpoint"),
    publicKey: vault.Read("dinaup.publickey"),
    secretKey: vault.Read("dinaup.secretkey")
);

Registrando el cliente como Singleton en inyección de dependencias:

var builder = WebApplication.CreateBuilder(args);

var vault = new VaultData(
    Environment.GetEnvironmentVariable("VAULT_URL"),
    Environment.GetEnvironmentVariable("VAULT_PASSWORD")
);
vault.Initialize();

var client = await DinaupClientC.ConnectAsync(
    endPoint: vault.Read("dinaup.endpoint"),
    publicKey: vault.Read("dinaup.publickey"),
    secretKey: vault.Read("dinaup.secretkey")
);

builder.Services.AddSingleton(client);
var app = builder.Build();

Lee

Ejecutar un informe y recorrer sus filas:

var report = new APIVentasC();
await report.ExecuteQueryAsync(client);

foreach (var row in report.Rows)
{
    Console.WriteLine($"{row.Factura}: {row.Total}");
}

Filtrar y ordenar:

var report = new APIVentasC();

report.AddFilterBetween(VentasES.Fecha, inicioMes, finMes);
report.AddFilter(VentasES.Estado, "=", EstadoE.Completada.INT());
report.AddOrder(VentasES.Total, descending: true);

await report.ExecuteQueryAsync(client);

foreach (var row in report.Rows)
{
    Console.WriteLine($"{row.Numero}: {row.Total}€");
}

Recorrer todas las páginas de resultados:

var report = new APIProductosC();
await report.ExecuteQueryAsync(client);

if (report.Rows.IsNotEmpty())
{
    do
    {
        foreach (var row in report.Rows)
        {
            // Procesar cada registro
        }
    } while (await report.ExecuteQuery_NextPageAsync());
}

Agrega

Alta individual con WriteOperation (Guid.Empty indica alta nueva):

var producto = new WriteOperation(Guid.Empty, new()
{
    { ProductosES.Nombre, "iPhone 16 Pro" },
    { ProductosES.Precio, 1199.00m.STR() }
});

client.RunWriteOperation(ProductosES._SectionID, producto);

Importar en lote por bloques. MaxItemsPerWriteOperation es el tope por llamada (25), parte la lista con .Chunk(...):

var externos = await http.GetFromJsonAsync<List<ProductoDTO>>(url);

var chunks = externos.Chunk(DinaupClientC.MaxItemsPerWriteOperation);

foreach (var chunk in chunks)
{
    var lote = chunk.Select(e => new WriteOperation(Guid.Empty, new()
    {
        { ProductosES.CodigoExterno, e.SKU },
        { ProductosES.Nombre, e.Nombre },
        { ProductosES.Precio, e.Precio.STR() }
    })).ToList();

    client.RunWriteOperation(ProductosES._SectionID, lote, false);
}

Edita

Editar un registro pasando su ID:

var cambios = new WriteOperation(productoId, new()
{
    { ProductosES.Precio, 999.00m.STR() }
});

client.RunWriteOperation(ProductosES._SectionID, cambios);

Actualizar precios en lote:

var cambios = productosIds.Select(id => new WriteOperation(id, new()
{
    { ProductosES.Precio, nuevoPrecio.STR() },
    { ProductosES.FechaActualizacion, DateTime.Now.STR() }
})).ToList();

client.RunWriteOperation(ProductosES._SectionID, cambios, false);

Factura

Cabecera con líneas en una sola llamada:

var factura = new WriteOperation(Guid.Empty, new()
{
    { FacturasES.Numero, "F-2024-0042" },
    { FacturasES.ClienteID, clienteId.STR() },
    { FacturasES.Fecha, DateTime.Now.STR() }
});

var lineas = new List<WriteOperation>
{
    new(Guid.Empty, new() {
        { LineasES.Descripcion, "Consultoría" },
        { LineasES.Cantidad, 10.STR() },
        { LineasES.Precio, 80m.STR() }
    }),
    new(Guid.Empty, new() {
        { LineasES.Descripcion, "Desarrollo" },
        { LineasES.Cantidad, 25.STR() },
        { LineasES.Precio, 60m.STR() }
    })
};

client.RunWriteOperation(FacturasES._SectionID, factura, lineas);

Archivos

Subir desde un array de bytes:

var bytes = System.Text.Encoding.UTF8.GetBytes("contenido del archivo");

var upload = await client.File_UploadBytesAsync(bytes, "documento.txt");

Guid fileId = upload.FileId;

Subir desde una URL externa:

var upload = await client.File_UploadURLAsync(
    "https://ejemplo.com/imagen.png",
    "imagen.png"
);

Guid fileId = upload.FileId;

Obtener una URL firmada para lectura:

var signed = await client.File_SignURLGetAsync(fileId);

var url = signed.url_original;

Anotaciones

Agregar un comentario de solo texto:

var params = new AnotationParameters(sectionId, rowId, AnnotationTypeE.Comments)
    .WithText("¡Hola! Este es mi comentario.");

await client.Annotation_PutAsync(params);

Comentario con archivo adjunto:

var upload = await client.File_UploadBytesAsync(bytes, "documento.pdf");

var params = new AnotationParameters(sectionId, rowId, AnnotationTypeE.Comments)
    .WithText("Adjunto el contrato firmado")
    .WithFile(upload.FileId);

await client.Annotation_PutAsync(params);

Leer las anotaciones de un registro:

var resultado = await client.Annotations_GetAsync(
    sectionId,
    rowId,
    AnnotationTypeE.Comments
);

foreach (var anotacion in resultado.Annotations)
{
    var texto = anotacion.Text;
    var adjuntos = anotacion.AttachedFiles;
}

Documentos dinámicos

Generar el HTML de una factura:

var doc = new DynamicDocuments.PaginasInformesD.FacturaModernaC(ventaId);

var response = await doc.ExecuteAsync(client);

var html = response.Content;

Patrones

Servicio con inyección de dependencias:

public class ProductosService(DinaupClientC client)
{
    public async Task<List<ProductoRow>> GetProductosAsync()
    {
        var report = new APIProductosC();
        await report.ExecuteQueryAsync(client);
        return report.Rows;
    }

    public void AddProducto(string nombre, decimal precio)
    {
        var op = new WriteOperation(Guid.Empty, new() {
            { ProductosES.Nombre, nombre },
            { ProductosES.Precio, precio.STR() }
        });
        client.RunWriteOperation(ProductosES._SectionID, op);
    }
}

Sincronizar desde una API externa, mismo método para crear y para actualizar (ID vacío crea, ID existente actualiza):

public async Task SyncProductosAsync(List<ProductoExterno> externos)
{
    var operaciones = externos.Select(e => new WriteOperation(
        e.DinaupId ?? Guid.Empty,
        new() {
            { ProductosES.CodigoExterno, e.SKU },
            { ProductosES.Nombre, e.Nombre },
            { ProductosES.Precio, e.Precio.STR() }
        })).ToList();

    client.RunWriteOperation(ProductosES._SectionID, operaciones, false);
}

Worker en segundo plano para procesar tareas pendientes:

public class TareasWorker : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        while (ct.IsCancellationRequested == false)
        {
            var pendientes = await GetPendientesAsync();
            foreach (var tarea in pendientes)
                await ProcesarAsync(tarea);

            await Task.Delay(TimeSpan.FromMinutes(5), ct);
        }
    }
}

Avanzado

Búsqueda libre, filtro IN, OR implícito, filtros por relaciones encadenadas y solapamiento de fechas:

// Búsqueda de texto libre
report.QuerySearch = "iPhone Pro Max";

// Filtro IN (múltiples valores)
report.AddFilter(ProductosES.CategoriaId, new[] { categoria1Id, categoria2Id });

// OR implícito: mismo campo, varias llamadas → devuelve ES e IT
report.AddFilter(PaisesES.Codigo, "=", "ES");
report.AddFilter(PaisesES.Codigo, "=", "IT");

// Filtro por relaciones encadenadas
var keyPath = $"{PedidosES._SectionID}.{PedidosES.ClienteId}.{ClientesD._SectionID}.{ClientesES.VIP}";
report.AddFilter(keyPath, "=", 1);

// Solapamiento de rangos de fechas
report.AddFilterDateRangeOverlapFilter(
    EventosES.FechaInicio, EventosES.FechaFin,
    rangeStart: new DateOnly(2024, 1, 1),
    rangeEnd: new DateOnly(2024, 12, 31)
);

// Incluir eliminados
report.AddFilter(ProductosES.Eliminado, "<>", -1);  // Todos

Escritura avanzada: DynamicSelector para resolver una relación por texto, editar por campo alternativo, actualizar un solo campo, limpiar una relación y leer el resultado:

// DynamicSelector: busca la relación por un campo de texto
var op = new WriteOperation(Guid.Empty, new() {
    { ProductosES.Nombre, "Mi producto" },
    { ProductosES.UnidadMedidaID, $"[{UnidadesMedidaES.TextoPrincipal}=Litros]" }
});

// Editar por campo alternativo (ej. SKU)
client.RunWriteOperation(
    ProductosD._SectionIDGUID, op,
    runScripts: false,
    identifierFieldKey: ProductosES.CodigoExterno
);

// Actualizar un solo campo
await client.RunInlineWriteOperationAsync(
    ProductosD._SectionIDGUID, productoId,
    ProductosES.Precio, 1299.00m.STR()
);

// Limpiar una relación con string vacío
valores.Add(ProductosES.ClienteID, "");

// Leer el resultado
op.WriteOperationResult.RowID      // Guid del registro
op.WriteOperationResult.Confirmed  // bool
op.WriteOperationResult.AError     // string (si hay error)

Relaciones, variables de informe y propiedades del informe:

// Cabecera + líneas en una llamada
var factura = await FacturasD.GetRowByIdWithListAsync(client, facturaId);
Console.WriteLine($"Factura: {factura.MainRow.Numero}");
foreach (var linea in factura.ListRows) { }

// GetRowsAsync con labels de relaciones
var productos = await ProductosD.GetRowsAsync(client, params);
foreach (var p in productos)
{
    Console.WriteLine($"{p.TextoPrincipal}: {p.ReferenciaCliente.Label}");
}

// Variables de informe
var report = new VentasPorClienteC();
report.AddVariable("ClienteId", clienteId.ToString());
report.AddVariable("FechaDesde", "2024-01-01");

// Propiedades del informe
report.Rows           // List<RowC>
report.RowsDic        // Dictionary<Guid, RowC>
report.TotalResults   // int
report.ExistNextPage  // bool

Utilidades: verificar conexión, conexión síncrona, conversión de enums, deduplicación por SHA1 y propiedades de archivo:

// Verificar conexión
if (client.IsConnected) { /* OK */ }

// Connect síncrono (alternativa a ConnectAsync)
var client = DinaupClientC.Connect(endpoint, publicKey, secretKey);

// Conversión de enums
{ FacturasES.Estado, EstadoFacturaE.Borrador.INT().STR() }

// Deduplicación con SHA1
string sha1 = Dinaup.extensions.ToSHA1(contenido);

// URL firmada con parámetros
var url = await client.File_SignURLGetAsync(fileId, cachear: false);

// Propiedades de archivo subido
upload.FileData.Id        // Guid
upload.FileData.CRC       // SHA1
upload.FileData.IsImage   // bool
upload.FileData.url_1080  // Redimensionado

Ejecutar como un usuario concreto. Afecta al autor del alta, al histórico, a las anotaciones y a los filtros de sesión:

// Solo userId
using (DinaupContext.WithUser(userId))
{
    client.RunWriteOperation(...);
}

// Con IP
using (DinaupContext.WithUser(userId, "192.168.1.1"))
{
    client.RunWriteOperation(...);
}

// Con IP y UserAgent
using (DinaupContext.WithUser(userId, "192.168.1.1", "Mozilla/5.0..."))
{
    client.RunWriteOperation(...);
}

Otras formas de conectar

Si no programas en .NET o no quieres el SDK, Dinaup expone los mismos datos por otros canales:

Quieres…CanalDónde
Probar una petición sin escribir códigoZona de pruebas dentro de Dinaup: eliges qué pedir, pulsas y ves la respuestaPanel de Dinaup
Que Dinaup avise a otro programa cuando entra un pedido o cambia una fichaWebhooks salientes: eliges qué vigilar y a dónde enviarlo, con botón de pruebaIntegraciones
Reaccionar a miles de cambios casi al instanteEventos Redis, el mismo aviso por un canal más rápidoEventos Redis
Que otro programa empuje datos a Dinaup sin programarWebhooks entrantes con Zapier, Make o n8nZapier, Make y n8n
Usar la API desde cualquier lenguajeAPI HTTP, sin el SDKIntegraciones
Pedir datos en lenguaje naturalConsulta con IA, Dinaup arma la queryIA

Para el detalle de cada clase, método y propiedad, ver Desarrollo · Cliente Dinaup.

On this page