3d5419403d
- 25 domain entities mapped from original Datos/ classes - 4 enums (EstadoLead, TipoPago, TipoDocumento, TipoDescuento) - DTOs for Lead, Auth flows - 6 repository interfaces - VentasDbContext with EF Core + Npgsql - LeadRepository (command SPs) and LeadQueryRepository (query SPs) with Dapper - Program.cs with JWT auth, CORS, DI setup - appsettings.json with connection string - .gitignore for bin/obj/node_modules - ROADMAP.md for daily progress tracking
1223 lines
45 KiB
Markdown
1223 lines
45 KiB
Markdown
# Plan de Migración: ModuloVentas
|
|
|
|
> **Repositorio:** https://git.norteamericano.cl/Nurfog/rm-ventas.git
|
|
|
|
> **Prompt original:**
|
|
> necesito migrar este proyecto de .net framework 4.8 a .net core 10 (ya es lts), con conexiones a bases de datos desde mysql a postgresql, ademas que tenga un backend (api).net core 10 c# y un frontend en next.js
|
|
> los datos de conexion a la base de datos son los siguientes:
|
|
> host: 192.168.0.254
|
|
> puerto: 5432
|
|
> usuario: postgres
|
|
> contraseña: apoca11
|
|
> database: ichn
|
|
> necesito que se cree un plan con los siguientes parametros:
|
|
> analizar completamente y minuciosamente el proyecto.
|
|
> los reportes de crystal reports tienen que ser migrados al formato de questpdf
|
|
> la estrategia de datos sera con entity frameworks para sp simples y ejecutandolos en la base de datos los sp complejos
|
|
> respecto a los facturadores, no usamos facturachile ni el soap wsAmazonSige
|
|
> todos los servicios externos los quiero en un proyecto aparte como backend como api, ya que varios proyectos mas lo usan
|
|
> ademas usar contenedores para backend y otro para frontend
|
|
> secretos en variables de entorno
|
|
> crear pruebas integrales e2e
|
|
> graficamente, el frontend tiene que se identico al original.
|
|
> todas las estructuras y datos ya se encuentan en la base de datos (nada se migra)
|
|
|
|
---
|
|
|
|
## 1. ANÁLISIS DEL PROYECTO ACTUAL
|
|
|
|
### 1.1 Estructura General
|
|
|
|
```
|
|
/home/juan/dev/ventas/
|
|
├── ModuloVentas.sln # Solución Visual Studio 2022
|
|
├── ModuloVentas.csproj # Consola bootstrap (obsoleto)
|
|
├── App.config
|
|
│
|
|
├── Datos/ # Capa de Datos - Class Library
|
|
│ ├── Datos.csproj # .NET Framework 4.7.2
|
|
│ ├── packages.config # MySql.Data 9.3, MySqlConnector 2.6
|
|
│ ├── Conexion.cs # Conexión MySQL (hardcodeada)
|
|
│ └── 25 clases de entidad:
|
|
│ Alumno, ArqueoCaja, Comuna, Contrato, Cotizacion,
|
|
│ CotizacionInscripcion, Curso, Descuento, Diagnostico,
|
|
│ Documento, Ejecutivo, Empresa, Horario, Informes,
|
|
│ Jornada, Lead, Programa, Propuesta, Region, Sala,
|
|
│ SamAntiguo, Samold, Sede, Tarifa, TransbankInfo, Usuario
|
|
│
|
|
├── Negocio/ # Capa de Negocio - Class Library
|
|
│ ├── Negocio.csproj # .NET Framework 4.7.2
|
|
│ └── 28 clases:
|
|
│ Nalumno, NArqueoCaja, Ncomuna, Ncontrato, Ncotizacion,
|
|
│ NcotizacionInscripcion, Ncurso, Ndescuento, Ndiagnostico,
|
|
│ Ndocumento, Nejecutivo, Nempresa, Nhorario, Ninforme,
|
|
│ Njornada, Nlead, Nmail, Nprograma, Npropuesta, Nregion,
|
|
│ Nsala, NsamAntiguo, Nsamold, Nsede, Ntarifa, Nusuario,
|
|
│ PagadorTransbank, ValidarRut, IchnEncrypt, FechaApi, Mails
|
|
│
|
|
└── Web/ # Presentación - ASP.NET WebForms
|
|
├── Web.csproj # .NET Framework 4.7.2
|
|
├── Web.config # Config principal
|
|
├── packages.config # 15 paquetes NuGet
|
|
├── SAM.Master / SAM.Master.cs # Master Page (sidebar + layout)
|
|
├── 40 páginas .aspx + code-behind
|
|
├── 17 reportes Crystal Reports (.rpt)
|
|
├── 12 Typed DataSets (.xsd)
|
|
├── 2 servicios WCF (Connected Services)
|
|
├── css/, js/, vendor/, img/
|
|
└── Template/, correo/ (HTML emails)
|
|
```
|
|
|
|
### 1.2 Tecnologías Actuales
|
|
|
|
| Componente | Tecnología | Versión |
|
|
|-----------|-----------|---------|
|
|
| **Framework** | .NET Framework | 4.7.2 |
|
|
| **Frontend** | ASP.NET WebForms + Bootstrap 5 + jQuery | - |
|
|
| **Base de datos** | MySQL | 9.3 (aws ec2) |
|
|
| **ORM** | Ninguno (ADO.NET puro) | - |
|
|
| **Reportes** | Crystal Reports | 13.0.4000 |
|
|
| **Pagos** | Transbank SDK | 6.0.0 |
|
|
| **DTE Chile** | LibreDTE (REST) + WSFacturaChile (SOAP) | - |
|
|
| **ERP/SIGE** | wsAmazonSige (SOAP) | - |
|
|
| **Auth** | FormsAuthentication + cookies | - |
|
|
| **Email** | SmtpClient (Gmail SMTP) | - |
|
|
| **Tests** | Ninguno | - |
|
|
| **Contenedores** | Ninguno | - |
|
|
|
|
### 1.3 Bases de Datos Originales (MySQL)
|
|
|
|
| Base de Datos | Uso |
|
|
|-------------|-----|
|
|
| `sige_sam_V3` | Principal - leads, cotizaciones, contratos, alumnos |
|
|
| `sige_sam` | Legado SAM |
|
|
| `caja_tbk` | Transbank/webpay |
|
|
| `sige_sam_empresa` | Módulo empresas |
|
|
|
|
### 1.4 Patrón de Acceso a Datos (Actual)
|
|
|
|
```csharp
|
|
// Patrón repetido en las 25 clases de Datos/
|
|
private readonly Conexion Conexion = new Conexion();
|
|
private MySqlCommand Comando;
|
|
private MySqlDataAdapter Adaptador;
|
|
private MySqlDataReader Leer;
|
|
|
|
// Ejemplo típico:
|
|
Comando = new MySqlCommand("sige_sam_V3.GrabaLead", Conexion.AbrirConnectionMySql())
|
|
{
|
|
CommandType = CommandType.StoredProcedure
|
|
};
|
|
Comando.Parameters.AddWithValue("@param", valor);
|
|
Adaptador = new MySqlDataAdapter(Comando);
|
|
Adaptador.Fill(tabla);
|
|
Conexion.CerrarConnectionMysql();
|
|
```
|
|
|
|
### 1.5 Conexiones Actuales (hardcodeadas en Conexion.cs)
|
|
|
|
```csharp
|
|
// 4 métodos de conexión, todas hardcodeadas:
|
|
AbrirConnectionMySql() → sige_sam_V3 @ AWS
|
|
AbrirConnectionAmazonSamOld()→ sige_sam @ AWS
|
|
AbrirConnectionTBK() → caja_tbk @ AWS
|
|
AbrirConnectionMySqlEmp() → sige_sam_empresa @ AWS
|
|
```
|
|
|
|
### 1.6 Crystal Reports - Inventario Completo
|
|
|
|
| # | Archivo .rpt | Ubicación | Uso en Code-behind |
|
|
|---|-------------|-----------|-------------------|
|
|
| 1 | Arqueo.rpt | Plantillas/ | Arqueo.aspx.cs |
|
|
| 2 | CrystalReportAnexo.rpt | Plantillas/ + Planillas/ | FacturadorBoleta.aspx.cs, pago.aspx.cs |
|
|
| 3 | CrystalReportContrato.rpt | Plantillas/ + Planillas/ | FacturadorBoleta.aspx.cs, pago.aspx.cs |
|
|
| 4 | CrystalReportContratoBlack.rpt | Planillas/ | FacturadorBoleta.aspx.cs |
|
|
| 5 | CrystalReportCotizacion.rpt | Plantillas/ | pago.aspx.cs, Panel.aspx.cs |
|
|
| 6 | ReportPresupuesto.rpt | Plantillas/ | - |
|
|
| 7 | CAEMP.rpt | PlantillasEmpresas/ | AdminEmpresa.aspx.cs |
|
|
| 8 | CAEMPSNC.rpt | PlantillasEmpresas/ | AdminEmpresa.aspx.cs |
|
|
| 9 | CAEMPV2.rpt | PlantillasEmpresas/ | AdminEmpresa.aspx.cs |
|
|
| 10 | CC_EMP.rpt | PlantillasEmpresas/ | AdminEmpresa.aspx.cs |
|
|
| 11 | CC_EMPCSD.rpt | PlantillasEmpresas/ | AdminEmpresa.aspx.cs |
|
|
| 12 | CC_PRS.rpt | PlantillasEmpresas/ | AdminEmpresa.aspx.cs |
|
|
| 13 | CC_SNC.rpt | PlantillasEmpresas/ | AdminEmpresa.aspx.cs |
|
|
| 14 | CotizacionCursoCerrado.rpt | PlantillasEmpresas/ | AdminEmpresa.aspx.cs |
|
|
| 15 | CotizacionPlanCentral.rpt | PlantillasEmpresas/ | AdminEmpresa.aspx.cs |
|
|
| 16 | PropuestaComercial.rpt | PlantillasEmpresas/ | AdminEmpresa.aspx.cs |
|
|
| 17 | ReportPresupuesto.rpt | Plantillas/ | - |
|
|
|
|
### 1.7 Servicios Externos
|
|
|
|
| Servicio | Tipo | Endpoint | Uso |
|
|
|---------|------|----------|-----|
|
|
| **wsAmazonSige** | SOAP WCF | `https://api.norteamericano.cl/wsSige/Service.asmx` | Sincronización ventas con ERP/SIGE |
|
|
| **WSFacturaChile** | SOAP WCF | `http://ws.facturachile.cl/WSServicios.asmx` | Emisión de boletas electrónicas |
|
|
| **LibreDTE** | REST | `https://libredte.cl` | Emisión DTE (facturación electrónica chilena) |
|
|
| **Transbank Webpay** | SDK | API Transbank | Pagos online con tarjetas |
|
|
| **Google OAuth** | OAuth 2.0 | Google APIs | Login con Google |
|
|
| **SMTP Email** | SMTP | `smtp.gmail.com:587` | Correos transaccionales |
|
|
|
|
---
|
|
|
|
## 2. ARQUITECTURA OBJETIVO
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Frontend (Next.js) │
|
|
│ Misma UI exacta: Bootstrap 5 + FontAwesome + CSS original │
|
|
│ SAM.Master → Layout.tsx (sidebar idéntica + responsive) │
|
|
│ 40 páginas .aspx → 40 rutas en App Router │
|
|
│ DataList/GridView → DataTables (react-data-table-component) │
|
|
│ Reportes → PDF vía API (QuestPDF) │
|
|
│ Auth → JWT + cookies HttpOnly │
|
|
└──────────────────────┬──────────────────────────────────────┘
|
|
│ HTTP (fetch/axios)
|
|
┌──────────────────────▼──────────────────────────────────────┐
|
|
│ Backend API (.NET 10) │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ Ventas.API (Controllers REST) │ │
|
|
│ │ /api/auth, /api/lead, /api/contrato, │ │
|
|
│ │ /api/cotizacion, /api/alumno, /api/arqueo, │ │
|
|
│ │ /api/curso, /api/descuento, /api/documento, │ │
|
|
│ │ /api/ejecutivo, /api/empresa, /api/horario, │ │
|
|
│ │ /api/informe, /api/jornada, /api/programa, │ │
|
|
│ │ /api/propuesta, /api/region, /api/sala, │ │
|
|
│ │ /api/sede, /api/tarifa, /api/usuario │ │
|
|
│ └──────────────────────┬──────────────────────────────┘ │
|
|
│ ┌──────────────────────▼──────────────────────────────┐ │
|
|
│ │ Ventas.Services (Business Logic) │ │
|
|
│ │ LeadService, ContratoService, CotizacionService, │ │
|
|
│ │ AlumnoService, ArqueoService, UsuarioService, etc. │ │
|
|
│ │ EmailService, ReportService (QuestPDF) │ │
|
|
│ └──────────────────────┬──────────────────────────────┘ │
|
|
│ ┌──────────────────────▼──────────────────────────────┐ │
|
|
│ │ Ventas.Infrastructure (Data Layer) │ │
|
|
│ │ │ EF Core → SPs simples (FromSql, ExecuteSqlRaw) │ │
|
|
│ │ │ Dapper → SPs complejos (multi-resultset) │ │
|
|
│ │ │ NpgsqlConnection → PostgreSQL │ │
|
|
│ │ └── DbContext, Repositories, UnitOfWork │ │
|
|
│ └──────────────────────┬──────────────────────────────┘ │
|
|
│ ┌──────────────────────▼──────────────────────────────┐ │
|
|
│ │ Ventas.Core (Domain) │ │
|
|
│ │ Entidades, DTOs, Interfaces, Enums │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
└──────────────────────┬──────────────────────────────────────┘
|
|
│
|
|
┌──────────────────────▼──────────────────────────────────────┐
|
|
│ PostgreSQL (192.168.0.254:5432) │
|
|
│ Database: ichn │
|
|
│ Tablas, SPs y datos ya migrados │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ ServicesExternos.API (Proyecto separado) │
|
|
│ ├── /api/dte (LibreDTE - emisión DTE Chile) │
|
|
│ ├── /api/pagos/transbank (Transbank Webpay Plus) │
|
|
│ ├── /api/email (SMTP con MailKit) │
|
|
│ ├── /api/auth/google (Google OAuth) │
|
|
│ │ │
|
|
│ ⚡ Reutilizable por otros proyectos de la organización │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 3. ESTRUCTURA DE DIRECTORIOS (NUEVA)
|
|
|
|
```
|
|
/ventas/
|
|
├── docker-compose.yml
|
|
├── .env.example
|
|
├── .gitignore
|
|
│
|
|
├── backend/
|
|
│ ├── Ventas.sln
|
|
│ ├── src/
|
|
│ │ ├── Ventas.API/
|
|
│ │ │ ├── Controllers/
|
|
│ │ │ │ ├── AuthController.cs
|
|
│ │ │ │ ├── LeadController.cs
|
|
│ │ │ │ ├── ContratoController.cs
|
|
│ │ │ │ ├── CotizacionController.cs
|
|
│ │ │ │ ├── AlumnoController.cs
|
|
│ │ │ │ ├── ArqueoController.cs
|
|
│ │ │ │ ├── CursoController.cs
|
|
│ │ │ │ ├── DescuentoController.cs
|
|
│ │ │ │ ├── DocumentoController.cs
|
|
│ │ │ │ ├── EjecutivoController.cs
|
|
│ │ │ │ ├── EmpresaController.cs
|
|
│ │ │ │ ├── HorarioController.cs
|
|
│ │ │ │ ├── InformeController.cs
|
|
│ │ │ │ ├── JornadaController.cs
|
|
│ │ │ │ ├── ProgramaController.cs
|
|
│ │ │ │ ├── PropuestaController.cs
|
|
│ │ │ │ ├── RegionController.cs
|
|
│ │ │ │ ├── SalaController.cs
|
|
│ │ │ │ ├── SedeController.cs
|
|
│ │ │ │ ├── TarifaController.cs
|
|
│ │ │ │ └── UsuarioController.cs
|
|
│ │ │ ├── Middleware/
|
|
│ │ │ │ └── JwtMiddleware.cs
|
|
│ │ │ ├── Program.cs
|
|
│ │ │ ├── appsettings.json
|
|
│ │ │ └── Dockerfile
|
|
│ │ ├── Ventas.Core/
|
|
│ │ │ ├── Entities/
|
|
│ │ │ │ ├── Lead.cs
|
|
│ │ │ │ ├── Contrato.cs
|
|
│ │ │ │ ├── Cotizacion.cs
|
|
│ │ │ │ ├── Alumno.cs
|
|
│ │ │ │ ├── ArqueoCaja.cs
|
|
│ │ │ │ ├── Curso.cs
|
|
│ │ │ │ ├── Descuento.cs
|
|
│ │ │ │ ├── Documento.cs
|
|
│ │ │ │ ├── Ejecutivo.cs
|
|
│ │ │ │ ├── Empresa.cs
|
|
│ │ │ │ ├── Horario.cs
|
|
│ │ │ │ ├── Jornada.cs
|
|
│ │ │ │ ├── Programa.cs
|
|
│ │ │ │ ├── Propuesta.cs
|
|
│ │ │ │ ├── Region.cs
|
|
│ │ │ │ ├── Sala.cs
|
|
│ │ │ │ ├── Sede.cs
|
|
│ │ │ │ ├── Tarifa.cs
|
|
│ │ │ │ ├── Usuario.cs
|
|
│ │ │ │ └── Comuna.cs
|
|
│ │ │ ├── DTOs/
|
|
│ │ │ ├── Enums/
|
|
│ │ │ └── Interfaces/
|
|
│ │ │ ├── ILeadRepository.cs
|
|
│ │ │ ├── IContratoRepository.cs
|
|
│ │ │ ├── ICotizacionRepository.cs
|
|
│ │ │ └── ...
|
|
│ │ ├── Ventas.Infrastructure/
|
|
│ │ │ ├── Data/
|
|
│ │ │ │ ├── VentasDbContext.cs
|
|
│ │ │ │ └── Configurations/
|
|
│ │ │ ├── Repositories/
|
|
│ │ │ │ ├── LeadRepository.cs
|
|
│ │ │ │ ├── ContratoRepository.cs
|
|
│ │ │ │ └── ...
|
|
│ │ │ └── Dapper/
|
|
│ │ │ └── SpComplexQueries.cs
|
|
│ │ └── Ventas.Services/
|
|
│ │ ├── LeadService.cs
|
|
│ │ ├── ContratoService.cs
|
|
│ │ ├── CotizacionService.cs
|
|
│ │ ├── AlumnoService.cs
|
|
│ │ ├── ArqueoService.cs
|
|
│ │ ├── UsuarioService.cs
|
|
│ │ ├── MailsService.cs
|
|
│ │ ├── ReportService.cs (QuestPDF)
|
|
│ │ └── ...
|
|
│ └── tests/
|
|
│ ├── Ventas.UnitTests/
|
|
│ │ ├── Services/
|
|
│ │ └── ...
|
|
│ └── Ventas.IntegrationTests/
|
|
│ ├── Repositories/
|
|
│ └── ...
|
|
│
|
|
├── services-externos/
|
|
│ ├── ServicesExternos.sln
|
|
│ ├── src/
|
|
│ │ ├── ServicesExternos.API/
|
|
│ │ │ ├── Controllers/
|
|
│ │ │ │ ├── DteController.cs
|
|
│ │ │ │ ├── TransbankController.cs
|
|
│ │ │ │ ├── EmailController.cs
|
|
│ │ │ │ └── GoogleAuthController.cs
|
|
│ │ │ ├── Program.cs
|
|
│ │ │ └── Dockerfile
|
|
│ │ ├── ServicesExternos.Core/
|
|
│ │ └── ServicesExternos.Infrastructure/
|
|
│ └── tests/
|
|
│
|
|
├── frontend/
|
|
│ ├── next.config.js
|
|
│ ├── package.json
|
|
│ ├── tsconfig.json
|
|
│ ├── Dockerfile
|
|
│ ├── public/
|
|
│ │ ├── img/
|
|
│ │ │ ├── favicon.png
|
|
│ │ │ ├── ichnHD.png
|
|
│ │ │ ├── Invertido_ICN.png
|
|
│ │ │ ├── samito.png
|
|
│ │ │ └── 03.jpg
|
|
│ │ └── ...
|
|
│ └── src/
|
|
│ ├── app/
|
|
│ │ ├── layout.tsx # = SAM.Master (sidebar + header)
|
|
│ │ ├── page.tsx # redirect a /dashboard
|
|
│ │ ├── login/
|
|
│ │ │ └── page.tsx # = Login.aspx
|
|
│ │ ├── dashboard/
|
|
│ │ │ └── page.tsx # = Index.aspx
|
|
│ │ ├── leads/
|
|
│ │ │ ├── page.tsx # = LeadV2.aspx
|
|
│ │ │ ├── [id]/
|
|
│ │ │ │ └── page.tsx # = LeadPanel.aspx
|
|
│ │ │ └── nuevo/
|
|
│ │ │ └── page.tsx # = Lead.aspx
|
|
│ │ ├── empresas/
|
|
│ │ │ ├── page.tsx # = Empresas.aspx
|
|
│ │ │ ├── [id]/
|
|
│ │ │ │ └── page.tsx # = AdminEmpresa.aspx
|
|
│ │ │ └── ventas/
|
|
│ │ │ └── page.tsx # = VentaEmpresa.aspx
|
|
│ │ ├── cotizaciones/
|
|
│ │ │ └── page.tsx # = pago.aspx
|
|
│ │ ├── arqueo/
|
|
│ │ │ └── page.tsx # = Arqueo.aspx
|
|
│ │ ├── cursos/
|
|
│ │ │ └── page.tsx # = Aperturas.aspx
|
|
│ │ ├── alumnos/
|
|
│ │ │ └── page.tsx # = Ficha.aspx
|
|
│ │ ├── reportes/
|
|
│ │ │ ├── ventas/
|
|
│ │ │ │ └── page.tsx # = ResumenVentas.aspx
|
|
│ │ │ ├── ventas-empresas/
|
|
│ │ │ │ └── page.tsx # = ResumenVentasEmp.aspx
|
|
│ │ │ └── reimpresion/
|
|
│ │ │ └── page.tsx # = Reimpresion.aspx
|
|
│ │ ├── autorizador/
|
|
│ │ │ └── page.tsx # = Autorizador.aspx
|
|
│ │ ├── contacto/
|
|
│ │ │ └── page.tsx # = Contacto.aspx
|
|
│ │ └── pdf/ # PDFs generados por API
|
|
│ ├── components/
|
|
│ │ ├── Sidebar.tsx # Navegación idéntica al original
|
|
│ │ ├── Header.tsx # Barra superior con usuario
|
|
│ │ ├── DataTable.tsx # Reemplazo de DataList/GridView
|
|
│ │ ├── ModalConfirmacion.tsx
|
|
│ │ ├── LoadingSpinner.tsx
|
|
│ │ └── ...
|
|
│ ├── services/
|
|
│ │ └── api.ts # Cliente HTTP centralizado
|
|
│ ├── hooks/
|
|
│ │ ├── useAuth.ts
|
|
│ │ ├── useInactividad.ts # Timeout 30 min (original)
|
|
│ │ └── ...
|
|
│ ├── lib/
|
|
│ │ ├── validarRut.ts # Migrado de validarRUT.js
|
|
│ │ └── utils.ts
|
|
│ └── styles/
|
|
│ ├── globals.css
|
|
│ ├── style.css # Mismo CSS original
|
|
│ ├── ichn.css # Mismo CSS original
|
|
│ ├── ichnPaginas.css # Mismo CSS original
|
|
│ └── checkRadio.css # Mismo CSS original
|
|
│
|
|
└── scripts/
|
|
├── migracion/ # Scripts de referencia
|
|
└── deploy/
|
|
```
|
|
|
|
---
|
|
|
|
## 4. FASES DE MIGRACIÓN
|
|
|
|
### FASE 1: BACKEND .NET 10 — API REST (6-8 semanas)
|
|
|
|
#### 1.1 Crear solución y proyectos base
|
|
|
|
```bash
|
|
dotnet new sln -n Ventas
|
|
dotnet new webapi -n Ventas.API
|
|
dotnet new classlib -n Ventas.Core
|
|
dotnet new classlib -n Ventas.Infrastructure
|
|
dotnet new classlib -n Ventas.Services
|
|
dotnet sln add src/**/*.csproj
|
|
```
|
|
|
|
#### 1.2 Ventas.Core — Entidades y DTOs
|
|
|
|
Mapear todas las entidades desde las 25 clases de `Datos/`. Crear DTOs para request/response de cada endpoint.
|
|
|
|
**Ejemplo Lead (Core):**
|
|
```csharp
|
|
namespace Ventas.Core.Entities;
|
|
|
|
public class Lead
|
|
{
|
|
public int Id { get; set; }
|
|
public string Nombre { get; set; } = string.Empty;
|
|
public string? Mail { get; set; }
|
|
public string? Telefono { get; set; }
|
|
public string? Producto { get; set; }
|
|
public int? Contacto { get; set; }
|
|
public int? EjecutivoId { get; set; }
|
|
public int? Estado { get; set; }
|
|
public DateTime? FechaCreacion { get; set; }
|
|
// ... resto de propiedades
|
|
}
|
|
```
|
|
|
|
#### 1.3 Ventas.Infrastructure — Acceso a Datos
|
|
|
|
**Estrategia EF Core + Dapper:**
|
|
|
|
| Tipo de SP | Estrategia | Ejemplo |
|
|
|-----------|-----------|---------|
|
|
| **SP simple** (1 tabla de vuelta) | `EF Core FromSqlRaw` | `Lead_buscarXestado` |
|
|
| **SP simple** (INSERT/UPDATE sin return) | `EF Core ExecuteSqlRaw` | `GrabaLead` |
|
|
| **SP complejo** (multi-resultset) | **Dapper** `QueryMultipleAsync` | `InformeVentasAgnoMesEjecutivo` |
|
|
| **SP complejo** (cursor, temp tables) | **Dapper** + `CommandType.StoredProcedure` | Reportes Crystal |
|
|
|
|
**DbContext (EF Core):**
|
|
```csharp
|
|
public class VentasDbContext : DbContext
|
|
{
|
|
public VentasDbContext(DbContextOptions<VentasDbContext> options) : base(options) { }
|
|
|
|
// DbSets solo para entidades que usan FromSqlRaw
|
|
public DbSet<Lead> Leads { get; set; }
|
|
public DbSet<Contrato> Contratos { get; set; }
|
|
// ...
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
// Configurar entidades sin tabla (solo para SPs)
|
|
modelBuilder.Entity<Lead>(e => {
|
|
e.HasNoKey();
|
|
e.ToView(null);
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**Repositorio con Dapper (SPs complejos):**
|
|
```csharp
|
|
public class InformeRepository
|
|
{
|
|
private readonly string _connectionString;
|
|
|
|
public async Task<InformeVentasDto> InformeVentasAgnoMesAsync(int mes, int agno, string tipo)
|
|
{
|
|
using var connection = new NpgsqlConnection(_connectionString);
|
|
using var multi = await connection.QueryMultipleAsync(
|
|
"sige_sam_v3.InformeVentasAgnoMes",
|
|
new { mes, agno, tipo },
|
|
commandType: CommandType.StoredProcedure
|
|
);
|
|
|
|
return new InformeVentasDto
|
|
{
|
|
Resumen = multi.ReadSingle<VentaResumen>(),
|
|
Detalle = multi.Read<VentaDetalle>().ToList(),
|
|
Totales = multi.ReadSingle<VentaTotales>()
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
**Paquetes NuGet requeridos:**
|
|
```xml
|
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.*" />
|
|
<PackageReference Include="Dapper" Version="2.*" />
|
|
<PackageReference Include="Npgsql" Version="10.*" />
|
|
<PackageReference Include="QuestPDF" Version="2025.*" />
|
|
<PackageReference Include="MailKit" Version="4.*" />
|
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.*" />
|
|
```
|
|
|
|
#### 1.4 Ventas.Services — Lógica de Negocio
|
|
|
|
Migrar las 28 clases de `Negocio/` a servicios con Inyección de Dependencias.
|
|
|
|
```csharp
|
|
public class LeadService : ILeadService
|
|
{
|
|
private readonly ILeadRepository _leadRepository;
|
|
|
|
public LeadService(ILeadRepository leadRepository)
|
|
{
|
|
_leadRepository = leadRepository;
|
|
}
|
|
|
|
public async Task<Result<string>> IngresarAsync(LeadCreateDto dto)
|
|
{
|
|
// Validar datos
|
|
// Llamar repositorio
|
|
// Aplicar reglas de negocio
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 1.5 Ventas.API — Controladores REST
|
|
|
|
Crear 1 endpoint por cada método relevante de las 25 clases de datos + 40 code-behind.
|
|
|
|
**Ejemplo LeadController:**
|
|
```csharp
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
[Authorize]
|
|
public class LeadController : ControllerBase
|
|
{
|
|
private readonly ILeadService _leadService;
|
|
|
|
public LeadController(ILeadService leadService)
|
|
{
|
|
_leadService = leadService;
|
|
}
|
|
|
|
[HttpGet("{id}")]
|
|
public async Task<IActionResult> GetById(int id)
|
|
{
|
|
var result = await _leadService.BuscarIdAsync(id);
|
|
return Ok(result);
|
|
}
|
|
|
|
[HttpGet("buscar")]
|
|
public async Task<IActionResult> Buscar(
|
|
[FromQuery] int ejecutivo,
|
|
[FromQuery] int estado,
|
|
[FromQuery] int dias)
|
|
{
|
|
var result = await _leadService.BuscarAsync(ejecutivo, estado, dias);
|
|
return Ok(result);
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<IActionResult> Ingresar(LeadCreateDto dto)
|
|
{
|
|
var result = await _leadService.IngresarAsync(dto);
|
|
return Ok(new { mensaje = result });
|
|
}
|
|
|
|
[HttpPost("{id}/actividad")]
|
|
public async Task<IActionResult> IngresarActividad(int id, ActividadCreateDto dto)
|
|
{
|
|
var result = await _leadService.IngresarActividadAsync(id, dto);
|
|
return Ok(new { mensaje = result });
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 1.6 Autenticación JWT (reemplazo de FormsAuthentication)
|
|
|
|
```csharp
|
|
// AuthController.cs
|
|
[HttpPost("login")]
|
|
public async Task<IActionResult> Login(LoginRequest request)
|
|
{
|
|
var usuario = await _usuarioService.BuscarAsync(request.Rut, request.Clave);
|
|
if (usuario == null)
|
|
return Unauthorized();
|
|
|
|
var token = _jwtService.GenerateToken(usuario);
|
|
var cookieOptions = new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
Secure = true,
|
|
SameSite = SameSiteMode.Strict,
|
|
Expires = DateTime.UtcNow.AddMinutes(30)
|
|
};
|
|
Response.Cookies.Append("SAM_TOKEN", token, cookieOptions);
|
|
|
|
return Ok(new {
|
|
nombre = usuario.Nombre,
|
|
sede = usuario.Sede,
|
|
token // también devuelto para apps mobile
|
|
});
|
|
}
|
|
```
|
|
|
|
**Migración de cookies originales → JWT claims:**
|
|
| Cookie original (FormsAuth) | Claims JWT |
|
|
|---------------------------|------------|
|
|
| `SAM_ID_SEDE` | `claim: sede` |
|
|
| `SAM_USUARIO_NOMBRE` | `claim: name` |
|
|
| `SAM_USUARIO_ID` | `claim: sub` (rut) |
|
|
| `SAM_USUARIO_ID_PASS` | No se migra (inseguro) |
|
|
|
|
#### 1.7 Reportes QuestPDF (reemplazo Crystal Reports)
|
|
|
|
Cada uno de los 17 reportes Crystal → una clase QuestPDF.
|
|
|
|
```csharp
|
|
// ReportService.cs
|
|
public class ReportService : IReportService
|
|
{
|
|
public async Task<byte[]> GenerarContratoPdfAsync(int contratoId)
|
|
{
|
|
var data = await _contratoRepository.BuscarInfoPdfAsync(contratoId);
|
|
// ... mapear a modelo del reporte
|
|
|
|
return Document.Create(container =>
|
|
{
|
|
container.Page(page =>
|
|
{
|
|
page.Size(PageSizes.A4);
|
|
page.Margin(2, Unit.Cm);
|
|
page.Header().Element(c => ComposeHeader(c, data));
|
|
page.Content().Element(c => ComposeContent(c, data));
|
|
page.Footer().Element(ComposeFooter);
|
|
});
|
|
}).GeneratePdf();
|
|
}
|
|
|
|
private void ComposeHeader(IContainer container, ContratoPdfData data)
|
|
{
|
|
container.Row(r =>
|
|
{
|
|
r.ConstantItem(100).Image("logo.png");
|
|
r.RelativeItem().AlignRight().Text($"Contrato N° {data.Numero}")
|
|
.FontSize(16).Bold();
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**Endpoint de reportes:**
|
|
```csharp
|
|
[HttpGet("reportes/contrato/{id}")]
|
|
public async Task<IActionResult> GetContratoPdf(int id)
|
|
{
|
|
var pdf = await _reportService.GenerarContratoPdfAsync(id);
|
|
return File(pdf, "application/pdf", $"contrato_{id}.pdf");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### FASE 2: SERVICES EXTERNOS — API APARTE (2-3 semanas)
|
|
|
|
Proyecto separado `services-externos/` que expone servicios reutilizables.
|
|
|
|
#### 2.1 LibreDTE (DTE Chile)
|
|
|
|
Migrar `Web/FacturadorDTE/LibreDteConector.cs` → `ServicesExternos.API`.
|
|
|
|
```csharp
|
|
[ApiController]
|
|
[Route("api/dte")]
|
|
public class DteController : ControllerBase
|
|
{
|
|
[HttpPost("emitir")]
|
|
public async Task<IActionResult> Emitir(DteEmissionRequest request)
|
|
{
|
|
// Lógica migrada de LibreDteConector.cs
|
|
}
|
|
|
|
[HttpGet("estado/{codigo}")]
|
|
public async Task<IActionResult> ConsultarEstado(string codigo)
|
|
{
|
|
// Consultar estado DTE
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 2.2 Transbank Webpay Plus
|
|
|
|
Migrar `Negocio/PagadorTransbank.cs` + `Datos/TransbankInfo.cs`.
|
|
|
|
```csharp
|
|
[ApiController]
|
|
[Route("api/pagos/transbank")]
|
|
public class TransbankController : ControllerBase
|
|
{
|
|
[HttpPost("crear-transaccion")]
|
|
public async Task<IActionResult> CrearTransaccion(TransbankRequest request)
|
|
{
|
|
// Lógica de creación de transacción Webpay
|
|
}
|
|
|
|
[HttpPost("confirmar")]
|
|
public async Task<IActionResult> Confirmar(string token_ws)
|
|
{
|
|
// Confirmar y guardar voucher
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 2.3 SMTP Email (con MailKit)
|
|
|
|
Migrar `Negocio/Mails.cs` usando `MailKit` (reemplaza `SmtpClient` obsoleto).
|
|
|
|
```csharp
|
|
[ApiController]
|
|
[Route("api/email")]
|
|
public class EmailController : ControllerBase
|
|
{
|
|
[HttpPost("send")]
|
|
public async Task<IActionResult> Send(EmailRequest request)
|
|
{
|
|
using var client = new SmtpClient();
|
|
await client.ConnectAsync(_smtpHost, _smtpPort, SecureSocketOptions.StartTls);
|
|
await client.AuthenticateAsync(_smtpUser, _smtpPassword);
|
|
// Enviar con templates HTML existentes
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### FASE 3: FRONTEND NEXT.JS (6-8 semanas)
|
|
|
|
#### 3.1 Inicialización
|
|
|
|
```bash
|
|
npx create-next-app@latest frontend --typescript --tailwind --app
|
|
```
|
|
|
|
#### 3.2 Layout Principal (SAM.Master)
|
|
|
|
Replicar exactamente el `SAM.Master` actual en `app/layout.tsx`:
|
|
|
|
- Sidebar con `boxicons` + FontAwesome
|
|
- Logo SAM + favicon
|
|
- Header con nombre de usuario + botón cerrar sesión
|
|
- Timeout de inactividad de 30 min (misma lógica JS)
|
|
- Responsive design (media queries originales)
|
|
- Bootstrap 5 via CDN (misma versión)
|
|
- Google Fonts Fira Sans
|
|
|
|
```tsx
|
|
// app/layout.tsx
|
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<html lang="es">
|
|
<head>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
<link href="https://fonts.googleapis.com/css?family=Fira+Sans&display=swap" rel="stylesheet" />
|
|
<link href="https://unpkg.com/boxicons@2.0.7/css/boxicons.min.css" rel="stylesheet" />
|
|
<link href="/css/style.css" rel="stylesheet" />
|
|
<link href="/css/ichn.css" rel="stylesheet" />
|
|
<link href="/css/ichnPaginas.css" rel="stylesheet" />
|
|
</head>
|
|
<body>
|
|
<AuthProvider>
|
|
<div className="sidebar">
|
|
{/* Sidebar idéntica al original */}
|
|
<div className="logo-details">
|
|
<img className="icon" src="/img/favicon.png" width="60" />
|
|
<div className="logo_name">SAM</div>
|
|
<i className='bx bx-menu' id="btn"></i>
|
|
</div>
|
|
<ul className="nav-list">
|
|
<li>
|
|
<Link href="/dashboard">
|
|
<i className="fas fa-home"></i>
|
|
<span className="links_name">Dashboard</span>
|
|
</Link>
|
|
</li>
|
|
{/* ... resto de links idénticos */}
|
|
</ul>
|
|
</div>
|
|
<section className="home-section alert-dark">
|
|
<Header /> {/* Nombre usuario + cerrar sesión */}
|
|
{children}
|
|
</section>
|
|
</AuthProvider>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
```
|
|
|
|
#### 3.3 Login Page
|
|
|
|
```tsx
|
|
// app/login/page.tsx
|
|
"use client";
|
|
export default function LoginPage() {
|
|
const [rut, setRut] = useState("");
|
|
const [clave, setClave] = useState("");
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
const res = await api.post("/auth/login", { rut, clave });
|
|
// Guardar token, redirigir a dashboard
|
|
};
|
|
|
|
return (
|
|
<div className="container mt-5">
|
|
<div className="row justify-content-center">
|
|
<div className="col-md-4">
|
|
<div className="card">
|
|
<div className="card-body">
|
|
<h3 className="text-center">Iniciar Sesión</h3>
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="mb-3">
|
|
<label>RUT</label>
|
|
<input className="form-control" value={rut}
|
|
onChange={e => setRut(e.target.value)} />
|
|
</div>
|
|
<div className="mb-3">
|
|
<label>Clave</label>
|
|
<input type="password" className="form-control" value={clave}
|
|
onChange={e => setClave(e.target.value)} />
|
|
</div>
|
|
<button type="submit" className="btn btn-primary w-100">Ingresar</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
#### 3.4 Migración de Funcionalidad JS
|
|
|
|
| Archivo JS original | Migración a TypeScript |
|
|
|-------------------|----------------------|
|
|
| `js/validarRUT.js` | `lib/validarRut.ts` |
|
|
| `js/numeroTexto.js` | `lib/utils.ts` |
|
|
| `js/toastr.min.css` | `react-hot-toast` package |
|
|
| Inline JS (inactividad) | `hooks/useInactividad.ts` |
|
|
| Inline JS (sidebar toggle) | `components/Sidebar.tsx` |
|
|
|
|
#### 3.5 Manejo de Estado y DataTables
|
|
|
|
| WebForms Control | Equivalente Next.js |
|
|
|-----------------|-------------------|
|
|
| `GridView` / `DataList` | `react-data-table-component` o `@tanstack/react-table` |
|
|
| `Repeater` | `.map()` con fragmentos |
|
|
| `UpdatePanel` (postback) | `useState` + fetch API |
|
|
| `ViewState` | React state + React Query cache |
|
|
| `LinkButton` | `button` + `onClick` + fetch |
|
|
| `RequiredFieldValidator` | React Hook Form + Zod |
|
|
| `Master.FindControl` | React Context + props |
|
|
|
|
---
|
|
|
|
### FASE 4: CONTENEDORES (1 semana)
|
|
|
|
#### 4.1 Dockerfile Backend
|
|
|
|
```dockerfile
|
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
|
WORKDIR /src
|
|
COPY . .
|
|
RUN dotnet restore
|
|
RUN dotnet publish -c Release -o /app
|
|
|
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0
|
|
WORKDIR /app
|
|
COPY --from=build /app .
|
|
EXPOSE 8080
|
|
ENTRYPOINT ["dotnet", "Ventas.API.dll"]
|
|
```
|
|
|
|
#### 4.2 Dockerfile Frontend
|
|
|
|
```dockerfile
|
|
FROM node:20-alpine AS build
|
|
WORKDIR /app
|
|
COPY package*.json ./
|
|
RUN npm ci
|
|
COPY . .
|
|
RUN npm run build
|
|
|
|
FROM node:20-alpine AS runtime
|
|
WORKDIR /app
|
|
COPY --from=build /app/.next/standalone ./
|
|
COPY --from=build /app/public ./public
|
|
EXPOSE 3000
|
|
ENV NODE_ENV=production
|
|
CMD ["node", "server.js"]
|
|
```
|
|
|
|
#### 4.3 docker-compose.yml
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
services:
|
|
backend-api:
|
|
build:
|
|
context: ./backend
|
|
dockerfile: src/Ventas.API/Dockerfile
|
|
environment:
|
|
- ConnectionStrings__Default=Host=192.168.0.254;Port=5432;Database=ichn;Username=postgres;Password=apoca11
|
|
- Jwt__Secret=${JWT_SECRET}
|
|
- Jwt__Expiration=30
|
|
- LibreDte__UserHash=${LIBREDTE_USER_HASH}
|
|
- LibreDte__Ambiente=${LIBREDTE_AMBIENTE}
|
|
ports:
|
|
- "5000:8080"
|
|
networks:
|
|
- ventas-network
|
|
extra_hosts:
|
|
- "host.docker.internal:192.168.0.254"
|
|
|
|
services-externos:
|
|
build:
|
|
context: ./services-externos
|
|
dockerfile: src/ServicesExternos.API/Dockerfile
|
|
environment:
|
|
- LibreDte__UserHash=${LIBREDTE_USER_HASH}
|
|
- LibreDte__Ambiente=${LIBREDTE_AMBIENTE}
|
|
- Transbank__ApiKey=${TRANSBANK_API_KEY}
|
|
- Transbank__CommerceCode=${TRANSBANK_COMMERCE_CODE}
|
|
- Smtp__Host=smtp.gmail.com
|
|
- Smtp__Port=587
|
|
- Smtp__User=noresponder@norteamericano.cl
|
|
- Smtp__Password=${SMTP_PASSWORD}
|
|
- Google__ClientId=${GOOGLE_CLIENT_ID}
|
|
- Google__ClientSecret=${GOOGLE_CLIENT_SECRET}
|
|
ports:
|
|
- "5001:8080"
|
|
networks:
|
|
- ventas-network
|
|
|
|
frontend:
|
|
build: ./frontend
|
|
environment:
|
|
- NEXT_PUBLIC_API_URL=http://backend-api:8080/api
|
|
- NEXT_PUBLIC_SERVICES_URL=http://services-externos:8080/api
|
|
ports:
|
|
- "3000:3000"
|
|
depends_on:
|
|
- backend-api
|
|
networks:
|
|
- ventas-network
|
|
|
|
networks:
|
|
ventas-network:
|
|
driver: bridge
|
|
```
|
|
|
|
#### 4.4 .env
|
|
|
|
```env
|
|
# JWT
|
|
JWT_SECRET=generar-clave-segura-aqui
|
|
JWT_EXPIRATION=30
|
|
|
|
# LibreDTE
|
|
LIBREDTE_USER_HASH=ZDLimhVCDEXoHR6yDTJpb80ta7KG4DqI
|
|
LIBREDTE_AMBIENTE=0
|
|
|
|
# Transbank
|
|
TRANSBANK_API_KEY=tu-api-key
|
|
TRANSBANK_COMMERCE_CODE=tu-codigo-comercio
|
|
|
|
# SMTP
|
|
SMTP_PASSWORD=smith2251!
|
|
|
|
# Google OAuth
|
|
GOOGLE_CLIENT_ID=tu-client-id
|
|
GOOGLE_CLIENT_SECRET=tu-client-secret
|
|
```
|
|
|
|
---
|
|
|
|
### FASE 5: PRUEBAS INTEGRALES E2E (2-3 semanas)
|
|
|
|
#### 5.1 Backend Unit Tests (xUnit + Moq)
|
|
|
|
```csharp
|
|
public class LeadServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task Ingresar_WithValidData_ReturnsOk()
|
|
{
|
|
// Arrange
|
|
var mockRepo = new Mock<ILeadRepository>();
|
|
mockRepo.Setup(r => r.IngresarAsync(It.IsAny<LeadCreateDto>()))
|
|
.ReturnsAsync("ok");
|
|
var service = new LeadService(mockRepo.Object);
|
|
|
|
// Act
|
|
var result = await service.IngresarAsync(validDto);
|
|
|
|
// Assert
|
|
Assert.Equal("ok", result);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 5.2 Backend Integration Tests (TestContainers + PostgreSQL)
|
|
|
|
```csharp
|
|
public class LeadRepositoryTests : IClassFixture<PostgreSqlFixture>
|
|
{
|
|
[Fact]
|
|
public async Task BuscarXestado_ReturnsLeads()
|
|
{
|
|
using var connection = new NpgsqlConnection(_fixture.ConnectionString);
|
|
var repo = new LeadRepository(_fixture.ConnectionString);
|
|
|
|
var result = await repo.BuscarXestadoAsync(ejecutivo: 1, estado: 1);
|
|
|
|
Assert.NotEmpty(result);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 5.3 Frontend E2E (Playwright)
|
|
|
|
**Escenarios a testear (mínimo 20):**
|
|
|
|
| # | Escenario | Flujo |
|
|
|---|-----------|-------|
|
|
| 1 | Login exitoso | Login.aspx → Index.aspx |
|
|
| 2 | Login fallido | Mostrar error |
|
|
| 3 | Dashboard carga | Ver cards de resumen |
|
|
| 4 | Listar leads | LeadV2.aspx carga DataTable |
|
|
| 5 | Crear lead | Formulario submit → éxito |
|
|
| 6 | Buscar lead por filtro | Búsqueda por estado/días |
|
|
| 7 | Agregar actividad a lead | Modal actividad → submit |
|
|
| 8 | Crear cotización | Lead → cotización → cálculo montos |
|
|
| 9 | Pagar cotización | pago.aspx → Transbank |
|
|
| 10 | Ver arqueo | Arqueo.aspx con datos |
|
|
| 11 | Generar PDF contrato | Descarga PDF |
|
|
| 12 | Generar PDF cotización | Descarga PDF |
|
|
| 13 | Autorizar descuento | Flujo autorizador |
|
|
| 14 | CRUD empresa | Crear/editar empresa |
|
|
| 15 | Reporte ventas | ResumenVentas.aspx |
|
|
| 16 | Cierre de sesión | Logout → Login |
|
|
| 17 | Timeout inactividad | 30 min → redirect |
|
|
| 18 | Reimpresión documento | Reimpresion.aspx |
|
|
| 19 | Sidebar navegación | Todas las rutas |
|
|
| 20 | Responsive mobile | Sidebar contraída |
|
|
|
|
**Ejemplo test Playwright:**
|
|
```typescript
|
|
// tests/e2e/login.spec.ts
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test('login exitoso redirige a dashboard', async ({ page }) => {
|
|
await page.goto('/login');
|
|
await page.fill('[name="rut"]', '12345678-5');
|
|
await page.fill('[name="clave"]', 'password');
|
|
await page.click('button:has-text("Ingresar")');
|
|
await expect(page).toHaveURL(/\/dashboard/);
|
|
await expect(page.locator('.badge')).toContainText('Menu de Aplicaciones');
|
|
});
|
|
|
|
test('timeout de inactividad redirige a login', async ({ page }) => {
|
|
// Mockear setTimeout para que dispare inmediatamente
|
|
await page.clock.install();
|
|
await page.goto('/dashboard');
|
|
await page.clock.fastForward(1800001);
|
|
await expect(page).toHaveURL(/\/login/);
|
|
});
|
|
```
|
|
|
|
#### 5.4 Visual Regression Testing
|
|
|
|
```typescript
|
|
test('dashboard visual match', async ({ page }) => {
|
|
await page.goto('/dashboard');
|
|
await expect(page).toHaveScreenshot('dashboard.png', {
|
|
maxDiffPixelRatio: 0.02
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 5. CRONOGRAMA ESTIMADO
|
|
|
|
| Fase | Actividad | Duración | Dependencias |
|
|
|------|-----------|----------|-------------|
|
|
| **Fase 1** | Backend .NET 10 — API REST | 6-8 semanas | DB ya operativa |
|
|
| 1.1 | Crear solución + proyectos base | 2 días | - |
|
|
| 1.2 | Ventas.Core (entidades, DTOs) | 1 semana | 1.1 |
|
|
| 1.3 | Ventas.Infrastructure (EF + Dapper) | 2-3 semanas | 1.2 |
|
|
| 1.4 | Ventas.Services (lógica negocio) | 2 semanas | 1.3 |
|
|
| 1.5 | Ventas.API (controladores) | 1 semana | 1.4 |
|
|
| 1.6 | Auth JWT | 3 días | 1.4 |
|
|
| 1.7 | Reportes QuestPDF (17 reportes) | 2-3 semanas | 1.3 |
|
|
| **Fase 2** | Services Externos API | 2-3 semanas | - |
|
|
| 2.1 | LibreDTE | 1 semana | - |
|
|
| 2.2 | Transbank | 1 semana | - |
|
|
| 2.3 | Email (MailKit) | 2 días | - |
|
|
| **Fase 3** | Frontend Next.js | 6-8 semanas | Fase 1 (API) |
|
|
| 3.1 | Setup + Layout (SAM.Master replica) | 1 semana | - |
|
|
| 3.2 | Login + Auth flow | 3 días | 3.1 |
|
|
| 3.3 | Dashboard | 3 días | 3.2 |
|
|
| 3.4 | Módulo Leads (5 páginas) | 1 semana | 3.2 |
|
|
| 3.5 | Módulo Cotizaciones/Pagos | 1 semana | 3.2 |
|
|
| 3.6 | Módulo Empresas | 1 semana | 3.2 |
|
|
| 3.7 | Resto de páginas (~25) | 2-3 semanas | 3.2 |
|
|
| **Fase 4** | Contenedores | 1 semana | Fases 1, 2, 3 |
|
|
| 4.1 | Dockerfiles | 2 días | - |
|
|
| 4.2 | docker-compose | 1 día | 4.1 |
|
|
| 4.3 | Secretos y .env | 1 día | 4.2 |
|
|
| **Fase 5** | Pruebas E2E | 2-3 semanas | Fases 1, 3 |
|
|
| 5.1 | Unit tests backend | 1 semana | Fase 1 |
|
|
| 5.2 | Integration tests | 1 semana | Fase 1 |
|
|
| 5.3 | Playwright E2E (20 escenarios) | 1-2 semanas | Fase 3 |
|
|
| **Total** | | **17-23 semanas** | |
|
|
|
|
---
|
|
|
|
## 6. RIESGOS Y MITIGACIONES
|
|
|
|
| Riesgo | Impacto | Probabilidad | Mitigación |
|
|
|--------|---------|-------------|------------|
|
|
| SPs complejos con lógica MySQL no tienen equivalente directo en PostgreSQL | Alto | Media | Usar Dapper con consultas raw; dividir SPs grandes en funciones más pequeñas |
|
|
| QuestPDF no puede replicar exactamente el layout visual de Crystal Reports | Alto | Alta | Revisar cada reporte visualmente; usar imágenes de fondo PNG si es necesario; considerar Puppeteer+HTML como fallback |
|
|
| FormsAuthentication → JWT cambia toda la lógica de sesión | Medio | Media | Implementar middleware progresivo; mantener compatibilidad de cookies |
|
|
| WebForms ViewState/postback → React state management | Medio | Media | Usar React Query para caché de datos del servidor; estado local para UI |
|
|
| Los 40 code-behind mezclan lógica de UI con lógica de negocio | Alto | Alta | Refactorizar: UI → frontend, lógica → servicios backend, datos → repositorios |
|
|
| Transbank SDK para .NET Framework puede no tener versión .NET 10 | Medio | Baja | Probar SDK .NET Standard 2.0; si no funciona, llamar API REST de Transbank directamente |
|
|
| Estilos CSS originales pueden no renderizar exactamente igual en Next.js | Bajo | Media | Usar los mismos archivos CSS sin modificaciones; probar visualmente cada página |
|
|
|
|
---
|
|
|
|
## 7. EQUIPO RECOMENDADO
|
|
|
|
| Rol | Cantidad | Dedicación |
|
|
|-----|----------|------------|
|
|
| Backend .NET 10 | 1-2 devs | Tiempo completo |
|
|
| Frontend Next.js | 1-2 devs | Tiempo completo |
|
|
| Base de datos PostgreSQL | 1 DBA | Partial |
|
|
| QA / E2E Tests | 1 QA | Partial |
|
|
|
|
---
|
|
|
|
## 8. ENTREGABLES POR FASE
|
|
|
|
| Fase | Entregable |
|
|
|------|-----------|
|
|
| **Fase 1** | Backend API funcionando con todos los endpoints, JWT auth, reportes QuestPDF |
|
|
| **Fase 2** | Services Externos API con LibreDTE, Transbank, Email funcionando |
|
|
| **Fase 3** | Frontend Next.js con UI idéntica, todas las páginas funcionales conectadas a la API |
|
|
| **Fase 4** | docker-compose.yml con backend + frontend + services-externos, secretos en .env |
|
|
| **Fase 5** | Suite de tests: unit tests (back), integration tests (back), E2E Playwright (20 escenarios) |
|
|
|
|
---
|
|
|
|
## 9. CONFIGURACIÓN DE CONEXIÓN A BASE DE DATOS
|
|
|
|
```json
|
|
// appsettings.json (solo estructura, valores reales en env vars)
|
|
{
|
|
"ConnectionStrings": {
|
|
"Default": "Host=192.168.0.254;Port=5432;Database=ichn;Username=postgres;Password=apoca11;Pooling=true;Maximum Pool Size=100;"
|
|
},
|
|
"Jwt": {
|
|
"Secret": "",
|
|
"ExpirationMinutes": 30
|
|
},
|
|
"LibreDte": {
|
|
"UserHash": "",
|
|
"Ambiente": "0"
|
|
},
|
|
"Smtp": {
|
|
"Host": "smtp.gmail.com",
|
|
"Port": 587,
|
|
"User": "noresponder@norteamericano.cl",
|
|
"Password": ""
|
|
}
|
|
}
|
|
```
|
|
|
|
```csharp
|
|
// Program.cs
|
|
builder.Services.AddDbContext<VentasDbContext>(options =>
|
|
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
|
|
|
|
builder.Services.AddScoped<ILeadRepository>(sp =>
|
|
new LeadRepository(builder.Configuration.GetConnectionString("Default")!));
|
|
```
|