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:
| Campo | Obligatorio | Descripción |
|---|---|---|
NombreSistema | Sí | Identificador único, normalmente el nombre del ensamblado sin extensión. |
NombreDescriptivo | Sí | Nombre visible en el backend. |
Version | Sí | Versión del módulo (p. ej. 2.1.0). |
Descripcion | No | Descripción para el listado de módulos. |
Autor | No | Autor del módulo. |
UrlProyecto | No | Enlace a la página del proyecto o autor. |
Etiquetas | No | Etiquetas separadas por comas. |
VersionMinimaApp | No | Versión mínima compatible de Crono. |
ClaveRaizRecurso | No | Prefijo de las claves de recursos de idioma (ver Localización de módulos). |
NombreEnsamblado | No | Nombre del ensamblado si difiere de {NombreSistema}.dll. |
Orden | No | Orden de visualización dentro del grupo. |
Grupo | No | Categoría conceptual: Contabilidad, Admin, Api, CMS, Datos, SEO, Medios, DocumentoIdentidad, Movil… |
ReferenciasPrivadas | No | Paquetes NuGet privados que el módulo copia a su salida (el núcleo no los provee). |
DependeDe | No | Nombres 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).
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
- Crea un proyecto de biblioteca de clases dentro de
src/Crono.Modulos/. - Añade el
modulo.jsoncon al menosNombreSistema,NombreDescriptivoyVersion. - Implementa
Modulo.csderivando deModuloBase(eIConfigurablesi tiene backend). - Añade un
Iniciadorpara registrar servicios y, si aplica, aportar entidades al contexto central. - Siembra configuración y recursos de idioma en
InstalarAsync, y límpialos enDesinstalarAsync. - 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.