LineaComando 3.0.9

dotnet add package LineaComando --version 3.0.9
                    
NuGet\Install-Package LineaComando -Version 3.0.9
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LineaComando" Version="3.0.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LineaComando" Version="3.0.9" />
                    
Directory.Packages.props
<PackageReference Include="LineaComando" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add LineaComando --version 3.0.9
                    
#r "nuget: LineaComando, 3.0.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package LineaComando@3.0.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=LineaComando&version=3.0.9
                    
Install as a Cake Addin
#tool nuget:?package=LineaComando&version=3.0.9
                    
Install as a Cake Tool

PER.Comandos.LineaComandos

Sistema de procesamiento de comandos y eventos para aplicaciones .NET. Incluye cola de comandos asincronos y arquitectura event-driven con outbox pattern.

Dependencias

  • .NET 8.0+
  • PostgreSQL (Npgsql)
  • Dapper
  • Microsoft.Extensions.Hosting
  • Microsoft.Extensions.DependencyInjection

Instalacion

Agregar referencias a los proyectos necesarios:

<ItemGroup>
    <ProjectReference Include="..\LineaComando.Builder\LineaComando.Builder.csproj" />
</ItemGroup>

Configuracion con el Builder

ASP.NET Core

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddLineaComando(builder.Configuration.GetConnectionString("Default")!)
    .ConColaComandos(tiempoRefresco: TimeSpan.FromSeconds(2), maxParalelismo: 8)
    .ConEventDriven(
        tiempoRefrescoEventos: TimeSpan.FromSeconds(1),
        tiempoRefrescoTareas: TimeSpan.FromSeconds(5))
    .Build();

var app = builder.Build();

await app.InicializarLineaComandoAsync();

app.Run();

Aplicacion de Consola con Host

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        services
            .AddLineaComando(context.Configuration.GetConnectionString("Default")!)
            .ConColaComandos()
            .ConEventDriven()
            .Build();
    })
    .Build();

await host.InicializarLineaComandoAsync();
await host.RunAsync();

Servicios en Segundo Plano

Al llamar Build(), se registran automaticamente los siguientes BackgroundService:

Servicio Descripcion
ServicioColaComandos Escanea la tabla per_cola_comandos, obtiene comandos pendientes y los ejecuta en paralelo
ServicioProcesadorEventos Escanea la tabla per_eventos_outbox, lee eventos pendientes y encola comandos asociados a sus manejadores
ServicioTareasProgramadas Verifica manejadores con expresiones cron y encola comandos cuando corresponde

Cola de Comandos

Crear un Comando

Un comando hereda de ComandoBase<TRead, TWrite>:

public class PagarOrdenComando : ComandoBase<string, ResultadoComando>
{
    private PagarOrdenParametros _parametros;

    public override void Preparar(
        ICollection<Parametro> parametros,
        IConfiguracion configuracion,
        ILogger logger)
    {
        _parametros = Parametro.New<PagarOrdenParametros>(parametros);
    }

    public override async Task EjecutarAsync(
        IStream<string, ResultadoComando> stream,
        CancellationToken token = default)
    {
        var datos = stream.ObtenerEntrada();

        // Logica del comando...

        stream.Escribir(ResultadoComando.Exito("Orden pagada"));
    }
}

public class PagarOrdenParametros : IParametro
{
    [Nombre("ordenId")]
    public long OrdenId { get; set; }

    [Nombre("monto")]
    public decimal Monto { get; set; }
}

Registrar un Comando

var registroComandos = new RegistroComandos<string, ResultadoComando>(connectionString);

await registroComandos.RegistrarComandoAsync(
    new MetadatosComando
    {
        RutaComando = "orden pagar",
        Descripcion = "Procesa el pago de una orden"
    },
    new Nodo<string, ResultadoComando>(new PagarOrdenComando()),
    token);

Encolar un Comando

Usa IAlmacenColaComandos para encolar comandos:

public class MiServicio
{
    private readonly IAlmacenColaComandos _almacenCola;

    public MiServicio(IAlmacenColaComandos almacenCola)
    {
        _almacenCola = almacenCola;
    }

    public async Task ProcesarPagoAsync(long ordenId, decimal monto)
    {
        var comando = new ComandoEnCola
        {
            RutaComando = "orden pagar",
            Argumentos = $"--ordenId={ordenId} --monto={monto}",
            DatosDeComando = JsonSerializer.Serialize(new { OrdenId = ordenId, Monto = monto })
        };

        await _almacenCola.EncolarAsync(comando);
    }
}

El ServicioColaComandos recogera automaticamente el comando y lo ejecutara.


Event-Driven

Flujo de Eventos

[Tu Codigo] -> IColaEventos.GuardarEventoAsync() -> [Tabla per_eventos_outbox]
                                                              |
                                                              v
[ServicioProcesadorEventos] <- ObtenerEventosPendientesAsync()
            |
            v
    ObtenerManejadoresParaEventoAsync()
            |
            v
    Por cada manejador -> IAlmacenColaComandos.EncolarAsync()
            |
            v
[ServicioColaComandos] ejecuta el comando

Modelo de Datos Event-Driven

El sistema event-driven utiliza tres tablas relacionadas:

+------------------+       +-----------------------+       +----------------------------+
|per_tipos_evento  |       | per_manejadores_evento|       | per_disparadores_manejador |
+------------------+       +-----------------------+       +----------------------------+
| id (PK)          |       | id (PK)               |       | id (PK)                    |
| codigo           |       | codigo                |       | manejador_evento_id(FK)    |---> per_manejadores_evento
| nombre           |       | nombre                |       | tipo_evento_id (FK)        |---> per_tipos_evento
| descripcion      |       | descripcion           |       | modo_disparo               |
| activo           |       | ruta_comando          |       | expresion                  |
| creado_en        |       | argumentos_comando    |       | activo                     |
+------------------+       | activo                |       | prioridad                  |
                           | creado_en             |       | ultima_ejecucion           |
                           +-----------------------+       | creado_en                  |
                                                           +----------------------------+

per_tipos_evento: Catalogo de eventos que pueden ocurrir en tu sistema. Define QUE cosas pueden pasar (ej: "ORDEN_CREADA", "PAGO_RECIBIDO", "USUARIO_REGISTRADO").

per_manejadores_evento: Define QUE COMANDO ejecutar como reaccion. Cada manejador tiene una ruta_comando que apunta a un comando registrado en la cola.

per_disparadores_manejador: Es el PUENTE que conecta todo. Define CUANDO se dispara un manejador:

  • modo_disparo = "Evento": Se dispara cuando ocurre un tipo de evento especifico (requiere tipo_evento_id)
  • modo_disparo = "Programado": Se dispara segun una expresion de intervalo (requiere expresion)

Relacion: Un tipo de evento puede tener multiples manejadores (1:N a traves de disparadores). Un manejador puede reaccionar a multiples tipos de evento (1:N). El disparador es la tabla intermedia que establece estas relaciones.

Registrar un Tipo de Evento

var registroTipos = new RegistroTiposEvento(connectionString);

await registroTipos.RegistrarTipoEventoAsync(new TipoEvento
{
    Codigo = "ORDEN_CREADA",
    Nombre = "Orden Creada",
    Descripcion = "Se emite cuando se crea una nueva orden",
    Activo = true,
    CreadoEn = DateTime.UtcNow
});

Registrar un Manejador de Eventos

Un manejador vincula un tipo de evento con un comando a ejecutar:

var registroManejadores = new RegistroManejadores(connectionString);

var manejadorId = await registroManejadores.RegistrarManejadorAsync(new ManejadorEvento
{
    Codigo = "NOTIFICAR_ORDEN_CREADA",
    Nombre = "Notificar orden creada",
    Descripcion = "Envia notificacion al crear una orden",
    RutaComando = "notificacion enviar",
    ArgumentosComando = "--tipo=email",
    Activo = true,
    CreadoEn = DateTime.UtcNow
});

Configurar el Disparador

Vincula el manejador con el tipo de evento:

var tipoEvento = await registroTipos.ObtenerTipoEventoPorCodigoAsync("ORDEN_CREADA");

await registroManejadores.RegistrarDisparadorAsync(new DisparadorManejador
{
    ManejadorEventoId = manejadorId,
    TipoEventoId = tipoEvento.Id,
    ModoDisparo = "Evento",
    Activo = true,
    Prioridad = 1,
    CreadoEn = DateTime.UtcNow
});

Publicar un Evento

public class OrdenServicio
{
    private readonly IColaEventos _colaEventos;

    public OrdenServicio(IColaEventos colaEventos)
    {
        _colaEventos = colaEventos;
    }

    public async Task CrearOrdenAsync(Orden orden)
    {
        // Guardar orden...

        await _colaEventos.GuardarEventoAsync(new DatosEvento
        {
            TipoEvento = "ORDEN_CREADA",
            AgregadoId = orden.Id,
            Datos = JsonSerializer.Serialize(orden),
            Metadatos = JsonSerializer.Serialize(new { Usuario = "sistema" })
        });
    }
}

El ServicioProcesadorEventos detectara el evento y encolara el comando notificacion enviar.


Tareas Programadas

Las tareas programadas permiten ejecutar comandos en intervalos definidos.

El sistema rastrea la ultima ejecucion de cada tarea mediante el campo ultima_ejecucion en per_disparadores_manejador. Al evaluar si una tarea debe ejecutarse, calcula la proxima ejecucion sumando el intervalo a ultima_ejecucion. Si no existe una ejecucion previa, la tarea se ejecuta inmediatamente.

Registrar una Tarea Programada

var registroManejadores = new RegistroManejadores(connectionString);

var manejadorId = await registroManejadores.RegistrarManejadorAsync(new ManejadorEvento
{
    Codigo = "LIMPIAR_LOGS",
    Nombre = "Limpieza de logs",
    RutaComando = "sistema limpiar-logs",
    Activo = true,
    CreadoEn = DateTime.UtcNow
});

await registroManejadores.RegistrarDisparadorAsync(new DisparadorManejador
{
    ManejadorEventoId = manejadorId,
    ModoDisparo = "Programado",
    Expresion = "00:01:00:00",
    Activo = true,
    Prioridad = 1,
    CreadoEn = DateTime.UtcNow
});

Expresiones de Intervalo

Formato: dd:hh:mm:ss (dias:horas:minutos:segundos)

Expresion Frecuencia
00:00:01:00 Cada minuto
00:00:30:00 Cada 30 minutos
00:01:00:00 Cada hora
00:06:00:00 Cada 6 horas
01:00:00:00 Cada dia
07:00:00:00 Cada semana

Esquema de Base de Datos

Los esquemas se inicializan automaticamente con InicializarLineaComandoAsync().

Tablas de Cola de Comandos

  • per_comandos_registrados: Catalogo de comandos disponibles
  • per_cola_comandos: Comandos encolados pendientes de ejecucion

Tablas de Event-Driven

  • per_tipos_evento: Catalogo de tipos de eventos
  • per_manejadores_evento: Manejadores que responden a eventos
  • per_disparadores_manejador: Configuracion de cuando se dispara cada manejador
  • per_eventos_outbox: Eventos publicados pendientes de procesar

Comportamiento en Conflictos (Upsert)

Las clases de registro utilizan ON CONFLICT ... DO UPDATE para manejar duplicados. Esta seccion documenta que campos actualiza el codigo automaticamente y cuales se preservan, permitiendo modificaciones manuales en base de datos sin que el codigo las sobrescriba.

per_comandos_registrados (RegistroComandos)

Clave de conflicto: ruta_comando

Campo Comportamiento
id Preservado (auto-generado)
ruta_comando Clave, no cambia
descripcion Actualizado por codigo
activo Actualizado a true al re-registrar
creado_en Preservado
actualizado_en Actualizado a NOW()

per_tipos_evento (RegistroTiposEvento)

Clave de conflicto: codigo

Campo Comportamiento
id Preservado (auto-generado)
codigo Clave, no cambia
nombre Actualizado por codigo
descripcion Actualizado por codigo
activo Actualizado por codigo
creado_en Preservado

per_manejadores_evento (RegistroManejadores)

Clave de conflicto: codigo

Campo Comportamiento
id Preservado (auto-generado)
codigo Clave, no cambia
nombre Actualizado por codigo
descripcion Actualizado por codigo
id_comando_registrado Actualizado por codigo
ruta_comando Actualizado por codigo
argumentos_comando Preservado - modificable manualmente
activo Actualizado por codigo
creado_en Preservado

per_disparadores_manejador (RegistroManejadores)

La clave de conflicto depende del modo de disparo:

  • Modo "Evento": (manejador_evento_id, COALESCE(tipo_evento_id, -1)) via indice idx_manejador_tipo
  • Modo "Programado": (manejador_evento_id, COALESCE(expresion, '')) via indice idx_manejador_expr
Campo Comportamiento
id Preservado (auto-generado)
manejador_evento_id Parte de la clave, no cambia
tipo_evento_id Parte de la clave (modo Evento), no cambia
modo_disparo Actualizado por codigo
expresion Parte de la clave (modo Programado) / Preservado en modo Evento
activo Actualizado por codigo
prioridad Actualizado por codigo
ultima_ejecucion Preservado - gestionado por el sistema en runtime
creado_en Preservado

Campos Modificables Manualmente

Los siguientes campos pueden ser modificados directamente en base de datos y el codigo no los sobrescribira al re-registrar:

  • per_manejadores_evento.argumentos_comando: Permite ajustar argumentos sin modificar codigo
  • per_disparadores_manejador.expresion: Permite cambiar expresiones de intervalo sin modificar codigo
  • Todos los campos creado_en: Se preservan como registro historico

Arquitectura

                    +-------------------+
                    |   Tu Aplicacion   |
                    +-------------------+
                           |
          +----------------+----------------+
          |                                 |
          v                                 v
+----------------------+         +-------------------+
| IAlmacenColaComandos |         |    IColaEventos   |
| (Encolar comandos)   |         | (Publicar eventos)|
+----------------------+         +-------------------+
          |                                 |
          v                                 v
+-------------------+            +-------------------+
| per_cola_comandos |            | per_eventos_outbox|
| (Tabla PostgreSQL)|            | (Tabla PostgreSQL)|
+-------------------+            +-------------------+
          ^                                 |
          |                                 v
          |                    +-------------------------+
          |                    |ServicioProcesadorEventos|
          |                    | (BackgroundService)     |
          |                    +-------------------------+
          |                                 |
          |                 Encola comandos segun manejadores
          |                                 |
          +<--------------------------------+
          |
          v
+-------------------+
|ServicioColaComandos|
| (BackgroundService)|
+-------------------+
          |
          v
+-------------------+
| Ejecuta comandos  |
| en paralelo       |
+-------------------+

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on LineaComando:

Package Downloads
LineaComando.Cola

Package Description

PER.Comandos

Librería de comandos con builder, colas y event-driven

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.9 201 1/28/2026