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.MyDinaupDinauptrae el cliente (conexión, informes, archivos, anotaciones, WriteOperations). El paquete*.MyDinauptrae 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, usaReadyToGo.MyDinaupen su lugar. -
Añade los
using:using Dinaup; // cliente, WriteOperation, VaultData using DemoUp.MyDinaup.Reports.FuncionalidadD; // informes tipados de tu modeloLas clases de informe (
API…C) y de sección (…ES) viven en el espacio de nombres de tu paqueteMyDinaup, ajusta la categoría (VentasD,ImpuestosD…) a la sección que uses. -
Consigue las tres credenciales.
ConnectAsyncpideendPoint,publicKeyysecretKey. Salen de una clave API que creas en Dinaup, vinculada a un usuario: la clave hereda sus permisos. ElendPointeshttps://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); // TodosEscritura 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 // boolUtilidades: 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 // RedimensionadoEjecutar 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… | Canal | Dónde |
|---|---|---|
| Probar una petición sin escribir código | Zona de pruebas dentro de Dinaup: eliges qué pedir, pulsas y ves la respuesta | Panel de Dinaup |
| Que Dinaup avise a otro programa cuando entra un pedido o cambia una ficha | Webhooks salientes: eliges qué vigilar y a dónde enviarlo, con botón de prueba | Integraciones |
| Reaccionar a miles de cambios casi al instante | Eventos Redis, el mismo aviso por un canal más rápido | Eventos Redis |
| Que otro programa empuje datos a Dinaup sin programar | Webhooks entrantes con Zapier, Make o n8n | Zapier, Make y n8n |
| Usar la API desde cualquier lenguaje | API HTTP, sin el SDK | Integraciones |
| Pedir datos en lenguaje natural | Consulta con IA, Dinaup arma la query | IA |
Para el detalle de cada clase, método y propiedad, ver Desarrollo · Cliente Dinaup.
DinaZen
Catálogo de componentes para montar pantallas de Dinaup: cómo elegir el selector correcto y usar las piezas dinámicas que se pintan solas.
Instalar Dinaup Terminal y publicar tu primer módulo
Instala Terminal, conéctate a un tenant, modela una sección con Flex y empaqueta todo en un módulo que distribuyes por canal.