Skip to main content

Primeros pasos con módulos

Un módulo es una unidad de funcionalidad opcional que se acopla al núcleo de Crono sin tocar su código: facturación electrónica, integración con RENIEC, respaldo externo, rutas de cobranza, etc. Cada módulo es un proyecto de biblioteca de clases con su propio manifiesto, su registro de servicios y sus rutinas de instalación/desinstalación.

Los módulos viven en src/Crono.Modulos/. Por ejemplo:

src/Crono.Modulos/
├── Crono.Facturacion/ (Facturación electrónica SUNAT)
├── Crono.Reniec/ (Consulta de identidad)
├── Crono.RegulatorioSbs/ (Reportes SBS)
├── Crono.RespaldoExterno/
├── Crono.RutasCobranza/
└── modulo.esquema.json (Esquema JSON compartido del manifiesto)

El manifiesto modulo.json

Todo módulo declara un modulo.json en su raíz. Es lo que lee el Administrador de módulos del backend para mostrarlo, versionarlo y ordenarlo. Referencia el esquema compartido para tener validación en el editor:

{
"$schema": "../modulo.esquema.json",
"Grupo": "Contabilidad",
"NombreSistema": "Crono.Facturacion",
"NombreDescriptivo": "Facturación electrónica (SUNAT)",
"Descripcion": "Emisión de comprobantes de pago electrónicos...",
"Version": "1.0.0",
"VersionMinimaApp": "0.1.0",
"Autor": "Crono",
"Orden": 1,
"ClaveRaizRecurso": "Complementos.Contabilidad.Facturacion",
"ReferenciasPrivadas": []
}

Campos del manifiesto:

CampoObligatorioDescripción
NombreSistemaIdentificador único, normalmente el nombre del ensamblado sin extensión.
NombreDescriptivoNombre visible en el backend.
VersionVersión del módulo (p. ej. 2.1.0).
DescripcionNoDescripción para el listado de módulos.
AutorNoAutor del módulo.
UrlProyectoNoEnlace a la página del proyecto o autor.
EtiquetasNoEtiquetas separadas por comas.
VersionMinimaAppNoVersión mínima compatible de Crono.
ClaveRaizRecursoNoPrefijo de las claves de recursos de idioma (ver Localización de módulos).
NombreEnsambladoNoNombre del ensamblado si difiere de {NombreSistema}.dll.
OrdenNoOrden de visualización dentro del grupo.
GrupoNoCategoría conceptual: Contabilidad, Admin, Api, CMS, Datos, SEO, Medios, DocumentoIdentidad, Movil
ReferenciasPrivadasNoPaquetes NuGet privados que el módulo copia a su salida (el núcleo no los provee).
DependeDeNoNombres de sistema de otros módulos requeridos.

La clase de entrada Modulo.cs

Cada módulo tiene una clase interna que implementa IModulo. Lo recomendado es derivar de ModuloBase, que ya resuelve el estado modular y ofrece ayudantes para sembrar configuración y recursos. Si el módulo es configurable desde el backend, implementa además IConfigurable:

internal class Modulo : ModuloBase, IConfigurable
{
public RutaInformacion ObtenerRutaConfiguracion()
=> new("Configurar", "FacturacionConfiguracion", new { area = "Admin" });

public override async Task InstalarAsync(ModuloInstalacionContexto contexto)
{
await IntentarGuardarConfiguracionesAsync<FacturacionConfiguraciones>();
await ImportarIdiomaRecursosAsync();
await base.InstalarAsync(contexto);
}

public override async Task DesinstalarAsync()
{
await EliminarConfiguracionesAsync<FacturacionConfiguraciones>();
await EliminarIdiomaRecursosAsync();
await base.DesinstalarAsync();
}
}

ModuloBase expone ayudantes protegidos habituales: ImportarIdiomaRecursosAsync() / EliminarIdiomaRecursosAsync(), GuardarConfiguracionesAsync<T>() / IntentarGuardarConfiguracionesAsync<T>() / EliminarConfiguracionesAsync<T>(), además de Descriptor (el IModuloDescriptor) y Servicios (un IComunServicios para resolver dependencias).

El ModuloInstalacionContexto que recibe InstalarAsync trae el AplicacionContexto, el ámbito de servicios (Ambito), la cultura de instalación, un Registrador para trazas y la Etapa (AppInstalacion cuando se instala junto con la aplicación, o ModuloInstalacion cuando el usuario lo instala a demanda).

info

IConfigurable.ObtenerRutaConfiguracion() devuelve un RutaInformacion (acción, controlador y valores de ruta). Es lo que pinta el botón Configurar del módulo en el backend y lo enlaza a su página de configuración.

Registrar servicios: el Iniciador

Un módulo aporta sus servicios y su parte del modelo de datos mediante un Iniciador que deriva de IniciadorBase (ver Iniciador). Se descubre automáticamente al arrancar:

internal class Iniciador : IniciadorBase
{
public override void ConfigurarServicios(IServiceCollection servicios, IAplicacionContexto appContexto)
{
// Aporta las entidades del módulo al modelo del contexto central.
servicios.AddTransient<IDbContextoConfiguracionFuente<CronoDbContexto>, CronoDbContextoConfigurador>();
servicios.AddHttpClient<IFacturaServicio, FacturaServicio>();
}

public override void ConfigurarContenedor(ContainerBuilder constructor, IAplicacionContexto appContexto)
{
constructor.RegisterType<TipoDocumentoServicio>()
.As<ITipoDocumentoServicio>()
.InstancePerLifetimeScope();
}
}

Un módulo puede aportar sus entidades al CronoDbContexto central registrando un IDbContextoConfiguracionFuente<CronoDbContexto>; así sus tablas y IEntityTypeConfiguration se integran en el mismo contexto sin crear una base de datos aparte (ver Acceso a datos).

Estructura de carpetas recomendada

Los módulos siguen una convención de carpetas para que el código sea predecible. Del módulo real de facturación:

Crono.Facturacion/
├── modulo.json
├── Modulo.cs
├── Iniciador.cs
├── AdminMenu.cs (elementos de menú del backend)
├── Permisos.cs (permisos del módulo)
├── Controllers/
├── Models/
├── Views/
├── Migraciones/
├── Configuraciones/
├── Consumidores/ (consumidores de eventos)
├── Localizacion/ (recursos.es-pe.xml)
├── Extensiones/
├── Infraestructura/
└── wwwroot/ (recursos estáticos)

Pasos para crear un módulo

  1. Crea un proyecto de biblioteca de clases dentro de src/Crono.Modulos/.
  2. Añade el modulo.json con al menos NombreSistema, NombreDescriptivo y Version.
  3. Implementa Modulo.cs derivando de ModuloBase (e IConfigurable si tiene backend).
  4. Añade un Iniciador para registrar servicios y, si aplica, aportar entidades al contexto central.
  5. Siembra configuración y recursos de idioma en InstalarAsync, y límpialos en DesinstalarAsync.
  6. Organiza el código según la convención de carpetas.

Con esto el módulo aparece en el Administrador de módulos y puede instalarse, configurarse y desinstalarse desde el backend. Los siguientes temas cubren controladores y ViewComponents, filtros, localización y despliegue.