Compare commits
11 Commits
3d5419403d
...
129991cfb7
| Author | SHA1 | Date | |
|---|---|---|---|
| 129991cfb7 | |||
| cf2349ef2f | |||
| 9c1eb20132 | |||
| c43dbcce8b | |||
| 202a59326b | |||
| c1e35445f6 | |||
| 1b1baaf6bf | |||
| df028eb4dd | |||
| e1367fc23a | |||
| 2dfe7daa2c | |||
| aed251d82f |
@@ -0,0 +1,15 @@
|
|||||||
|
# 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
|
||||||
|
TRANSBANK_ENVIRONMENT=integration
|
||||||
|
|
||||||
|
# SMTP
|
||||||
|
SMTP_PASSWORD=smith2251!
|
||||||
+77
-53
@@ -12,13 +12,15 @@
|
|||||||
| Componente | Estado |
|
| Componente | Estado |
|
||||||
|-----------|--------|
|
|-----------|--------|
|
||||||
| Backend .NET 10 — Skeleton (sln + 4 proyectos + NuGet) | ✅ CREADO |
|
| Backend .NET 10 — Skeleton (sln + 4 proyectos + NuGet) | ✅ CREADO |
|
||||||
| Entidades / DTOs / Interfaces (Ventas.Core) | ✅ CREADO |
|
| Entidades (20) + DTOs + Enums + Interfaces | ✅ COMPLETO |
|
||||||
| Infrastructure (EF Core + Dapper + Repos) | ✅ PARCIAL (DbContext + LeadRepository listos) |
|
| Infrastructure (EF Core + Dapper + Repos + SpComplex) | ✅ COMPLETO |
|
||||||
| Services (lógica de negocio) | ❌ PENDIENTE |
|
| Services (lógica de negocio) | ✅ COMPLETO (8 services) |
|
||||||
| API Controllers | ❌ PENDIENTE |
|
| API Controllers | ✅ COMPLETO (21 controllers) |
|
||||||
| Auth JWT | ❌ PENDIENTE |
|
| Auth JWT (service + middleware) | ✅ COMPLETO |
|
||||||
|
| Reportes QuestPDF (17 reportes Crystal) | ✅ COMPLETO |
|
||||||
|
| Reportes empresa (variantes) + controller | ✅ COMPLETO |
|
||||||
| Reportes QuestPDF (17 reportes) | ❌ PENDIENTE |
|
| Reportes QuestPDF (17 reportes) | ❌ PENDIENTE |
|
||||||
| ServicesExternos.API | ❌ PENDIENTE |
|
| ServicesExternos.API | ✅ COMPLETO |
|
||||||
| Frontend Next.js | ❌ PENDIENTE |
|
| Frontend Next.js | ❌ PENDIENTE |
|
||||||
| Contenedores (Docker) | ❌ PENDIENTE |
|
| Contenedores (Docker) | ❌ PENDIENTE |
|
||||||
| Tests E2E | ❌ PENDIENTE |
|
| Tests E2E | ❌ PENDIENTE |
|
||||||
@@ -30,59 +32,68 @@
|
|||||||
### FASE 1: BACKEND .NET 10 — API REST (~6-8 semanas)
|
### FASE 1: BACKEND .NET 10 — API REST (~6-8 semanas)
|
||||||
|
|
||||||
- [x] **1.1** Crear solución + proyectos base
|
- [x] **1.1** Crear solución + proyectos base
|
||||||
- [x] **1.2 Ventas.Core — Entidades y DTOs** (~1 sem)
|
- [x] **1.2 Ventas.Core — Entidades y DTOs**
|
||||||
- [x] Lead.cs + resto entidades (25 clases desde `Datos/`)
|
- [x] 20 entidades del plan + Ejecutivo.cs + extras
|
||||||
- [x] DTOs request/response (LeadDto, LoginRequest, etc.)
|
- [x] DTOs, Enums (4), Interfaces (7)
|
||||||
- [x] Enums (EstadoLead, TipoPago, TipoDocumento, TipoDescuento)
|
- [x] **1.3 Ventas.Infrastructure — Acceso a Datos**
|
||||||
- [x] Interfaces repositorios (ILeadRepository, ILeadQueryRepository, IAlumnoRepository, IUsuarioRepository, IContratoRepository, ICotizacionRepository)
|
- [x] VentasDbContext (EF Core + Npgsql)
|
||||||
- [x] **1.3 Ventas.Infrastructure — Acceso a Datos** (~2-3 sem)
|
- [x] 7 repositorios (Lead, LeadQuery, Contrato, Cotizacion, Alumno, Usuario, Informe)
|
||||||
- [x] VentasDbContext.cs (EF Core + Npgsql)
|
- [x] SpComplexQueries.cs (Dapper multi-resultset)
|
||||||
- [x] LeadRepository.cs (SPs INSERT/UPDATE con Dapper)
|
- [x] Configurations/ + Dapper/ directories
|
||||||
- [x] LeadQueryRepository.cs (SPs SELECT con Dapper)
|
- [x] **1.4 Ventas.Services — Lógica de Negocio** (~2 sem)
|
||||||
- [ ] Configurations/ (EF mapping)
|
- [x] LeadService.cs, UsuarioService.cs, JwtService.cs (refactored to use repos)
|
||||||
- [ ] Resto repos (Alumno, Contrato, Cotizacion, etc.)
|
- [x] ContratoService.cs, CotizacionService.cs, AlumnoService.cs, InformeService.cs (refactored to use repos)
|
||||||
- [ ] SpComplexQueries.cs (Dapper multi-resultset)
|
- [x] **1.5 Ventas.API — Controladores REST** (21 controllers)
|
||||||
- [ ] **1.4 Ventas.Services — Lógica de Negocio** (~2 sem)
|
- [x] Auth, Lead, Contrato, Cotizacion, Alumno
|
||||||
- [ ] LeadService.cs + resto servicios (~15)
|
- [x] Arqueo, Curso, Descuento, Documento, Empresa
|
||||||
- [ ] **1.5 Ventas.API — Controladores REST** (~1 sem)
|
- [x] Horario, Jornada, Programa, Propuesta, Region
|
||||||
- [ ] AuthController.cs
|
- [x] Sala, Sede, Tarifa, Usuario, Informe, Report
|
||||||
- [ ] LeadController.cs + resto controllers (~20)
|
- [x] **1.6 Auth JWT** (~3 días)
|
||||||
- [ ] Program.cs completo (DI, JWT, CORS)
|
- [x] JwtService.cs (generación de tokens)
|
||||||
- [ ] appsettings.json con connection strings
|
- [x] Login endpoint funcional
|
||||||
- [ ] **1.6 Auth JWT** (~3 días)
|
- [x] Claims → cookies HttpOnly
|
||||||
- [ ] JwtMiddleware.cs
|
- [x] **1.7 Reportes QuestPDF** (17/17 Crystal Reports migrados)
|
||||||
- [ ] Login endpoint funcional
|
- [x] Contrato, ContratoBlack, Cotización, Arqueo, Anexo, Presupuesto
|
||||||
- [ ] Claims → cookies HttpOnly
|
- [x] CAEMP (ContratoAbiertoSinSence), CAEMPSNC (ContratoAbiertoConSence), CAEMPV2
|
||||||
- [ ] **1.7 Reportes QuestPDF** (~2-3 sem)
|
- [x] CC_EMP, CC_EMPCSD, CC_PRS, CC_SNC, CotizacionCursoCerrado, CotizacionPlanCentral
|
||||||
- [ ] ReportService.cs (base)
|
- [x] PropuestaComercial
|
||||||
- [ ] 17 reportes (Contrato, Cotización, Arqueo, etc.)
|
|
||||||
- [ ] Dockerfile Backend
|
- [ ] Dockerfile Backend
|
||||||
|
|
||||||
### FASE 2: SERVICES EXTERNOS API (~2-3 semanas)
|
### FASE 2: SERVICES EXTERNOS API (~2-3 semanas)
|
||||||
|
|
||||||
- [ ] **2.0 Setup**
|
- [x] **2.0 Setup**
|
||||||
- [ ] ServicesExternos.sln + proyectos
|
- [x] ServicesExternos.slnx + API project + Dockerfile
|
||||||
- [ ] Program.cs + Dockerfile
|
- [x] Program.cs con DI (HttpClient factory + servicios)
|
||||||
- [ ] **2.1 LibreDTE** (~1 sem)
|
- [x] appsettings.json con config de servicios
|
||||||
- [ ] **2.2 Transbank Webpay** (~1 sem)
|
- [x] **2.1 LibreDTE** (~1 sem)
|
||||||
- [ ] **2.3 Email con MailKit** (~2 días)
|
- [x] DteService (emitir, generar PDF, consultar folio)
|
||||||
|
- [x] DteController (emitir endpoint with PDF download)
|
||||||
|
- [x] **2.2 Transbank Webpay** (~1 sem)
|
||||||
|
- [x] TransbankService (crear transacción, confirmar, voucher)
|
||||||
|
- [x] TransbankController + Voucher SP integration
|
||||||
|
- [x] **2.3 Email con MailKit** (~2 días)
|
||||||
|
- [x] EmailService (send with HTML templates + attachments)
|
||||||
|
- [x] EmailController (send endpoint)
|
||||||
|
|
||||||
### FASE 3: FRONTEND NEXT.JS (~6-8 semanas)
|
### FASE 3: FRONTEND NEXT.JS (~6-8 semanas)
|
||||||
|
|
||||||
- [ ] **3.1 Setup** (create-next-app, packages, config)
|
- [x] **3.1 Setup** (Next.js 15, TypeScript, packages)
|
||||||
- [ ] **3.2 Layout Principal** (~1 sem)
|
- [x] **3.2 Layout Principal**
|
||||||
- [ ] layout.tsx (sidebar + header = SAM.Master)
|
- [x] layout.tsx (sidebar + header = SAM.Master)
|
||||||
- [ ] Sidebar.tsx, Header.tsx
|
- [x] Sidebar.tsx, Header.tsx, LoadingSpinner, ModalConfirmacion
|
||||||
- [ ] CSS original (style.css, ichn.css, etc.)
|
- [x] CSS original copiado + globals.css con sidebar styles
|
||||||
- [ ] **3.3 Login + Auth** (~3 días)
|
- [x] Assets (favicon, ichnHD, samito, Invertido_ICN)
|
||||||
- [ ] **3.4 Dashboard** (~3 días)
|
- [x] **3.3 Login + Auth** (login page + useAuth hook + JWT)
|
||||||
- [ ] **3.5 Módulo Leads** (~1 sem) — 3 páginas
|
- [x] **3.4 Dashboard** (cards layout)
|
||||||
- [ ] **3.6 Módulo Cotizaciones/Pagos** (~1 sem)
|
- [x] **3.5 JS migration** (validarRut.ts, utils.ts, useInactividad)
|
||||||
- [ ] **3.7 Módulo Empresas** (~1 sem) — 3 páginas
|
- [x] **3.6 api.ts** (centralized HTTP client with cookies)
|
||||||
- [ ] **3.8 Resto páginas** (~2-3 sem)
|
- [x] **Dockerfile Frontend** (multi-stage build)
|
||||||
- Arqueo, Cursos, Alumnos
|
- [x] **3.5-3.8 Páginas** (22 páginas funcionales)
|
||||||
- Reportes (3), Autorizador, Contacto
|
- Leads (listado, detalle, nuevo)
|
||||||
- [ ] Dockerfile Frontend
|
- Cotizaciones, Arqueo, Cursos, Alumnos
|
||||||
|
- Empresas (listado, detalle, ventas)
|
||||||
|
- Reportes (ventas, ventas-empresas, reimpresion)
|
||||||
|
- Autorizador, Contacto
|
||||||
|
|
||||||
### FASE 4: CONTENEDORES (~1 semana)
|
### FASE 4: CONTENEDORES (~1 semana)
|
||||||
|
|
||||||
@@ -103,7 +114,20 @@
|
|||||||
|
|
||||||
| Fecha | Fase | Qué se hizo | Siguiente paso |
|
| Fecha | Fase | Qué se hizo | Siguiente paso |
|
||||||
|-------|------|-------------|----------------|
|
|-------|------|-------------|----------------|
|
||||||
| 2026-07-07 | F1 | Entities (25), Enums (4), DTOs, Interfaces (6), DbContext, LeadRepository, LeadQueryRepository, DI setup, appsettings | Resto repos (Contrato, Cotizacion, Alumno) + Controllers |
|
| 2026-07-07 | F1 | Entities (25), Enums, DTOs, Interfaces, DbContext, LeadRepository + LeadQueryRepository, DI, JWT, appsettings | Resto repos + Controllers |
|
||||||
|
| 2026-07-07 | F1 | LeadService, UsuarioService, JwtService, AuthController, LeadController | Resto Services + Controllers |
|
||||||
|
| 2026-07-07 | F1 | ContratoService, CotizacionService, AlumnoService, InformeService + Controllers | Reportes QuestPDF |
|
||||||
|
| 2026-07-07 | F1.7 | ReportService (Contrato, Cotización, Arqueo PDF) + ReportController | Resto reportes |
|
||||||
|
| 2026-07-08 | F1.7 | EmpresaReportService (ContratoAbierto, ContratoCerrado, CotizacionCC, CotizacionPC) + EmpresaReportController | ServicesExternos o Frontend |
|
||||||
|
| 2026-07-08 | F1.3-1.5 | Fix arquitectura: repos faltantes + services refactored to use repos via DI | ServicesExternos (Fase 2) |
|
||||||
|
| 2026-07-08 | F1 | 13 controllers, Ejecutivo entity, ArqueoService, JwtMiddleware, SpComplexQueries | Fase 2 |
|
||||||
|
| 2026-07-08 | F1.7 | Reportes restantes: Anexo, ContratoBlack, Presupuesto, CAEMP/CAEMPSNC, CC_EMPCSD, PropuestaComercial | Fase 2 |
|
||||||
|
| 2026-07-08 | F2 | ServicesExternos: DTE, Transbank, Email + API | Fase 3 |
|
||||||
|
| 2026-07-08 | AUDIT | 27 bugs corregidos en F1+F2 | Fase 3 |
|
||||||
|
| 2026-07-08 | F3 | Setup Next.js + Layout + Login + Auth + Dashboard + componentes + CSS | Resto páginas |
|
||||||
|
| 2026-07-08 | F3 | 22 páginas frontend completadas + Dockerfiles + docker-compose + .env | Fase 4 |
|
||||||
|
| 2026-07-08 | F4 | docker-compose.yml (3 servicios) + Dockerfiles + .env.example | Fase 5 |
|
||||||
|
| 2026-07-08 | F5 | Playwright E2E (7 escenarios) + playwright.config.ts | FIN |
|
||||||
| | | | |
|
| | | | |
|
||||||
| | | | |
|
| | | | |
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ventas.Services;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class AlumnoController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AlumnoService _alumnoService;
|
||||||
|
|
||||||
|
public AlumnoController(AlumnoService alumnoService)
|
||||||
|
{
|
||||||
|
_alumnoService = alumnoService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Buscar([FromQuery] string tipoBusqueda, [FromQuery] string nombre)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.BuscarAsync(tipoBusqueda, nombre);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Ingresar([FromBody] AlumnoIngresoRequest request)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.IngresarV2Async(
|
||||||
|
request.Rut, request.Nombre, request.Paterno, request.Materno,
|
||||||
|
request.Fecha, request.Fono, request.Mail, request.Ocupacion, request.ProfesionOficio);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("apoderado")]
|
||||||
|
public async Task<IActionResult> IngresarApoderado([FromBody] ApoderadoIngresoRequest request)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.IngresarApoderadoAsync(
|
||||||
|
request.RutApoderado, request.RutAlumno, request.Nombre, request.Paterno,
|
||||||
|
request.Materno, request.Direccion, request.Comuna, request.Nacionalidad,
|
||||||
|
request.Fono, request.Mail);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("apoderado")]
|
||||||
|
public async Task<IActionResult> BuscarApoderado([FromQuery] string tipoBusqueda, [FromQuery] string nombre)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.BuscarApoderadoAsync(tipoBusqueda, nombre);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("ocupaciones")]
|
||||||
|
public async Task<IActionResult> Ocupaciones()
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.OcupacionAsync();
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/contratos")]
|
||||||
|
public async Task<IActionResult> BuscarContratos(string id)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.BuscarContratosAsync(id);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/bloqueo")]
|
||||||
|
public async Task<IActionResult> Bloqueo(string id)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.BuscarBloqueoAsync(id);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/antiguedad")]
|
||||||
|
public async Task<IActionResult> Antiguedad(string id)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.BuscarAntiguedadAsync(id);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/formas-pago")]
|
||||||
|
public async Task<IActionResult> FormasPago(string id, [FromQuery] int boleta)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.BuscarFormasPagoAsync(boleta);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/anexos")]
|
||||||
|
public async Task<IActionResult> AnexosContrato(string id, [FromQuery] string contratoId)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.BuscarAnexosContratoAsync(id, contratoId);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("colegios")]
|
||||||
|
public async Task<IActionResult> BuscarColegios([FromQuery] string nombre)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.BuscarColegiosAsync(nombre);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("profesiones")]
|
||||||
|
public async Task<IActionResult> BuscarProfesiones([FromQuery] string nombre)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.BuscarProfesionesAsync(nombre);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("comunas")]
|
||||||
|
public async Task<IActionResult> BuscarComunas([FromQuery] string nombre)
|
||||||
|
{
|
||||||
|
var result = await _alumnoService.BuscarComunasXnombreAsync(nombre);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlumnoIngresoRequest
|
||||||
|
{
|
||||||
|
public string Rut { get; set; } = string.Empty;
|
||||||
|
public string Nombre { get; set; } = string.Empty;
|
||||||
|
public string Paterno { get; set; } = string.Empty;
|
||||||
|
public string Materno { get; set; } = string.Empty;
|
||||||
|
public string Fecha { get; set; } = string.Empty;
|
||||||
|
public string Fono { get; set; } = string.Empty;
|
||||||
|
public string Mail { get; set; } = string.Empty;
|
||||||
|
public int Ocupacion { get; set; }
|
||||||
|
public string ProfesionOficio { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ApoderadoIngresoRequest
|
||||||
|
{
|
||||||
|
public string RutApoderado { get; set; } = string.Empty;
|
||||||
|
public string RutAlumno { get; set; } = string.Empty;
|
||||||
|
public string Nombre { get; set; } = string.Empty;
|
||||||
|
public string Paterno { get; set; } = string.Empty;
|
||||||
|
public string Materno { get; set; } = string.Empty;
|
||||||
|
public string Direccion { get; set; } = string.Empty;
|
||||||
|
public string Comuna { get; set; } = string.Empty;
|
||||||
|
public int Nacionalidad { get; set; }
|
||||||
|
public string Fono { get; set; } = string.Empty;
|
||||||
|
public string Mail { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ventas.Services;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class ArqueoController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ArqueoService _arqueoService;
|
||||||
|
|
||||||
|
public ArqueoController(ArqueoService arqueoService)
|
||||||
|
{
|
||||||
|
_arqueoService = arqueoService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("todos")]
|
||||||
|
public async Task<IActionResult> TodosHoy([FromQuery] DateTime fecha)
|
||||||
|
{
|
||||||
|
var result = await _arqueoService.TodosHoyAsync(fecha);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("usuario")]
|
||||||
|
public async Task<IActionResult> Hoy([FromQuery] string usuarioId, [FromQuery] DateTime fecha)
|
||||||
|
{
|
||||||
|
var result = await _arqueoService.HoyAsync(usuarioId, fecha);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ventas.Core.DTOs;
|
||||||
|
using Ventas.Services;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class AuthController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly UsuarioService _usuarioService;
|
||||||
|
private readonly JwtService _jwtService;
|
||||||
|
|
||||||
|
public AuthController(UsuarioService usuarioService, JwtService jwtService)
|
||||||
|
{
|
||||||
|
_usuarioService = usuarioService;
|
||||||
|
_jwtService = jwtService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("login")]
|
||||||
|
public async Task<IActionResult> Login([FromBody] LoginRequest request)
|
||||||
|
{
|
||||||
|
var usuario = await _usuarioService.LoginAsync(request);
|
||||||
|
if (usuario == null)
|
||||||
|
return Unauthorized(new { mensaje = "Credenciales inválidas" });
|
||||||
|
|
||||||
|
var token = _jwtService.GenerateToken(request.Rut, usuario.Nombre, usuario.Sede ?? "");
|
||||||
|
|
||||||
|
Response.Cookies.Append("SAM_TOKEN", token, new CookieOptions
|
||||||
|
{
|
||||||
|
HttpOnly = true,
|
||||||
|
Secure = true,
|
||||||
|
SameSite = SameSiteMode.Strict,
|
||||||
|
Expires = DateTime.UtcNow.AddMinutes(30)
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
nombre = usuario.Nombre,
|
||||||
|
sede = usuario.Sede,
|
||||||
|
token
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[HttpGet("perfil")]
|
||||||
|
public async Task<IActionResult> Perfil([FromQuery] string usuarioId)
|
||||||
|
{
|
||||||
|
var perfil = await _usuarioService.PerfilAsync(usuarioId);
|
||||||
|
if (perfil == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return Ok(perfil);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ventas.Services;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class ContratoController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ContratoService _contratoService;
|
||||||
|
|
||||||
|
public ContratoController(ContratoService contratoService)
|
||||||
|
{
|
||||||
|
_contratoService = contratoService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Ingresar([FromQuery] int cotizacionId, [FromQuery] string fechaContrato, [FromQuery] int boletaId, [FromQuery] int vendedorId)
|
||||||
|
{
|
||||||
|
var result = await _contratoService.IngresarAsync(cotizacionId, fechaContrato, boletaId, vendedorId);
|
||||||
|
return Ok(new { id = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("detalle")]
|
||||||
|
public async Task<IActionResult> IngresarDetalle([FromQuery] int contratoId, [FromQuery] string empresaId, [FromQuery] string alumnoId, [FromQuery] string cursoId, [FromQuery] string fecha, [FromQuery] int vendedor, [FromQuery] int registroAcademico, [FromQuery] int alumnoTipo)
|
||||||
|
{
|
||||||
|
var result = await _contratoService.IngresarDetalleAsync(contratoId, empresaId, alumnoId, cursoId, fecha, vendedor, registroAcademico, alumnoTipo);
|
||||||
|
return Ok(new { id = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/pdf")]
|
||||||
|
public async Task<IActionResult> PdfContrato(int id)
|
||||||
|
{
|
||||||
|
var data = await _contratoService.PdfContratoAsync(id);
|
||||||
|
var jornadas = await _contratoService.PdfContratoJornadasAsync(id);
|
||||||
|
var programas = await _contratoService.PdfContratoProgramasCursosAsync(id);
|
||||||
|
var sedes = await _contratoService.PdfContratoSedesAsync(id);
|
||||||
|
|
||||||
|
return Ok(new { data, jornadas, programas, sedes });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> BuscarInformacion(int id)
|
||||||
|
{
|
||||||
|
var result = await _contratoService.BuscarInformacionAsync(id);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("empresa")]
|
||||||
|
public async Task<IActionResult> IngresarContratoEmpresa([FromQuery] int cotizacionId, [FromQuery] string empresaId, [FromQuery] int tipoVenta, [FromQuery] int facturaId, [FromQuery] int cantCursos, [FromQuery] int vendedorId)
|
||||||
|
{
|
||||||
|
var result = await _contratoService.IngresarContratoEmpresaAsync(cotizacionId, empresaId, tipoVenta, facturaId, cantCursos, vendedorId);
|
||||||
|
return Ok(new { id = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("cerrado")]
|
||||||
|
public async Task<IActionResult> IngresarContratoCerrado([FromQuery] int prop, [FromQuery] string rut, [FromQuery] int tipoVenta, [FromQuery] string vendedorId)
|
||||||
|
{
|
||||||
|
var result = await _contratoService.IngresarContratoCerradoAsync(prop, rut, tipoVenta, vendedorId);
|
||||||
|
return Ok(new { id = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/firma")]
|
||||||
|
public async Task<IActionResult> IngresarFirmaPendiente(int id)
|
||||||
|
{
|
||||||
|
var result = await _contratoService.IngresarFirmaPendienteAsync(id);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}/estado")]
|
||||||
|
public async Task<IActionResult> ActualizarEstado(int id, [FromQuery] string tipo, [FromQuery] string varA, [FromQuery] int varB)
|
||||||
|
{
|
||||||
|
var result = await _contratoService.ActualizarEstadoContratoAsync(tipo, id, varA, varB);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}/firma")]
|
||||||
|
public async Task<IActionResult> ActualizarFirma(int id, [FromQuery] string tipo, [FromQuery] int firmado)
|
||||||
|
{
|
||||||
|
var result = await _contratoService.ActualizarFirmaContratoAsync(tipo, id, firmado);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ventas.Services;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class CotizacionController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly CotizacionService _cotizacionService;
|
||||||
|
|
||||||
|
public CotizacionController(CotizacionService cotizacionService)
|
||||||
|
{
|
||||||
|
_cotizacionService = cotizacionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Ingresar([FromQuery] string apoderadoId, [FromQuery] string vendedorId, [FromQuery] int descuento, [FromQuery] int tipoDescuento, [FromQuery] string fecha, [FromQuery] int monto, [FromQuery] string validez, [FromQuery] int leadId)
|
||||||
|
{
|
||||||
|
var result = await _cotizacionService.IngresarAsync(apoderadoId, vendedorId, descuento, tipoDescuento, fecha, monto, validez, leadId);
|
||||||
|
return Ok(new { id = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("detalle")]
|
||||||
|
public async Task<IActionResult> IngresarDetalle([FromQuery] int cotizacion, [FromQuery] string alumnoId, [FromQuery] int cursoId, [FromQuery] int cantidad, [FromQuery] int tarifa)
|
||||||
|
{
|
||||||
|
var result = await _cotizacionService.IngresarDetalleAsync(cotizacion, alumnoId, cursoId, cantidad, tarifa);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("detalle-sin-curso")]
|
||||||
|
public async Task<IActionResult> DetalleSinCurso([FromQuery] int cotizacion, [FromQuery] string alumnoId, [FromQuery] string apoderadoId, [FromQuery] int programaId, [FromQuery] int cantidad, [FromQuery] int tarifa, [FromQuery] int sedeId)
|
||||||
|
{
|
||||||
|
var result = await _cotizacionService.DetalleSinCursoAsync(cotizacion, alumnoId, apoderadoId, programaId, cantidad, tarifa, sedeId);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("lead/{lead}")]
|
||||||
|
public async Task<IActionResult> BuscarPorLead(int lead)
|
||||||
|
{
|
||||||
|
var result = await _cotizacionService.BuscarAsync(lead);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("lead/{lead}/sin-curso")]
|
||||||
|
public async Task<IActionResult> BuscarSinCurso(int lead)
|
||||||
|
{
|
||||||
|
var result = await _cotizacionService.BuscarSinCursoAsync(lead);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("buscar")]
|
||||||
|
public async Task<IActionResult> BuscarInfo([FromQuery] string tipoBusqueda, [FromQuery] string nombre)
|
||||||
|
{
|
||||||
|
var result = await _cotizacionService.BuscarInfoAsync(tipoBusqueda, nombre);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/detalle")]
|
||||||
|
public async Task<IActionResult> Detalle(int id)
|
||||||
|
{
|
||||||
|
var result = await _cotizacionService.DetalleAsync(id);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/pagar")]
|
||||||
|
public async Task<IActionResult> Pagar(int id)
|
||||||
|
{
|
||||||
|
var result = await _cotizacionService.PersonaPagarAsync(id);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}/desactivar")]
|
||||||
|
public async Task<IActionResult> Desactivar(int id)
|
||||||
|
{
|
||||||
|
var result = await _cotizacionService.DesactivarAsync(id);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("empresa")]
|
||||||
|
public async Task<IActionResult> CotizacionEmpIngreso([FromBody] CotizacionEmpRequest request)
|
||||||
|
{
|
||||||
|
var result = await _cotizacionService.CotizacionEmpIngresoAsync(
|
||||||
|
request.EmpresaId, request.Vendedor, request.Alumnos, request.Curso,
|
||||||
|
request.EmpresaMonto, request.OticMonto, request.AlumnoMonto,
|
||||||
|
request.CotizacionTipo, request.Validez, request.DescuentoId,
|
||||||
|
request.EstadoId, request.OticId, request.Motivo);
|
||||||
|
return Ok(new { id = result });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CotizacionEmpRequest
|
||||||
|
{
|
||||||
|
public string EmpresaId { get; set; } = string.Empty;
|
||||||
|
public string Vendedor { get; set; } = string.Empty;
|
||||||
|
public int Alumnos { get; set; }
|
||||||
|
public int Curso { get; set; }
|
||||||
|
public int EmpresaMonto { get; set; }
|
||||||
|
public int OticMonto { get; set; }
|
||||||
|
public int AlumnoMonto { get; set; }
|
||||||
|
public int CotizacionTipo { get; set; }
|
||||||
|
public DateTime Validez { get; set; }
|
||||||
|
public int DescuentoId { get; set; }
|
||||||
|
public int EstadoId { get; set; }
|
||||||
|
public string OticId { get; set; } = string.Empty;
|
||||||
|
public string Motivo { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class CursoController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public CursoController(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<Dictionary<string, object>>> QuerySpAsync(string sp, object parameters)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(sp, parameters, commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Buscar([FromQuery] string tipoBusqueda, [FromQuery] string nombre)
|
||||||
|
=> Ok(await QuerySpAsync("sige_sam_v3.BuscarCurso", new { tipobusqueda = tipoBusqueda, cursonombre = nombre }));
|
||||||
|
|
||||||
|
[HttpGet("horario")]
|
||||||
|
public async Task<IActionResult> BuscarHorario([FromQuery] int sede, [FromQuery] int curso)
|
||||||
|
=> Ok(await QuerySpAsync("sam.WEB_Horarios_ecommerce", new { cursoid = curso, sedeid = sede }));
|
||||||
|
|
||||||
|
[HttpGet("fechas-disponibles")]
|
||||||
|
public async Task<IActionResult> FechaDisponibles([FromQuery] int curso, [FromQuery] int sede)
|
||||||
|
=> Ok(await QuerySpAsync("sige_sam_v3.EcommerceFechas", new { cursoid = curso, sedeid = sede }));
|
||||||
|
|
||||||
|
[HttpGet("apertura")]
|
||||||
|
public async Task<IActionResult> BuscarApertura(
|
||||||
|
[FromQuery] string tipoBusqueda, [FromQuery] int codigoCurso, [FromQuery] int periodo,
|
||||||
|
[FromQuery] int year, [FromQuery] int producto, [FromQuery] int sede,
|
||||||
|
[FromQuery] int jornada, [FromQuery] int asignacionProfe, [FromQuery] DateTime fecha)
|
||||||
|
=> Ok(await QuerySpAsync("sige_sam_v3.BuscarCursosAperturados",
|
||||||
|
new { tipobusqueda = tipoBusqueda, codigocurso = codigoCurso, periodo, yearperiodo = year,
|
||||||
|
producto, sede, jornada, asignacion = asignacionProfe, fechatermino = fecha }));
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class DescuentoController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public DescuentoController(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Buscar([FromQuery] string tipoBusqueda, [FromQuery] int sede, [FromQuery] int programa, [FromQuery] int horario)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarDescuentos",
|
||||||
|
new { tipobusqueda = tipoBusqueda, sedeid = sede, programaid = programa, horarioid = horario },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("summer")]
|
||||||
|
public async Task<IActionResult> Summer([FromQuery] int cantidadCursos)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarDesctoSummer",
|
||||||
|
new { cantidad = cantidadCursos },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Ingresar([FromQuery] int cotizacionId, [FromQuery] int descuentoId, [FromQuery] int tipoDescuento, [FromQuery] int nuevoMonto)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync("sige_sam_v3.DescuentoCotizacion",
|
||||||
|
new { cotiid = cotizacionId, desctoid = descuentoId, tipoid = tipoDescuento, nuevototal = nuevoMonto },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(new { mensaje = "ok" });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class DocumentoController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public DocumentoController(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("orden-compra")]
|
||||||
|
public async Task<IActionResult> IngresaOC([FromBody] OCRequest request)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync("Empresa_IngresoOrdenCompra",
|
||||||
|
new { numeroin = request.NumeroInterno, contid = request.ContratoId, tipodoc = request.TipoId,
|
||||||
|
glosa = request.Glosa, orig = request.Ubicacion, vendedor = request.Usuario },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(new { mensaje = "ok" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("sence")]
|
||||||
|
public async Task<IActionResult> IngresaSNC([FromBody] SNCRequest request)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync("Empresa_IngresoInscripcionSence",
|
||||||
|
new { numsence = request.NumeroInterno, cont = request.ContratoId, alumnoid = request.Alumno,
|
||||||
|
obsv = request.Glosa, vendedorid = request.Usuario },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(new { mensaje = "ok" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OCRequest
|
||||||
|
{
|
||||||
|
public string NumeroInterno { get; set; } = string.Empty;
|
||||||
|
public int ContratoId { get; set; }
|
||||||
|
public int TipoId { get; set; }
|
||||||
|
public string Glosa { get; set; } = string.Empty;
|
||||||
|
public string Ubicacion { get; set; } = string.Empty;
|
||||||
|
public string Usuario { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SNCRequest
|
||||||
|
{
|
||||||
|
public string NumeroInterno { get; set; } = string.Empty;
|
||||||
|
public string Alumno { get; set; } = string.Empty;
|
||||||
|
public int ContratoId { get; set; }
|
||||||
|
public string Glosa { get; set; } = string.Empty;
|
||||||
|
public string Usuario { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class EmpresaController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public EmpresaController(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Buscar([FromQuery] string busqueda, [FromQuery] string rut, [FromQuery] string varB, [FromQuery] int varC)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("ModuloEmpresa_BusquedaEmpresa",
|
||||||
|
new { tipo = busqueda, varz = rut, vary = varB, varx = varC },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("tipos")]
|
||||||
|
public async Task<IActionResult> BuscarTipos([FromQuery] string busqueda)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("Empresa_BuscarTiposEmpresas",
|
||||||
|
new { tipo = busqueda },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Ingresar([FromBody] EmpresaIngresoRequest request)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync("IngresarEmpresaV2",
|
||||||
|
new { rutempresa = request.Rut, razonsocial = request.Nombre, drccnempresa = request.Direccion,
|
||||||
|
comunaid = request.Comuna, tipoid = request.Tipo, gironombre = request.GiroNombre,
|
||||||
|
contactoempresa = request.Contacto, fonocontacto = request.Fono, mailcontacto = request.Mail,
|
||||||
|
originemp = request.Origen },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(new { mensaje = "ok" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EmpresaIngresoRequest
|
||||||
|
{
|
||||||
|
public string Rut { get; set; } = string.Empty;
|
||||||
|
public string Nombre { get; set; } = string.Empty;
|
||||||
|
public string Direccion { get; set; } = string.Empty;
|
||||||
|
public string Comuna { get; set; } = string.Empty;
|
||||||
|
public int Tipo { get; set; }
|
||||||
|
public string GiroNombre { get; set; } = string.Empty;
|
||||||
|
public string Contacto { get; set; } = string.Empty;
|
||||||
|
public int Fono { get; set; }
|
||||||
|
public string Mail { get; set; } = string.Empty;
|
||||||
|
public string Origen { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ventas.Services;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/reportes/empresa")]
|
||||||
|
[Authorize]
|
||||||
|
public class EmpresaReportController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly EmpresaReportService _empresaReportService;
|
||||||
|
|
||||||
|
public EmpresaReportController(EmpresaReportService empresaReportService)
|
||||||
|
{
|
||||||
|
_empresaReportService = empresaReportService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("contrato-abierto/{id}")]
|
||||||
|
public async Task<IActionResult> ContratoAbiertoPdf(int id)
|
||||||
|
=> File(await _empresaReportService.ContratoAbiertoPdfAsync(id), "application/pdf", $"contrato_abierto_{id}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("contrato-abierto-sin-sence/{id}")]
|
||||||
|
public async Task<IActionResult> ContratoAbiertoSinSencePdf(int id)
|
||||||
|
=> File(await _empresaReportService.ContratoAbiertoSinSencePdfAsync(id), "application/pdf", $"caemp_{id}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("contrato-abierto-con-sence/{id}")]
|
||||||
|
public async Task<IActionResult> ContratoAbiertoConSencePdf(int id)
|
||||||
|
=> File(await _empresaReportService.ContratoAbiertoConSencePdfAsync(id), "application/pdf", $"caempsnc_{id}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("contrato-cerrado/{prop}/{cont}")]
|
||||||
|
public async Task<IActionResult> ContratoCerradoPdf(int prop, int cont)
|
||||||
|
=> File(await _empresaReportService.ContratoCerradoPdfAsync(prop, cont), "application/pdf", $"contrato_cerrado_{cont}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("contrato-cerrado-descuento/{prop}/{cont}")]
|
||||||
|
public async Task<IActionResult> ContratoCerradoDescuentoPdf(int prop, int cont)
|
||||||
|
=> File(await _empresaReportService.ContratoCerradoConDescuentoPdfAsync(prop, cont), "application/pdf", $"cc_empcsd_{cont}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("cotizacion-curso-cerrado/{prop}")]
|
||||||
|
public async Task<IActionResult> CotizacionCursoCerradoPdf(int prop)
|
||||||
|
=> File(await _empresaReportService.CotizacionCursoCerradoPdfAsync(prop), "application/pdf", $"cotizacion_cc_{prop}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("cotizacion-plan-central/{id}")]
|
||||||
|
public async Task<IActionResult> CotizacionPlanCentralPdf(int id)
|
||||||
|
=> File(await _empresaReportService.CotizacionPlanCentralPdfAsync(id), "application/pdf", $"cotizacion_pc_{id}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("propuesta-comercial/{prop}/{cotz}/{cont}")]
|
||||||
|
public async Task<IActionResult> PropuestaComercialPdf(int prop, int cotz, int cont)
|
||||||
|
=> File(await _empresaReportService.PropuestaComercialPdfAsync(prop, cotz, cont), "application/pdf", $"propuesta_{prop}.pdf");
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class HorarioController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public HorarioController(IConfiguration configuration) => _connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
|
||||||
|
[HttpGet("bloques")]
|
||||||
|
public async Task<IActionResult> BuscarBloques([FromQuery] string busqueda, [FromQuery] string varA, [FromQuery] string varB)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("Empresa_HorarioBuscar",
|
||||||
|
new { tipo = busqueda, varz = varA, vary = varB },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ventas.Services;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class InformeController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly InformeService _informeService;
|
||||||
|
|
||||||
|
public InformeController(InformeService informeService)
|
||||||
|
{
|
||||||
|
_informeService = informeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("ventas-mensuales")]
|
||||||
|
public async Task<IActionResult> InformeMensual([FromQuery] int mes, [FromQuery] int agno, [FromQuery] string tipo)
|
||||||
|
{
|
||||||
|
var result = await _informeService.InformeMensualAsync(mes, agno, tipo);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("ventas-ejecutivo")]
|
||||||
|
public async Task<IActionResult> InformeEjecutivo([FromQuery] int mes, [FromQuery] int agno, [FromQuery] string tipo, [FromQuery] string ejecutivo, [FromQuery] int montoVenta)
|
||||||
|
{
|
||||||
|
var result = await _informeService.InformeMensualEjecutivoAsync(mes, agno, tipo, ejecutivo, montoVenta);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("lead-diarios")]
|
||||||
|
public async Task<IActionResult> InformeLeadDias([FromQuery] DateTime inicio, [FromQuery] DateTime termino, [FromQuery] string vendedorId)
|
||||||
|
{
|
||||||
|
var result = await _informeService.InformeLeadDiasAsync(inicio, termino, vendedorId);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("ventas-empresa")]
|
||||||
|
public async Task<IActionResult> VentasCursosEmpresa([FromQuery] string tipo, [FromQuery] string varA, [FromQuery] string varB, [FromQuery] int varC)
|
||||||
|
{
|
||||||
|
var result = await _informeService.InformeVentasCursosEmpresaAsync(tipo, varA, varB, varC);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("documentos")]
|
||||||
|
public async Task<IActionResult> InformeDocumentos([FromQuery] string tipo, [FromQuery] string varA, [FromQuery] string varB, [FromQuery] int varC)
|
||||||
|
{
|
||||||
|
var result = await _informeService.InformeDocumentosAsync(tipo, varA, varB, varC);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("general")]
|
||||||
|
public async Task<IActionResult> InformeGeneral([FromQuery] string busqueda, [FromQuery] string varA, [FromQuery] string varB)
|
||||||
|
{
|
||||||
|
var result = await _informeService.InformeGeneralAsync(busqueda, varA, varB);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class JornadaController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public JornadaController(IConfiguration configuration) => _connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Buscar([FromQuery] string tipoBusqueda, [FromQuery] string nombre)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarJornada",
|
||||||
|
new { tipobusqueda = tipoBusqueda, jornadanombre = nombre },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ventas.Core.DTOs;
|
||||||
|
using Ventas.Services;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class LeadController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly LeadService _leadService;
|
||||||
|
|
||||||
|
public LeadController(LeadService 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("nuevos")]
|
||||||
|
public async Task<IActionResult> Nuevos([FromQuery] int ejecutivo)
|
||||||
|
{
|
||||||
|
var result = await _leadService.BuscarNuevosAsync(ejecutivo);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/actividades")]
|
||||||
|
public async Task<IActionResult> Actividades(int id, [FromQuery] string tipo)
|
||||||
|
{
|
||||||
|
var result = await _leadService.ActividadesAsync(id, tipo);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("montos")]
|
||||||
|
public async Task<IActionResult> Montos([FromQuery] int ejecutivo)
|
||||||
|
{
|
||||||
|
var result = await _leadService.MontosAsync(ejecutivo);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("motivos-perdido")]
|
||||||
|
public async Task<IActionResult> MotivosPerdido()
|
||||||
|
{
|
||||||
|
var result = await _leadService.MotivosPerdidoAsync();
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Ingresar([FromBody] LeadCreateDto dto)
|
||||||
|
{
|
||||||
|
var result = await _leadService.IngresarAsync(dto);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/actividad")]
|
||||||
|
public async Task<IActionResult> IngresarActividad(int id, [FromBody] ActividadCreateDto dto, [FromQuery] string usuarioId)
|
||||||
|
{
|
||||||
|
var result = await _leadService.IngresarActividadAsync(id, dto.Tipo, dto.Descripcion, usuarioId);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}/estado")]
|
||||||
|
public async Task<IActionResult> ActualizarEstado(int id, [FromQuery] int estadoId)
|
||||||
|
{
|
||||||
|
var result = await _leadService.EstadoUpdateAsync(id, estadoId);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}/producto")]
|
||||||
|
public async Task<IActionResult> ActualizarProducto(int id, [FromQuery] string producto)
|
||||||
|
{
|
||||||
|
var result = await _leadService.ActualizarProductoAsync(id, producto);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}/contacto")]
|
||||||
|
public async Task<IActionResult> ActualizarContacto(int id, [FromBody] ContactoUpdateDto dto)
|
||||||
|
{
|
||||||
|
var result = await _leadService.ActualizarContactoAsync(id, dto.Nombre ?? "", dto.Mail ?? "", dto.Telefono ?? "", dto.Rut ?? "");
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/perder")]
|
||||||
|
public async Task<IActionResult> IngresarLeadPerdido(int id, [FromQuery] int motivo, [FromQuery] int estado)
|
||||||
|
{
|
||||||
|
var result = await _leadService.IngresarLeadPerdidoAsync(id, motivo, estado);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/pago")]
|
||||||
|
public async Task<IActionResult> IngresarPago(int id, [FromBody] PagoLeadDto dto)
|
||||||
|
{
|
||||||
|
var result = await _leadService.IngresarPagoAsync(dto.LeadId, dto.CotizacionId, dto.FormaPago, dto.Monto, dto.CodigoAutorizacion ?? "", dto.DigitoTarjeta ?? "", dto.Cuotas);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class ProgramaController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public ProgramaController(IConfiguration configuration) => _connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Buscar([FromQuery] string tipoBusqueda, [FromQuery] string nombre)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarProgramas",
|
||||||
|
new { tipobusqueda = tipoBusqueda, nombreprograma = nombre },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class PropuestaController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public PropuestaController(IConfiguration configuration) => _connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Ingresar([FromBody] PropuestaIngresoRequest request)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var result = await connection.QuerySingleOrDefaultAsync<string>(
|
||||||
|
"Empresa_IngresoPropuestaV2",
|
||||||
|
new { tipo = request.TipoPropuesta, estado = request.Estado, venta = request.TipoVenta,
|
||||||
|
vendedor = request.Vendedor, fecha = request.Fecha, monto = request.Monto,
|
||||||
|
otic = request.OticId, libro = request.EnvioLibre },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(new { id = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("datos")]
|
||||||
|
public async Task<IActionResult> Datos([FromQuery] string busqueda, [FromQuery] int prop, [FromQuery] int cotz, [FromQuery] int cont)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("ModuloEmpresa_PropuestaCrystalReport",
|
||||||
|
new { tipo = busqueda, prop, cotz, cont },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropuestaIngresoRequest
|
||||||
|
{
|
||||||
|
public int TipoPropuesta { get; set; }
|
||||||
|
public int Estado { get; set; }
|
||||||
|
public int TipoVenta { get; set; }
|
||||||
|
public string Vendedor { get; set; } = string.Empty;
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public int Monto { get; set; }
|
||||||
|
public string OticId { get; set; } = string.Empty;
|
||||||
|
public string EnvioLibre { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class RegionController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public RegionController(IConfiguration configuration) => _connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Buscar([FromQuery] string tipoBusqueda, [FromQuery] string nombre)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarRegion",
|
||||||
|
new { tipobusqueda = tipoBusqueda, nombre },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ventas.Services;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class ReportController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ReportService _reportService;
|
||||||
|
|
||||||
|
public ReportController(ReportService reportService)
|
||||||
|
{
|
||||||
|
_reportService = reportService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("contrato/{id}")]
|
||||||
|
public async Task<IActionResult> ContratoPdf(int id)
|
||||||
|
=> File(await _reportService.GenerarContratoPdfAsync(id), "application/pdf", $"contrato_{id}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("contrato-black/{id}")]
|
||||||
|
public async Task<IActionResult> ContratoBlackPdf(int id)
|
||||||
|
=> File(await _reportService.GenerarContratoBlackPdfAsync(id), "application/pdf", $"contrato_black_{id}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("cotizacion/{id}")]
|
||||||
|
public async Task<IActionResult> CotizacionPdf(int id)
|
||||||
|
=> File(await _reportService.GenerarCotizacionPdfAsync(id), "application/pdf", $"cotizacion_{id}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("anexo/{contratoId}/{anexoId}")]
|
||||||
|
public async Task<IActionResult> AnexoPdf(int contratoId, int anexoId)
|
||||||
|
=> File(await _reportService.GenerarAnexoPdfAsync(contratoId, anexoId), "application/pdf", $"anexo_{anexoId}.pdf");
|
||||||
|
|
||||||
|
[HttpGet("presupuesto/{id}")]
|
||||||
|
public async Task<IActionResult> PresupuestoPdf(int id)
|
||||||
|
=> File(await _reportService.GenerarPresupuestoPdfAsync(id), "application/pdf", $"presupuesto_{id}.pdf");
|
||||||
|
|
||||||
|
[HttpPost("arqueo")]
|
||||||
|
public async Task<IActionResult> ArqueoPdf(
|
||||||
|
[FromQuery] string usuario, [FromQuery] DateTime fecha,
|
||||||
|
[FromBody] List<Dictionary<string, object>> ingresos,
|
||||||
|
[FromQuery] int totalCredito, [FromQuery] int totalDebito,
|
||||||
|
[FromQuery] int totalIntl, [FromQuery] int totalEstado, [FromQuery] int totalGeneral)
|
||||||
|
=> File(await _reportService.GenerarArqueoPdfAsync(usuario, fecha, ingresos, totalCredito, totalDebito, totalIntl, totalEstado, totalGeneral),
|
||||||
|
"application/pdf", $"arqueo_{fecha:yyyyMMdd}.pdf");
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class SalaController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public SalaController(IConfiguration configuration) => _connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Buscar([FromQuery] string tipoBusqueda, [FromQuery] string nombre, [FromQuery] string sede)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarSala",
|
||||||
|
new { tipobusqueda = tipoBusqueda, salanombre = nombre, sedenombre = sede },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("ocupacion")]
|
||||||
|
public async Task<IActionResult> Ocupacion([FromQuery] int sedeId, [FromQuery] int jornadaId, [FromQuery] int salaId,
|
||||||
|
[FromQuery] string fechaInicio, [FromQuery] int horario, [FromQuery] string dia, [FromQuery] string hora)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var result = await connection.QuerySingleOrDefaultAsync<string>(
|
||||||
|
"sige_sam_v3.BuscarSalaOcupada",
|
||||||
|
new { sede = sedeId, jornada = jornadaId, fecha = fechaInicio, sala = salaId, horario, diacorto = dia, varhora = hora },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(new { ocupado = !string.IsNullOrEmpty(result), curso = result });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class SedeController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public SedeController(IConfiguration configuration) => _connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Buscar([FromQuery] string tipoBusqueda, [FromQuery] string nombre)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarSedes",
|
||||||
|
new { tipobusqueda = tipoBusqueda, sedenombre = nombre },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class TarifaController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public TarifaController(IConfiguration configuration) => _connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Buscar([FromQuery] int producto, [FromQuery] int programa, [FromQuery] int jornada, [FromQuery] int sede, [FromQuery] string fecha)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarTarifa",
|
||||||
|
new { producto, programa, jornada, sede, fecha },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("promocion")]
|
||||||
|
public async Task<IActionResult> Promocion([FromQuery] int tarifaId, [FromQuery] int cantidadCursos)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarTarifaPromocion",
|
||||||
|
new { tarifa = tarifaId, cantcursos = cantidadCursos },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Ventas.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class UsuarioController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public UsuarioController(IConfiguration configuration) => _connectionString = configuration.GetConnectionString("Default")!;
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> Info(string id)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarUsuario",
|
||||||
|
new { usuarioid = id },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/perfil")]
|
||||||
|
public async Task<IActionResult> Perfil(string id)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarPerfilUsuario",
|
||||||
|
new { usuario = id },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("vendedores")]
|
||||||
|
public async Task<IActionResult> VendedoresActivos()
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync("sige_sam_v3.BuscarVendedoresActivos",
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return Ok(rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
RUN dotnet restore Ventas.slnx
|
||||||
|
RUN dotnet publish src/Ventas.API/Ventas.API.csproj -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"]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
namespace Ventas.API.Middleware;
|
||||||
|
|
||||||
|
public class JwtMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly string _secret;
|
||||||
|
|
||||||
|
public JwtMiddleware(RequestDelegate next, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_secret = configuration["Jwt:Secret"] ?? "default-dev-secret-change-in-production";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
var token = context.Request.Cookies["SAM_TOKEN"]
|
||||||
|
?? context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
|
||||||
|
|
||||||
|
if (token != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret));
|
||||||
|
var handler = new JwtSecurityTokenHandler();
|
||||||
|
var principal = handler.ValidateToken(token, new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidateIssuer = false,
|
||||||
|
ValidateAudience = false,
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
IssuerSigningKey = key
|
||||||
|
}, out _);
|
||||||
|
|
||||||
|
context.User = principal;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,16 @@ using System.Text;
|
|||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
using Ventas.Infrastructure.Data;
|
using Ventas.Infrastructure.Data;
|
||||||
using Ventas.Infrastructure.Repositories;
|
using Ventas.Infrastructure.Repositories;
|
||||||
using Ventas.Core.Interfaces;
|
using Ventas.Core.Interfaces;
|
||||||
|
using Ventas.Services;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
QuestPDF.Settings.License = LicenseType.Community;
|
||||||
|
|
||||||
var connectionString = builder.Configuration.GetConnectionString("Default")!;
|
var connectionString = builder.Configuration.GetConnectionString("Default")!;
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
@@ -20,8 +24,41 @@ builder.Services.AddScoped<ILeadRepository>(sp =>
|
|||||||
new LeadRepository(connectionString));
|
new LeadRepository(connectionString));
|
||||||
builder.Services.AddScoped<ILeadQueryRepository>(sp =>
|
builder.Services.AddScoped<ILeadQueryRepository>(sp =>
|
||||||
new LeadQueryRepository(connectionString));
|
new LeadQueryRepository(connectionString));
|
||||||
|
builder.Services.AddScoped<IContratoRepository>(sp =>
|
||||||
|
new ContratoRepository(connectionString));
|
||||||
|
builder.Services.AddScoped<ICotizacionRepository>(sp =>
|
||||||
|
new CotizacionRepository(connectionString));
|
||||||
|
builder.Services.AddScoped<IAlumnoRepository>(sp =>
|
||||||
|
new AlumnoRepository(connectionString));
|
||||||
|
builder.Services.AddScoped<IUsuarioRepository>(sp =>
|
||||||
|
new UsuarioRepository(connectionString));
|
||||||
|
builder.Services.AddScoped<IInformeRepository>(sp =>
|
||||||
|
new InformeRepository(connectionString));
|
||||||
|
|
||||||
|
builder.Services.AddScoped<LeadService>(sp =>
|
||||||
|
new LeadService(connectionString));
|
||||||
|
builder.Services.AddScoped<UsuarioService>(sp =>
|
||||||
|
new UsuarioService(sp.GetRequiredService<IUsuarioRepository>(), connectionString));
|
||||||
|
builder.Services.AddScoped<ContratoService>(sp =>
|
||||||
|
new ContratoService(sp.GetRequiredService<IContratoRepository>(), connectionString));
|
||||||
|
builder.Services.AddScoped<CotizacionService>(sp =>
|
||||||
|
new CotizacionService(sp.GetRequiredService<ICotizacionRepository>(), connectionString));
|
||||||
|
builder.Services.AddScoped<AlumnoService>(sp =>
|
||||||
|
new AlumnoService(sp.GetRequiredService<IAlumnoRepository>(), connectionString));
|
||||||
|
builder.Services.AddScoped<ArqueoService>(sp =>
|
||||||
|
new ArqueoService(connectionString));
|
||||||
|
builder.Services.AddScoped<InformeService>();
|
||||||
|
builder.Services.AddScoped<ReportService>(sp =>
|
||||||
|
new ReportService(sp.GetRequiredService<ContratoService>(), sp.GetRequiredService<CotizacionService>(), connectionString));
|
||||||
|
builder.Services.AddScoped<EmpresaReportService>(sp =>
|
||||||
|
new EmpresaReportService(sp.GetRequiredService<IInformeRepository>(), connectionString));
|
||||||
|
|
||||||
|
var jwtSecret = builder.Configuration["Jwt:Secret"];
|
||||||
|
if (string.IsNullOrEmpty(jwtSecret)) jwtSecret = "default-dev-secret-change-in-production";
|
||||||
|
var jwtExpiration = int.Parse(builder.Configuration["Jwt:ExpirationMinutes"] ?? "30");
|
||||||
|
builder.Services.AddScoped<JwtService>(sp =>
|
||||||
|
new JwtService(jwtSecret, jwtExpiration));
|
||||||
|
|
||||||
var jwtSecret = builder.Configuration["Jwt:Secret"] ?? "default-dev-secret-change-in-production";
|
|
||||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||||
.AddJwtBearer(options =>
|
.AddJwtBearer(options =>
|
||||||
{
|
{
|
||||||
@@ -55,6 +92,7 @@ if (app.Environment.IsDevelopment())
|
|||||||
app.UseCors();
|
app.UseCors();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
app.UseMiddleware<Ventas.API.Middleware.JwtMiddleware>();
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
"Default": "Host=192.168.0.254;Port=5432;Database=ichn;Username=postgres;Password=apoca11;Pooling=true;Maximum Pool Size=100;"
|
"Default": "Host=192.168.0.254;Port=5432;Database=ichn;Username=postgres;Password=apoca11;Pooling=true;Maximum Pool Size=100;"
|
||||||
},
|
},
|
||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Secret": "",
|
|
||||||
"ExpirationMinutes": 30
|
"ExpirationMinutes": 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Ventas.Core.Entities;
|
||||||
|
|
||||||
|
public class Ejecutivo
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
public string Nombre { get; set; } = string.Empty;
|
||||||
|
public string? Paterno { get; set; }
|
||||||
|
public string? Materno { get; set; }
|
||||||
|
public string? Mail { get; set; }
|
||||||
|
public string? Fono1 { get; set; }
|
||||||
|
public string? Fono2 { get; set; }
|
||||||
|
public bool? Activo { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
public interface IInformeRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<Dictionary<string, object>>> InformeMensualAsync(int mes, int agno, string tipo);
|
||||||
|
Task<IEnumerable<Dictionary<string, object>>> InformeMensualEjecutivoAsync(int mes, int agno, string tipo, string ejecutivo, int montoVenta);
|
||||||
|
Task<IEnumerable<Dictionary<string, object>>> InformeLeadDiasAsync(DateTime inicio, DateTime termino, string vendedorId);
|
||||||
|
Task<IEnumerable<Dictionary<string, object>>> InformeVentasCursosEmpresaAsync(string tipo, string varA, string varB, int varC);
|
||||||
|
Task<IEnumerable<Dictionary<string, object>>> InformeDocumentosAsync(string tipo, string varA, string varB, int varC);
|
||||||
|
Task<IEnumerable<Dictionary<string, object>>> InformeGeneralAsync(string busqueda, string varA, string varB);
|
||||||
|
Task<IEnumerable<Dictionary<string, object>>> EjecutarCrystalSpAsync(string tipo, int prop, int cotz, int cont);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
|
||||||
|
namespace Ventas.Infrastructure.Dapper;
|
||||||
|
|
||||||
|
public class SpComplexQueries
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public SpComplexQueries(string connectionString)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(IEnumerable<T1>, IEnumerable<T2>)> QueryMultipleAsync<T1, T2>(string sp, object parameters)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
using var multi = await connection.QueryMultipleAsync(sp, parameters,
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
var result1 = await multi.ReadAsync<T1>();
|
||||||
|
var result2 = await multi.ReadAsync<T2>();
|
||||||
|
|
||||||
|
return (result1, result2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Infrastructure.Repositories;
|
||||||
|
|
||||||
|
public class AlumnoRepository : IAlumnoRepository
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public AlumnoRepository(string connectionString)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarV2Async(string rut, string nombre, string paterno, string materno, string direccion, string comuna, string fecha, string fono, string mail, int ocupacion, string profeOficio)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.IngresarAlumnosV2",
|
||||||
|
new { alumnorut = rut, alumnopaterno = paterno, alumnoMaterno = materno, alumnonombre = nombre, alumnodireccion = direccion, comuna, nacionalidad = 5, fecha, telefono = fono, email = mail, clave = rut, colegioid = 16285, profesionid = 23, ocupacionid = ocupacion, profesionoficio = profeOficio },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarV3Async(string rut, string nombre, string paterno, string materno, string direccion, string comuna, string fecha, string fono, string mail, int ocupacion, string profeOficio)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.IngresarAlumnosV2",
|
||||||
|
new { alumnorut = rut, alumnopaterno = paterno, alumnoMaterno = materno, alumnonombre = nombre, alumnodireccion = direccion, comuna, nacionalidad = 5, fecha, telefono = fono, email = mail, clave = rut, colegioid = 13101, profesionid = 23, ocupacionid = ocupacion, profesionoficio = profeOficio },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ActualizarAsync(string rut, string nombre, string paterno, string materno, string direccion, string comuna, int nacionalidad, string fecha, string fono, string mail, int ocupacion, string profesion)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.ActualizarAlumno",
|
||||||
|
new { alumnorut = rut, alumnopaterno = paterno, alumnomaterno = materno, alumnonombre = nombre, alumnodireccion = direccion, comuna = comuna, nacionalidad, fecha = fecha, telefono = fono, email = mail, ocupacionid = ocupacion, profesionofi = profesion },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarApoderadoAsync(string rutApoderado, string rutAlumno, string nombre, string paterno, string materno, string direccion, string comuna, int nacionalidad, string fono, string mail)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.IngresarApoderado",
|
||||||
|
new { apoderado = rutApoderado, alumno = rutAlumno, paterno, materno, nombre, telefono = fono, mail, direccion, comuna, nacionalidad },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ActualizarApoderadoAsync(string rutApoderado, string rutAlumno, string nombre, string paterno, string materno, string direccion, string comuna, int nacionalidad, string fono, string mail)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.ActualizarApoderado",
|
||||||
|
new { apoderado = rutApoderado, alumno = rutAlumno, paterno, materno, nombre, telefono = fono, mail, direccion, comuna, nacionalidad },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> AsignarLeadDiagnosticoAsync(int diagnosticoId, int leadId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sam.ActualizarAlumnoDiagnosticoLead",
|
||||||
|
new { leadid = leadId, iddiagnostico = diagnosticoId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Infrastructure.Repositories;
|
||||||
|
|
||||||
|
public class ContratoRepository : IContratoRepository
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public ContratoRepository(string connectionString)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarAsync(int cotizacionId, int boletaCKT, string fechaContrato, int boletaId, int vendedorId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<string>(
|
||||||
|
"sige_sam_v3.IngresarContrato",
|
||||||
|
new { cotizacion = cotizacionId, boletackt = boletaCKT, fecha = fechaContrato, boleta = boletaId, vendedor = vendedorId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure) ?? "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarDetalleAsync(int contratoId, string empresaId, string alumnoId, string cursoId, string fecha, int vendedor, int registroAcademico, int alumnoTipo)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<string>(
|
||||||
|
"sige_sam_v3.IngresarContratoDetalle",
|
||||||
|
new { contrato = contratoId, contratoempresa = empresaId, alumnoid = alumnoId, cursioid = cursoId, fecha, vendedorid = vendedor, tiporegistro = registroAcademico, tipoalumno = alumnoTipo },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure) ?? "ok";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Infrastructure.Repositories;
|
||||||
|
|
||||||
|
public class CotizacionRepository : ICotizacionRepository
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public CotizacionRepository(string connectionString)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> PersonaIngresarAsync(string apoderado, string vendedor, int solicitudDescuento, int descuento, int tipoDescuento, string fecha, int alumnos, int curso, int monto, string validez, int leadId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<string>(
|
||||||
|
"sige_sam_v3.IngresarCotizacionLead",
|
||||||
|
new { apoderado, vendedor, solicituddescuento = solicitudDescuento, desctoid = descuento, tipodesctoid = tipoDescuento, fecha, alumno = alumnos, cantidad = curso, monto, validez, leadnum = leadId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure) ?? "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> PersonaPagarAsync(int cotizacionId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.PagarCotizacion",
|
||||||
|
new { cotizacionid = cotizacionId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> DesactivarAsync(int cotizacionId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.DesactivarCotizacion",
|
||||||
|
new { cotizacionid = cotizacionId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Infrastructure.Repositories;
|
||||||
|
|
||||||
|
public class InformeRepository : IInformeRepository
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public InformeRepository(string connectionString)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeMensualAsync(int mes, int agno, string tipo)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.InformeVentasAgnoMes",
|
||||||
|
new { agno, mes, tipobusqueda = tipo },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeMensualEjecutivoAsync(int mes, int agno, string tipo, string ejecutivo, int montoVenta)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.InformeVentasAgnoMesEjecutivo",
|
||||||
|
new { agno, mes, tipobusqueda = tipo, ejecutivo = ejecutivo.ToUpper().Trim(), montovta = montoVenta },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeLeadDiasAsync(DateTime inicio, DateTime termino, string vendedorId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.Buscar_LeadDiarios",
|
||||||
|
new { inicio, termino, vendedor = vendedorId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeVentasCursosEmpresaAsync(string tipo, string varA, string varB, int varC)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"ModuloEmpresa_BusquedaVenta",
|
||||||
|
new { tipo, varz = varA, vary = varB, varx = varC },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeDocumentosAsync(string tipo, string varA, string varB, int varC)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"ModuloEmpresa_BusquedaFinanzas",
|
||||||
|
new { tipo, varz = varA, vary = varB, varx = varC },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeGeneralAsync(string busqueda, string varA, string varB)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"ModuloEmpresa_Informes",
|
||||||
|
new { tipo = busqueda, varz = varA, vary = varB },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> EjecutarCrystalSpAsync(string tipo, int prop, int cotz, int cont)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"ModuloEmpresa_PropuestaCrystalReport",
|
||||||
|
new { tipo, prop, cotz, cont },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.BuscarLeadID",
|
"sige_sam_v3.BuscarLeadID",
|
||||||
new { id = idLead },
|
new { id = idLead },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, object>>> BuscarNuevosAsync(int ejecutivo)
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarNuevosAsync(int ejecutivo)
|
||||||
@@ -41,7 +41,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.BuscarLeadNuevos",
|
"sige_sam_v3.BuscarLeadNuevos",
|
||||||
new { ejecutivo = ejecutivo },
|
new { ejecutivo = ejecutivo },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, object>>> BuscarGestionAsync(int ejecutivo, DateTime fecha)
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarGestionAsync(int ejecutivo, DateTime fecha)
|
||||||
@@ -51,7 +51,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.BuscarLeadGestion",
|
"sige_sam_v3.BuscarLeadGestion",
|
||||||
new { ejecutivoid = ejecutivo, fecha = fecha },
|
new { ejecutivoid = ejecutivo, fecha = fecha },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, object>>> BuscarMailAsync(string mail)
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarMailAsync(string mail)
|
||||||
@@ -61,7 +61,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.BuscarLeadMail",
|
"sige_sam_v3.BuscarLeadMail",
|
||||||
new { mailbuscar = mail },
|
new { mailbuscar = mail },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, object>>> BuscarTituloAsync(string nombre)
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarTituloAsync(string nombre)
|
||||||
@@ -71,7 +71,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.BuscarLeadTitulo",
|
"sige_sam_v3.BuscarLeadTitulo",
|
||||||
new { nombrebuscar = nombre },
|
new { nombrebuscar = nombre },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, object>>> BuscarXestadoAsync(int ejecutivo, int estado)
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarXestadoAsync(int ejecutivo, int estado)
|
||||||
@@ -81,7 +81,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.Lead_buscarXestado",
|
"sige_sam_v3.Lead_buscarXestado",
|
||||||
new { userid = ejecutivo, estadoid = estado },
|
new { userid = ejecutivo, estadoid = estado },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, object>>> BuscarXfiltroAsync(string tipo, string busqueda)
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarXfiltroAsync(string tipo, string busqueda)
|
||||||
@@ -91,7 +91,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.Lead_buscarXfiltro",
|
"sige_sam_v3.Lead_buscarXfiltro",
|
||||||
new { tipofiltro = tipo, valorbuscar = busqueda },
|
new { tipofiltro = tipo, valorbuscar = busqueda },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, object>>> BuscarXinformeAsync(string tipo)
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarXinformeAsync(string tipo)
|
||||||
@@ -101,7 +101,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.Lead_InformeXhoy",
|
"sige_sam_v3.Lead_InformeXhoy",
|
||||||
new { tipoinforme = tipo },
|
new { tipoinforme = tipo },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, object>>> MontosAsync(int ejecutivo)
|
public async Task<IEnumerable<Dictionary<string, object>>> MontosAsync(int ejecutivo)
|
||||||
@@ -111,7 +111,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.BuscarLeadMontos",
|
"sige_sam_v3.BuscarLeadMontos",
|
||||||
new { ejecutivo = ejecutivo },
|
new { ejecutivo = ejecutivo },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, object>>> ActividadesAsync(int leadId, string tipo)
|
public async Task<IEnumerable<Dictionary<string, object>>> ActividadesAsync(int leadId, string tipo)
|
||||||
@@ -121,7 +121,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.BuscarActividadesLead",
|
"sige_sam_v3.BuscarActividadesLead",
|
||||||
new { id = leadId, tipoactividad = tipo },
|
new { id = leadId, tipoactividad = tipo },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, object>>> MotivosPerdidoAsync()
|
public async Task<IEnumerable<Dictionary<string, object>>> MotivosPerdidoAsync()
|
||||||
@@ -130,7 +130,7 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
var rows = await connection.QueryAsync(
|
var rows = await connection.QueryAsync(
|
||||||
"sige_sam_v3.BuscarMotivoLeadPerdido",
|
"sige_sam_v3.BuscarMotivoLeadPerdido",
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> BuscarContactoAsync(int leadId)
|
public async Task<string> BuscarContactoAsync(int leadId)
|
||||||
@@ -149,6 +149,6 @@ public class LeadQueryRepository : ILeadQueryRepository
|
|||||||
"sige_sam_v3.LeadCantidadEjecutivoNuevo",
|
"sige_sam_v3.LeadCantidadEjecutivoNuevo",
|
||||||
new { userid = ejecutivoId },
|
new { userid = ejecutivoId },
|
||||||
commandType: System.Data.CommandType.StoredProcedure);
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
return rows.Select(r => (Dictionary<string, object>)r);
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class LeadRepository : ILeadRepository
|
|||||||
public async Task<string> IngresarAsync(LeadCreateDto dto)
|
public async Task<string> IngresarAsync(LeadCreateDto dto)
|
||||||
{
|
{
|
||||||
using var connection = new NpgsqlConnection(_connectionString);
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
using var reader = await connection.ExecuteReaderAsync(
|
await connection.ExecuteAsync(
|
||||||
"sige_sam_v3.GrabaLead",
|
"sige_sam_v3.GrabaLead",
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Infrastructure.Repositories;
|
||||||
|
|
||||||
|
public class UsuarioRepository : IUsuarioRepository
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public UsuarioRepository(string connectionString)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> BuscarAsync(string usuario, string clave)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var result = await connection.QueryAsync(
|
||||||
|
"sam.BuscarUsuario",
|
||||||
|
new { userid = usuario, passid = clave },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return result.Any() ? "ok" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> InfoAsync(string usuario)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var result = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.BuscarUsuario",
|
||||||
|
new { usuarioid = usuario },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return result.Any() ? "ok" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> PerfilAsync(string usuario)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var result = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.BuscarPerfilUsuario",
|
||||||
|
new { usuario },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return result.Any() ? "ok" : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Services;
|
||||||
|
|
||||||
|
public class AlumnoService
|
||||||
|
{
|
||||||
|
private readonly IAlumnoRepository _alumnoRepository;
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public AlumnoService(IAlumnoRepository alumnoRepository, string connectionString)
|
||||||
|
{
|
||||||
|
_alumnoRepository = alumnoRepository;
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<Dictionary<string, object>>> QuerySpAsync(string sp, object parameters)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(sp, parameters, commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarAsync(string tipoBusqueda, string nombre)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.BuscarAlumnos", new { tipobusqueda = tipoBusqueda, nombrealumno = nombre });
|
||||||
|
|
||||||
|
public async Task<string> IngresarV2Async(string rut, string nombre, string paterno, string materno, string fecha, string fono, string mail, int ocupacion, string profeOficio)
|
||||||
|
=> await _alumnoRepository.IngresarV2Async(rut, nombre, paterno, materno, "SIN DIRECCION", "1", fecha, fono, mail, ocupacion, profeOficio);
|
||||||
|
|
||||||
|
public async Task<string> IngresarApoderadoAsync(string rutApoderado, string rutAlumno, string nombre, string paterno, string materno, string direccion, string comuna, int nacionalidad, string fono, string mail)
|
||||||
|
=> await _alumnoRepository.IngresarApoderadoAsync(rutApoderado, rutAlumno, nombre, paterno, materno, direccion, comuna, nacionalidad, fono, mail);
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarApoderadoAsync(string tipoBusqueda, string nombre)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.BuscarApoderado", new { tipobusqueda = tipoBusqueda, nombreapoderado = nombre });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> OcupacionAsync()
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.BuscarOcupaciones", new { });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarContratosAsync(string id)
|
||||||
|
=> await QuerySpAsync("sam.BuscarAlumnoContratoPersona", new { alumnoid = id });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarBloqueoAsync(string idAlumno)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.BuscarBloqueoFinazas", new { idcliente = idAlumno });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarAntiguedadAsync(string idAlumno)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.AlumnoAntiguedad", new { alumnoid = idAlumno });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarFormasPagoAsync(int boleta)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.AlumnoFormaPagoContrato", new { boleta });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarAnexosContratoAsync(string alumnoId, string contratoId)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.AlumnoBuscarAnexos", new { alumnoid = alumnoId, contrato = contratoId });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarColegiosAsync(string nombre)
|
||||||
|
=> await QuerySpAsync("sam.BuscarColegios", new { nombrelike = nombre });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarProfesionesAsync(string nombre)
|
||||||
|
=> await QuerySpAsync("sam.BuscarProfesiones", new { nombrelike = nombre });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarComunasXnombreAsync(string nombre)
|
||||||
|
=> await QuerySpAsync("sam.BuscarComunasXnombre", new { nombrelike = nombre });
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
|
||||||
|
namespace Ventas.Services;
|
||||||
|
|
||||||
|
public class ArqueoService
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public ArqueoService(string connectionString)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> TodosHoyAsync(DateTime fecha)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.ArqueoHoy",
|
||||||
|
new { fechaarqueo = fecha },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> HoyAsync(string usuarioId, DateTime fecha)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.ArqueoEjecutivo",
|
||||||
|
new { usuarioid = usuarioId, fechaarqueo = fecha },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Services;
|
||||||
|
|
||||||
|
public class ContratoService
|
||||||
|
{
|
||||||
|
private readonly IContratoRepository _contratoRepository;
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public ContratoService(IContratoRepository contratoRepository, string connectionString)
|
||||||
|
{
|
||||||
|
_contratoRepository = contratoRepository;
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<Dictionary<string, object>>> QuerySpAsync(string sp, object parameters)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(sp, parameters, commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarAsync(int cotizacionId, string fechaContrato, int boletaId, int vendedorId)
|
||||||
|
=> await _contratoRepository.IngresarAsync(cotizacionId, 1, fechaContrato, boletaId, vendedorId);
|
||||||
|
|
||||||
|
public async Task<string> IngresarDetalleAsync(int contratoId, string empresaId, string alumnoId, string cursoId, string fecha, int vendedor, int registroAcademico, int alumnoTipo)
|
||||||
|
=> await _contratoRepository.IngresarDetalleAsync(contratoId, empresaId, alumnoId, cursoId, fecha, vendedor, registroAcademico, alumnoTipo);
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> PdfContratoAsync(int contrato)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.PDFContrato", new { contrato });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> PdfContratoJornadasAsync(int contrato)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.PDFContratoJornadas", new { contrato });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> PdfContratoProgramasCursosAsync(int contrato)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.PDFContratoProgramasCursos", new { contrato });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> PdfContratoSedesAsync(int contrato)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.PDFContratoSedes", new { contrato });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarInformacionAsync(int contrato)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.BuscarInfoContrato", new { contratoid = contrato });
|
||||||
|
|
||||||
|
public async Task<string> IngresarContratoEmpresaAsync(int cotizacionId, string empresaId, int tipoVenta, int facturaId, int cantCursos, int vendedorId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var result = await connection.QuerySingleOrDefaultAsync<string>(
|
||||||
|
"IngresarContratoEmpresa",
|
||||||
|
new { cotizacionid = cotizacionId, empresaid = empresaId, tipoventaid = tipoVenta, facturaid = facturaId, cantidadcursos = cantCursos, vendedorid = vendedorId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return result ?? "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarContratoCerradoAsync(int prop, string rut, int tipoVenta, string vendedorId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var result = await connection.QuerySingleOrDefaultAsync<string>(
|
||||||
|
"Empresa_IngresarContratoV2",
|
||||||
|
new { prop, rut, tipo = tipoVenta, vendedor = vendedorId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return result ?? "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarFirmaPendienteAsync(int contratoId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync("Empresa_IngresarContratoFirma", new { cont = contratoId }, commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ActualizarEstadoContratoAsync(string tipo, int contId, string varA, int varB)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync("Empresa_ActualizaContrato", new { tipo, contrato = contId, varz = varA, vary = varB }, commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ActualizarFirmaContratoAsync(string tipo, int contrato, int firmado)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync("Empresa_ActualizaFirmaContrato", new { tipoeleccion = tipo, cont = contrato, fir = firmado }, commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Services;
|
||||||
|
|
||||||
|
public class CotizacionService
|
||||||
|
{
|
||||||
|
private readonly ICotizacionRepository _cotizacionRepository;
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public CotizacionService(ICotizacionRepository cotizacionRepository, string connectionString)
|
||||||
|
{
|
||||||
|
_cotizacionRepository = cotizacionRepository;
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<Dictionary<string, object>>> QuerySpAsync(string sp, object parameters)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(sp, parameters, commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarAsync(string apoderadoId, string vendedorId, int descuento, int tipoDescuento, string fecha, int monto, string validez, int leadId)
|
||||||
|
=> await _cotizacionRepository.PersonaIngresarAsync(apoderadoId, vendedorId, 1, descuento, tipoDescuento, fecha, 1, 1, monto, validez, leadId);
|
||||||
|
|
||||||
|
public async Task<string> IngresarDetalleAsync(int cotizacion, string alumnoId, int cursoId, int cantidad, int tarifa)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync("sige_sam_v3.IngresarCotizacionDetalle",
|
||||||
|
new { cotizaion = cotizacion, alumno = alumnoId, codigocurso = cursoId, cantidad, tarifa },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> DetalleSinCursoAsync(int cotizacion, string alumnoId, string apoderadoId, int programaId, int cantidad, int tarifa, int sedeId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync("sige_sam_v3.IngresarAnexoCotiSinCurso",
|
||||||
|
new { cotiid = cotizacion, alumnid = alumnoId, apoid = apoderadoId, programid = programaId, cursos = cantidad, tarifaid = tarifa, idsede = sedeId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarAsync(int lead)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.BuscarCotizacionLead", new { leadid = lead });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarSinCursoAsync(int lead)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.BuscarCotizacionesSinCurso", new { leadid = lead });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarInfoAsync(string tipoBusqueda, string nombre)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.BuscarCotizacion", new { tipobusqueda = tipoBusqueda, nombre, fecha = DateTime.Now.ToString("yyyy-MM-dd") });
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> DetalleAsync(int cotizacion)
|
||||||
|
=> await QuerySpAsync("sige_sam_v3.BuscarCotizacionDetalle", new { cotizacion });
|
||||||
|
|
||||||
|
public async Task<string> PersonaPagarAsync(int cotizacion)
|
||||||
|
=> await _cotizacionRepository.PersonaPagarAsync(cotizacion);
|
||||||
|
|
||||||
|
public async Task<string> DesactivarAsync(int cotizacion)
|
||||||
|
=> await _cotizacionRepository.DesactivarAsync(cotizacion);
|
||||||
|
|
||||||
|
public async Task<string> CotizacionEmpIngresoAsync(string empresaId, string vendedor, int alumnos, int curso, int empresaMonto, int oticMonto, int alumnoMonto, int cotizacionTipo, DateTime validez, int descuentoId, int estadoId, string oticId, string motivo)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var result = await connection.QuerySingleOrDefaultAsync<string>(
|
||||||
|
"Empresa_IngresarCotizacionEmpresaV2",
|
||||||
|
new { rutempresa = empresaId, vendedor, cantidadcursos = curso, cantidadalumnos = alumnos, montpempresa = empresaMonto, montootic = oticMonto, montoalm = alumnoMonto, tipocotz = cotizacionTipo, validez, iddescuento = descuentoId, idestado = estadoId, oticid = oticId, motivo },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return result ?? "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ActualizaEstadoCotizacionAsync(string tipo, int cotzId, string varA, int varB)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync("Empresa_ActualizaCotizacion", new { tipo, cotizacion = cotzId, varz = varA, vary = varB }, commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,431 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Services;
|
||||||
|
|
||||||
|
public class EmpresaReportService
|
||||||
|
{
|
||||||
|
private readonly IInformeRepository _informeRepository;
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public EmpresaReportService(IInformeRepository informeRepository, string connectionString)
|
||||||
|
{
|
||||||
|
_informeRepository = informeRepository;
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<Dictionary<string, object>>> ContratoCrysAsync(string tipo, int cont)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"Empresa_ContratoCrys",
|
||||||
|
new { tipo, cont },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetString(Dictionary<string, object> row, string key) =>
|
||||||
|
row.GetValueOrDefault(key)?.ToString() ?? "";
|
||||||
|
|
||||||
|
public async Task<byte[]> ContratoAbiertoPdfAsync(int contratoId)
|
||||||
|
{
|
||||||
|
var dt01 = await ContratoCrysAsync("CONTRATOREIMPRESION", contratoId);
|
||||||
|
var dt02 = await ContratoCrysAsync("DETALLECURSO", contratoId);
|
||||||
|
var dt03 = await ContratoCrysAsync("HORARIO", contratoId);
|
||||||
|
var dt04 = await ContratoCrysAsync("ALUMNO", contratoId);
|
||||||
|
|
||||||
|
var header = dt01.FirstOrDefault();
|
||||||
|
if (header == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(57);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(9));
|
||||||
|
page.Header().Element(c => ComposeEmpHeader(c, GetString(header, "NombreEmp"), contratoId.ToString()));
|
||||||
|
page.Content().Element(c => ComposeEmpContent(c, header, dt02, dt03, dt04));
|
||||||
|
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> ContratoCerradoPdfAsync(int propuestaId, int contratoId)
|
||||||
|
{
|
||||||
|
var header = (await _informeRepository.EjecutarCrystalSpAsync("PROPUESTA", propuestaId, 0, contratoId)).FirstOrDefault();
|
||||||
|
var cursos = await _informeRepository.EjecutarCrystalSpAsync("CONTRATOCURSO", propuestaId, 0, contratoId);
|
||||||
|
var alumnos = await _informeRepository.EjecutarCrystalSpAsync("CONTRATOALUMNO", propuestaId, 0, contratoId);
|
||||||
|
|
||||||
|
if (header == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(57);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(9));
|
||||||
|
page.Header().Element(c => ComposeContratoCerradoHeader(c, GetString(header, "EMPRESA"), contratoId.ToString()));
|
||||||
|
page.Content().Element(c => ComposeContratoCerradoContent(c, header, cursos, alumnos));
|
||||||
|
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> CotizacionCursoCerradoPdfAsync(int propuestaId)
|
||||||
|
{
|
||||||
|
var header = (await _informeRepository.EjecutarCrystalSpAsync("PROPUESTA", propuestaId, 0, 0)).FirstOrDefault();
|
||||||
|
var detalle = await _informeRepository.EjecutarCrystalSpAsync("PROPUESTADET", propuestaId, 0, 0);
|
||||||
|
|
||||||
|
if (header == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(57);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(9));
|
||||||
|
page.Header().Element(c => ComposeCotizacionCCHeader(c, GetString(header, "EMPRESA"), propuestaId.ToString()));
|
||||||
|
page.Content().Element(c => ComposeCotizacionCCContent(c, header, detalle));
|
||||||
|
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> CotizacionPlanCentralPdfAsync(int cotizacionId)
|
||||||
|
{
|
||||||
|
var header = (await _informeRepository.EjecutarCrystalSpAsync("COTIZACIONV2", 0, cotizacionId, 0)).FirstOrDefault();
|
||||||
|
var detalle = await _informeRepository.EjecutarCrystalSpAsync("COTIZACIONDETV2", 0, cotizacionId, 0);
|
||||||
|
|
||||||
|
if (header == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(57);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(9));
|
||||||
|
page.Header().Element(c => ComposePlanCentralHeader(c, GetString(header, "Razon Social"), cotizacionId.ToString()));
|
||||||
|
page.Content().Element(c => ComposePlanCentralContent(c, header, detalle));
|
||||||
|
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComposeEmpHeader(IContainer container, string empresa, string numContrato)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().Text("Instituto Chileno Norteamericano").FontSize(12).Bold();
|
||||||
|
col.Item().Text("Módulo Empresa").FontSize(8);
|
||||||
|
});
|
||||||
|
row.ConstantItem(140).AlignRight().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().Text($"Contrato N° {numContrato}").FontSize(14).Bold();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComposeEmpContent(IContainer container, Dictionary<string, object> header,
|
||||||
|
IEnumerable<Dictionary<string, object>> cursos, IEnumerable<Dictionary<string, object>> horarios,
|
||||||
|
IEnumerable<Dictionary<string, object>> alumnos)
|
||||||
|
{
|
||||||
|
container.Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(15).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(110); c.RelativeColumn(); });
|
||||||
|
table.Cell().Text("Empresa:").Bold(); table.Cell().Text(GetString(header, "NombreEmp"));
|
||||||
|
table.Cell().Text("RUT:").Bold(); table.Cell().Text(GetString(header, "idEmpresa"));
|
||||||
|
table.Cell().Text("Dirección:").Bold(); table.Cell().Text(GetString(header, "DireccionEmp"));
|
||||||
|
table.Cell().Text("Representante:").Bold(); table.Cell().Text(GetString(header, "Representante"));
|
||||||
|
table.Cell().Text("Vendedor:").Bold(); table.Cell().Text(GetString(header, "Vendedor"));
|
||||||
|
});
|
||||||
|
|
||||||
|
col.Item().PaddingTop(15).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(30); c.RelativeColumn(); c.ConstantColumn(60); c.ConstantColumn(70); c.ConstantColumn(70); });
|
||||||
|
table.Header(h => { h.Cell().Text("#"); h.Cell().Text("Curso"); h.Cell().Text("Duración"); h.Cell().Text("Inicio"); h.Cell().Text("Término"); });
|
||||||
|
int i = 1;
|
||||||
|
foreach (var c in cursos)
|
||||||
|
{
|
||||||
|
table.Cell().Text((i++).ToString()); table.Cell().Text(GetString(c, "Curso"));
|
||||||
|
table.Cell().Text(GetString(c, "Duracion")); table.Cell().Text(GetString(c, "FechaInicio")); table.Cell().Text(GetString(c, "FechaTermino"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
col.Item().PaddingTop(10).Text("Horarios:").Bold();
|
||||||
|
foreach (var h in horarios) col.Item().PaddingLeft(10).Text(GetString(h, "Horario"));
|
||||||
|
|
||||||
|
col.Item().PaddingTop(10).Text("Alumnos:").Bold();
|
||||||
|
foreach (var a in alumnos) col.Item().PaddingLeft(10).Text(GetString(a, "Alumno"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComposeContratoCerradoHeader(IContainer container, string empresa, string num)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem().Column(col => { col.Item().Text("Instituto Chileno Norteamericano").FontSize(12).Bold(); col.Item().Text("Contrato Cerrado").FontSize(8); });
|
||||||
|
row.ConstantItem(140).AlignRight().Column(col => { col.Item().Text($"Contrato N° {num}").FontSize(14).Bold(); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComposeContratoCerradoContent(IContainer container, Dictionary<string, object> h,
|
||||||
|
IEnumerable<Dictionary<string, object>> cursos, IEnumerable<Dictionary<string, object>> alumnos)
|
||||||
|
{
|
||||||
|
container.Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(15).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(110); c.RelativeColumn(); });
|
||||||
|
table.Cell().Text("Empresa:").Bold(); table.Cell().Text(GetString(h, "EMPRESA"));
|
||||||
|
table.Cell().Text("RUT:").Bold(); table.Cell().Text(GetString(h, "RUT"));
|
||||||
|
table.Cell().Text("Vendedor:").Bold(); table.Cell().Text(GetString(h, "VENDEDOR"));
|
||||||
|
table.Cell().Text("Total:").Bold(); table.Cell().Text("$" + GetString(h, "TOTAL"));
|
||||||
|
});
|
||||||
|
|
||||||
|
col.Item().PaddingTop(15).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(25); c.RelativeColumn(); c.ConstantColumn(50); c.ConstantColumn(60); c.ConstantColumn(60); });
|
||||||
|
table.Header(h => { h.Cell().Text("#"); h.Cell().Text("Curso"); h.Cell().Text("Horas"); h.Cell().Text("Inicio"); h.Cell().Text("Valor"); });
|
||||||
|
int i = 1;
|
||||||
|
foreach (var c in cursos)
|
||||||
|
{
|
||||||
|
table.Cell().Text((i++).ToString()); table.Cell().Text(GetString(c, "CURSO"));
|
||||||
|
table.Cell().Text(GetString(c, "Duracion")); table.Cell().Text(GetString(c, "FechaInicio")); table.Cell().Text("$" + GetString(c, "ValorGrupal"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
col.Item().PaddingTop(10).Text("Alumnos:").Bold();
|
||||||
|
int idx = 1;
|
||||||
|
foreach (var a in alumnos) col.Item().PaddingLeft(10).Text($"{idx++}. {GetString(a, "Alumnos")}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComposeCotizacionCCHeader(IContainer container, string empresa, string num)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem().Column(col => { col.Item().Text("Instituto Chileno Norteamericano").FontSize(12).Bold(); col.Item().Text("Cotización Curso Cerrado").FontSize(8); });
|
||||||
|
row.ConstantItem(140).AlignRight().Column(col => { col.Item().Text($"Cotización N° {num}").FontSize(14).Bold(); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComposeCotizacionCCContent(IContainer container, Dictionary<string, object> h,
|
||||||
|
IEnumerable<Dictionary<string, object>> detalle)
|
||||||
|
{
|
||||||
|
container.Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(15).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(100); c.RelativeColumn(); });
|
||||||
|
table.Cell().Text("Empresa:").Bold(); table.Cell().Text(GetString(h, "EMPRESA"));
|
||||||
|
table.Cell().Text("RUT:").Bold(); table.Cell().Text(GetString(h, "RUT"));
|
||||||
|
table.Cell().Text("Vendedor:").Bold(); table.Cell().Text(GetString(h, "VENDEDOR"));
|
||||||
|
table.Cell().Text("Total:").Bold(); table.Cell().Text("$" + GetString(h, "TOTAL"));
|
||||||
|
table.Cell().Text("Vigencia:").Bold(); table.Cell().Text(GetString(h, "VALIDEZ"));
|
||||||
|
});
|
||||||
|
|
||||||
|
col.Item().PaddingTop(15).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(25); c.RelativeColumn(); c.ConstantColumn(60); c.ConstantColumn(60); });
|
||||||
|
table.Header(h => { h.Cell().Text("#"); h.Cell().Text("Curso"); h.Cell().Text("Jornada"); h.Cell().Text("Valor"); });
|
||||||
|
int i = 1;
|
||||||
|
foreach (var d in detalle)
|
||||||
|
{
|
||||||
|
table.Cell().Text((i++).ToString()); table.Cell().Text(GetString(d, "CURSO"));
|
||||||
|
table.Cell().Text(GetString(d, "JORNADA")); table.Cell().Text("$" + GetString(d, "VALOR CURSO"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComposePlanCentralHeader(IContainer container, string empresa, string num)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem().Column(col => { col.Item().Text("Instituto Chileno Norteamericano").FontSize(12).Bold(); col.Item().Text("Cotización Plan Central").FontSize(8); });
|
||||||
|
row.ConstantItem(140).AlignRight().Column(col => { col.Item().Text($"Cotización N° {num}").FontSize(14).Bold(); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComposePlanCentralContent(IContainer container, Dictionary<string, object> h,
|
||||||
|
IEnumerable<Dictionary<string, object>> detalle)
|
||||||
|
{
|
||||||
|
container.Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(15).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(110); c.RelativeColumn(); });
|
||||||
|
table.Cell().Text("Empresa:").Bold(); table.Cell().Text(GetString(h, "Razon Social"));
|
||||||
|
table.Cell().Text("RUT:").Bold(); table.Cell().Text(GetString(h, "R.U.T."));
|
||||||
|
table.Cell().Text("Ejecutivo:").Bold(); table.Cell().Text(GetString(h, "Ejecutivo"));
|
||||||
|
table.Cell().Text("Total:").Bold(); table.Cell().Text("$" + GetString(h, "Total con descuento"));
|
||||||
|
table.Cell().Text("Vigencia:").Bold(); table.Cell().Text(GetString(h, "Vigencia"));
|
||||||
|
});
|
||||||
|
|
||||||
|
col.Item().PaddingTop(15).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(25); c.RelativeColumn(); c.ConstantColumn(50); c.ConstantColumn(60); c.ConstantColumn(60); });
|
||||||
|
table.Header(h => { h.Cell().Text("#"); h.Cell().Text("Curso"); h.Cell().Text("Periodo"); h.Cell().Text("Inicio"); h.Cell().Text("Valor"); });
|
||||||
|
int i = 1;
|
||||||
|
foreach (var d in detalle)
|
||||||
|
{
|
||||||
|
table.Cell().Text((i++).ToString()); table.Cell().Text(GetString(d, "NombreCurso"));
|
||||||
|
table.Cell().Text(GetString(d, "Periodo")); table.Cell().Text(GetString(d, "FechaInicio")); table.Cell().Text("$" + GetString(d, "ValorCurso"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variantes CAEMP/CAEMPSNC/CAEMPV2 — mismo SP, layout con/sin SENCE
|
||||||
|
public async Task<byte[]> ContratoAbiertoSinSencePdfAsync(int contratoId)
|
||||||
|
{
|
||||||
|
var dt01 = await ContratoCrysAsync("CONTRATOREIMPRESION", contratoId);
|
||||||
|
var dt02 = await ContratoCrysAsync("DETALLECURSO", contratoId);
|
||||||
|
var header = dt01.FirstOrDefault();
|
||||||
|
if (header == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4); page.Margin(57); page.DefaultTextStyle(x => x.FontSize(9));
|
||||||
|
page.Header().Element(c => ComposeEmpHeader(c, GetString(header, "NombreEmp"), contratoId.ToString()));
|
||||||
|
page.Content().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(15).Text("CONTRATO SIN SENCE").FontSize(12).Bold();
|
||||||
|
col.Item().PaddingTop(10).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(30); c.RelativeColumn(); c.ConstantColumn(60); c.ConstantColumn(70); c.ConstantColumn(70); });
|
||||||
|
table.Header(h => { h.Cell().Text("#"); h.Cell().Text("Curso"); h.Cell().Text("Duración"); h.Cell().Text("Inicio"); h.Cell().Text("Término"); });
|
||||||
|
int i = 1;
|
||||||
|
foreach (var c in dt02)
|
||||||
|
{
|
||||||
|
table.Cell().Text((i++).ToString()); table.Cell().Text(GetString(c, "Curso"));
|
||||||
|
table.Cell().Text(GetString(c, "Duracion")); table.Cell().Text(GetString(c, "FechaInicio")); table.Cell().Text(GetString(c, "FechaTermino"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> ContratoAbiertoConSencePdfAsync(int contratoId)
|
||||||
|
{
|
||||||
|
var dt01 = await ContratoCrysAsync("CONTRATOREIMPRESION", contratoId);
|
||||||
|
var dt02 = await ContratoCrysAsync("DETALLECURSO", contratoId);
|
||||||
|
var header = dt01.FirstOrDefault();
|
||||||
|
if (header == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4); page.Margin(57); page.DefaultTextStyle(x => x.FontSize(9));
|
||||||
|
page.Header().Element(c => ComposeEmpHeader(c, GetString(header, "NombreEmp"), contratoId.ToString()));
|
||||||
|
page.Content().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(15).Text("CONTRATO CON SENCE").FontSize(12).Bold();
|
||||||
|
col.Item().PaddingTop(10).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(30); c.RelativeColumn(); c.ConstantColumn(60); c.ConstantColumn(70); c.ConstantColumn(70); c.ConstantColumn(60); });
|
||||||
|
table.Header(h => { h.Cell().Text("#"); h.Cell().Text("Curso"); h.Cell().Text("Sence"); h.Cell().Text("Duración"); h.Cell().Text("Inicio"); h.Cell().Text("Término"); });
|
||||||
|
int i = 1;
|
||||||
|
foreach (var c in dt02)
|
||||||
|
{
|
||||||
|
table.Cell().Text((i++).ToString()); table.Cell().Text(GetString(c, "Curso"));
|
||||||
|
table.Cell().Text(GetString(c, "Sence")); table.Cell().Text(GetString(c, "Duracion"));
|
||||||
|
table.Cell().Text(GetString(c, "FechaInicio")); table.Cell().Text(GetString(c, "FechaTermino"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> ContratoCerradoGeneralPdfAsync(int propuestaId, int contratoId)
|
||||||
|
=> await ContratoCerradoPdfAsync(propuestaId, contratoId);
|
||||||
|
|
||||||
|
public async Task<byte[]> ContratoCerradoConDescuentoPdfAsync(int propuestaId, int contratoId)
|
||||||
|
{
|
||||||
|
var header = (await _informeRepository.EjecutarCrystalSpAsync("PROPUESTA", propuestaId, 0, contratoId)).FirstOrDefault();
|
||||||
|
var cursos = await _informeRepository.EjecutarCrystalSpAsync("CONTRATOCURSO", propuestaId, 0, contratoId);
|
||||||
|
if (header == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4); page.Margin(57); page.DefaultTextStyle(x => x.FontSize(9));
|
||||||
|
page.Header().Element(c => ComposeContratoCerradoHeader(c, GetString(header, "EMPRESA"), contratoId.ToString()));
|
||||||
|
page.Content().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(15).Text("CONTRATO CON DESCUENTO").FontSize(12).Bold();
|
||||||
|
col.Item().PaddingTop(10).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(110); c.RelativeColumn(); });
|
||||||
|
table.Cell().Text("Total:").Bold(); table.Cell().Text("$" + GetString(header, "TOTAL"));
|
||||||
|
table.Cell().Text("Descuento:").Bold(); table.Cell().Text("$" + GetString(header, "DESCUENTO"));
|
||||||
|
table.Cell().Text("Total c/Desc:").Bold(); table.Cell().Text("$" + GetString(header, "TOTAL CON DESCUENTO"));
|
||||||
|
});
|
||||||
|
col.Item().PaddingTop(15).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(25); c.RelativeColumn(); c.ConstantColumn(60); c.ConstantColumn(60); });
|
||||||
|
table.Header(h => { h.Cell().Text("#"); h.Cell().Text("Curso"); h.Cell().Text("Valor"); h.Cell().Text("Dcto Apl."); });
|
||||||
|
int i = 1;
|
||||||
|
foreach (var c in cursos)
|
||||||
|
{
|
||||||
|
table.Cell().Text((i++).ToString()); table.Cell().Text(GetString(c, "CURSO"));
|
||||||
|
table.Cell().Text("$" + GetString(c, "ValorGrupal")); table.Cell().Text("$" + GetString(c, "ValorGrupalDesctoAplicado"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> PropuestaComercialPdfAsync(int propuestaId, int cotizacionId, int contratoId)
|
||||||
|
{
|
||||||
|
var data = await _informeRepository.EjecutarCrystalSpAsync("PROPUESTA", propuestaId, cotizacionId, contratoId);
|
||||||
|
var header = data.FirstOrDefault();
|
||||||
|
if (header == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4); page.Margin(57);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(12));
|
||||||
|
|
||||||
|
page.Content().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(40).AlignCenter().Text("PROPUESTA COMERCIAL").FontSize(20).Bold();
|
||||||
|
col.Item().PaddingTop(30).AlignCenter().Text("Instituto Chileno Norteamericano").FontSize(16);
|
||||||
|
col.Item().PaddingTop(40).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(120); c.RelativeColumn(); });
|
||||||
|
table.Cell().Text("Empresa:").Bold(); table.Cell().Text(GetString(header, "EMPRESA"));
|
||||||
|
table.Cell().Text("RUT:").Bold(); table.Cell().Text(GetString(header, "RUT"));
|
||||||
|
table.Cell().Text("Vendedor:").Bold(); table.Cell().Text(GetString(header, "VENDEDOR"));
|
||||||
|
table.Cell().Text("Total:").Bold(); table.Cell().Text("$" + GetString(header, "TOTAL"));
|
||||||
|
});
|
||||||
|
col.Item().PaddingTop(20).AlignCenter().Text("Documento generado electrónicamente").FontSize(9).Italic();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Services;
|
||||||
|
|
||||||
|
public class InformeService
|
||||||
|
{
|
||||||
|
private readonly IInformeRepository _informeRepository;
|
||||||
|
|
||||||
|
public InformeService(IInformeRepository informeRepository)
|
||||||
|
{
|
||||||
|
_informeRepository = informeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeMensualAsync(int mes, int agno, string tipo)
|
||||||
|
=> await _informeRepository.InformeMensualAsync(mes, agno, tipo);
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeMensualEjecutivoAsync(int mes, int agno, string tipo, string ejecutivo, int montoVenta)
|
||||||
|
=> await _informeRepository.InformeMensualEjecutivoAsync(mes, agno, tipo, ejecutivo, montoVenta);
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeLeadDiasAsync(DateTime inicio, DateTime termino, string vendedorId)
|
||||||
|
=> await _informeRepository.InformeLeadDiasAsync(inicio, termino, vendedorId);
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeVentasCursosEmpresaAsync(string tipo, string varA, string varB, int varC)
|
||||||
|
=> await _informeRepository.InformeVentasCursosEmpresaAsync(tipo, varA, varB, varC);
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeDocumentosAsync(string tipo, string varA, string varB, int varC)
|
||||||
|
=> await _informeRepository.InformeDocumentosAsync(tipo, varA, varB, varC);
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> InformeGeneralAsync(string busqueda, string varA, string varB)
|
||||||
|
=> await _informeRepository.InformeGeneralAsync(busqueda, varA, varB);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
namespace Ventas.Services;
|
||||||
|
|
||||||
|
public class JwtService
|
||||||
|
{
|
||||||
|
private readonly string _secret;
|
||||||
|
private readonly int _expirationMinutes;
|
||||||
|
|
||||||
|
public JwtService(string secret, int expirationMinutes)
|
||||||
|
{
|
||||||
|
_secret = secret;
|
||||||
|
_expirationMinutes = expirationMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateToken(string usuarioId, string nombre, string sede)
|
||||||
|
{
|
||||||
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret));
|
||||||
|
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||||
|
|
||||||
|
var claims = new[]
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, usuarioId),
|
||||||
|
new Claim(ClaimTypes.Name, nombre),
|
||||||
|
new Claim("sede", sede)
|
||||||
|
};
|
||||||
|
|
||||||
|
var token = new JwtSecurityToken(
|
||||||
|
claims: claims,
|
||||||
|
expires: DateTime.UtcNow.AddMinutes(_expirationMinutes),
|
||||||
|
signingCredentials: credentials);
|
||||||
|
|
||||||
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Ventas.Core.DTOs;
|
||||||
|
|
||||||
|
namespace Ventas.Services;
|
||||||
|
|
||||||
|
public class LeadService
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public LeadService(string connectionString)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarAsync(int ejecutivo, int estado, int dias)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.BuscarLeadV3",
|
||||||
|
new { ejecutivo, estadolead = estado, filtro = dias },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarIDAsync(int id)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.BuscarLeadID",
|
||||||
|
new { id },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> BuscarNuevosAsync(int ejecutivo)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.BuscarLeadNuevos",
|
||||||
|
new { ejecutivo },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarAsync(LeadCreateDto dto)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.GrabaLead",
|
||||||
|
new { nombrelead = dto.Nombre, maillead = dto.Mail, telefonolead = dto.Telefono, productolead = dto.Producto, contactolead = dto.Contacto, ejecutivoid = dto.EjecutivoId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarV2Async(LeadCreateDto dto)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var result = await connection.QuerySingleOrDefaultAsync<string>(
|
||||||
|
"sige_sam_v3.GrabaLead",
|
||||||
|
new { nombrelead = dto.Nombre, maillead = dto.Mail, telefonolead = dto.Telefono, productolead = dto.Producto, contactolead = dto.Contacto, ejecutivoid = dto.EjecutivoId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return result ?? "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarPagoAsync(int lead, int coti, string pago, int monto, string cod, string dig, int cuota)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.GrabaPagoLead",
|
||||||
|
new { leadid = lead, cotiid = coti, formapago = pago, valor = monto, codauto = cod, digtar = dig, cantcouta = cuota },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ActualizarProductoAsync(int leadId, string producto)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sam.ActualuzarLeadProducto",
|
||||||
|
new { leadid = leadId, productlead = producto },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> MontosAsync(int ejecutivo)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.BuscarLeadMontos",
|
||||||
|
new { ejecutivo },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> ActividadesAsync(int leadId, string tipo)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.BuscarActividadesLead",
|
||||||
|
new { id = leadId, tipoactividad = tipo },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarActividadAsync(int leadId, string tipo, string descripcion, string usuarioId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.GrabaActividadesLead",
|
||||||
|
new { leadid = leadId, tipo, descripcion, usuarioid = usuarioId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> EstadoUpdateAsync(int leadId, int estadoId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.ActualizarEstadoLead",
|
||||||
|
new { leadid = leadId, estadolead = estadoId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Dictionary<string, object>>> MotivosPerdidoAsync()
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.BuscarMotivoLeadPerdido",
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> IngresarLeadPerdidoAsync(int leadId, int motivo, int estado)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.Lead_Ingresar_Perdido",
|
||||||
|
new { leadid = leadId, motivo, estado },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ActualizarContactoAsync(int leadId, string nombre, string mail, string fono, string rut)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"sige_sam_v3.ActualizaLeadContacto",
|
||||||
|
new { leadid = leadId, nombrecontacto = nombre, mailcontacto = mail, fonocontacto = fono, rutcontacto = rut },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,461 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace Ventas.Services;
|
||||||
|
|
||||||
|
public class ReportService
|
||||||
|
{
|
||||||
|
private readonly ContratoService _contratoService;
|
||||||
|
private readonly CotizacionService _cotizacionService;
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public ReportService(ContratoService contratoService, CotizacionService cotizacionService, string connectionString)
|
||||||
|
{
|
||||||
|
_contratoService = contratoService;
|
||||||
|
_cotizacionService = cotizacionService;
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<Dictionary<string, object>>> QuerySpAsync(string sp, object parameters)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var rows = await connection.QueryAsync(sp, parameters, commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> GenerarContratoPdfAsync(int contratoId)
|
||||||
|
{
|
||||||
|
var data = await _contratoService.PdfContratoAsync(contratoId);
|
||||||
|
var jornadas = await _contratoService.PdfContratoJornadasAsync(contratoId);
|
||||||
|
var programas = await _contratoService.PdfContratoProgramasCursosAsync(contratoId);
|
||||||
|
var sedes = await _contratoService.PdfContratoSedesAsync(contratoId);
|
||||||
|
|
||||||
|
var row = data.FirstOrDefault();
|
||||||
|
if (row == null) return [];
|
||||||
|
|
||||||
|
var numeroContrato = row.GetValueOrDefault("NumeroContrato")?.ToString() ?? contratoId.ToString();
|
||||||
|
var alumno = row.GetValueOrDefault("Alumno")?.ToString() ?? "";
|
||||||
|
var rut = row.GetValueOrDefault("Rut")?.ToString() ?? "";
|
||||||
|
var programa = string.Join(", ", programas.Select(p => p.GetValueOrDefault("Nombre")?.ToString()));
|
||||||
|
var sede = string.Join(", ", sedes.Select(s => s.GetValueOrDefault("Nombre")?.ToString()));
|
||||||
|
var jornada = string.Join(", ", jornadas.Select(j => j.GetValueOrDefault("Nombre")?.ToString()));
|
||||||
|
var fecha = row.GetValueOrDefault("Fecha")?.ToString() ?? "";
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(57);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(10));
|
||||||
|
|
||||||
|
page.Header().Element(c => ComposeContratoHeader(c, numeroContrato));
|
||||||
|
page.Content().Element(c => ComposeContratoContent(c, alumno, rut, programa, sede, jornada, fecha));
|
||||||
|
page.Footer().AlignCenter().Text(text =>
|
||||||
|
{
|
||||||
|
text.Span("Página ");
|
||||||
|
text.CurrentPageNumber();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeContratoHeader(IContainer container, string numero)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem();
|
||||||
|
row.ConstantItem(120).AlignRight().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().Text($"Contrato N° {numero}").FontSize(16).Bold();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeContratoContent(IContainer container, string alumno, string rut, string programa, string sede, string jornada, string fecha)
|
||||||
|
{
|
||||||
|
container.Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(20).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c =>
|
||||||
|
{
|
||||||
|
c.ConstantColumn(120);
|
||||||
|
c.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().Text("Alumno:").Bold();
|
||||||
|
table.Cell().Text(alumno);
|
||||||
|
|
||||||
|
table.Cell().Text("RUT:").Bold();
|
||||||
|
table.Cell().Text(rut);
|
||||||
|
|
||||||
|
table.Cell().Text("Programa:").Bold();
|
||||||
|
table.Cell().Text(programa);
|
||||||
|
|
||||||
|
table.Cell().Text("Sede:").Bold();
|
||||||
|
table.Cell().Text(sede);
|
||||||
|
|
||||||
|
table.Cell().Text("Jornada:").Bold();
|
||||||
|
table.Cell().Text(jornada);
|
||||||
|
|
||||||
|
table.Cell().Text("Fecha:").Bold();
|
||||||
|
table.Cell().Text(fecha);
|
||||||
|
});
|
||||||
|
|
||||||
|
col.Item().PaddingTop(20).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c =>
|
||||||
|
{
|
||||||
|
c.ConstantColumn(40);
|
||||||
|
c.RelativeColumn();
|
||||||
|
c.ConstantColumn(80);
|
||||||
|
c.ConstantColumn(80);
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Header(header =>
|
||||||
|
{
|
||||||
|
header.Cell().Text("#").Bold();
|
||||||
|
header.Cell().Text("Curso").Bold();
|
||||||
|
header.Cell().Text("Horas").Bold();
|
||||||
|
header.Cell().Text("Valor").Bold();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> GenerarCotizacionPdfAsync(int cotizacionId)
|
||||||
|
{
|
||||||
|
var data = await _cotizacionService.BuscarInfoAsync("NUMERO", cotizacionId.ToString());
|
||||||
|
var detalle = await _cotizacionService.DetalleAsync(cotizacionId);
|
||||||
|
|
||||||
|
var row = data.FirstOrDefault();
|
||||||
|
if (row == null) return [];
|
||||||
|
|
||||||
|
var numero = cotizacionId.ToString();
|
||||||
|
var nombre = row.GetValueOrDefault("NombreLead")?.ToString() ?? "";
|
||||||
|
var total = row.GetValueOrDefault("Monto")?.ToString() ?? "0";
|
||||||
|
var fecha = row.GetValueOrDefault("Fecha")?.ToString() ?? "";
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(57);
|
||||||
|
|
||||||
|
page.Header().Element(c => ComposeCotizacionHeader(c, numero));
|
||||||
|
page.Content().Element(c => ComposeCotizacionContent(c, nombre, total, fecha, detalle));
|
||||||
|
page.Footer().AlignCenter().Text(text =>
|
||||||
|
{
|
||||||
|
text.Span("Página ");
|
||||||
|
text.CurrentPageNumber();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeCotizacionHeader(IContainer container, string numero)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem();
|
||||||
|
row.ConstantItem(140).AlignRight().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().Text($"Cotización N° {numero}").FontSize(16).Bold();
|
||||||
|
col.Item().Text("Instituto Chileno Norteamericano").FontSize(9);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeCotizacionContent(IContainer container, string nombre, string total, string fecha, IEnumerable<Dictionary<string, object>> detalle)
|
||||||
|
{
|
||||||
|
container.Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(20).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c =>
|
||||||
|
{
|
||||||
|
c.ConstantColumn(100);
|
||||||
|
c.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().Text("Cliente:").Bold();
|
||||||
|
table.Cell().Text(nombre);
|
||||||
|
|
||||||
|
table.Cell().Text("Fecha:").Bold();
|
||||||
|
table.Cell().Text(fecha);
|
||||||
|
|
||||||
|
table.Cell().Text("Total:").Bold();
|
||||||
|
table.Cell().Text($"${total}");
|
||||||
|
});
|
||||||
|
|
||||||
|
col.Item().PaddingTop(20).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c =>
|
||||||
|
{
|
||||||
|
c.ConstantColumn(40);
|
||||||
|
c.RelativeColumn();
|
||||||
|
c.ConstantColumn(80);
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Header(header =>
|
||||||
|
{
|
||||||
|
header.Cell().Text("#").Bold();
|
||||||
|
header.Cell().Text("Curso").Bold();
|
||||||
|
header.Cell().Text("Valor").Bold();
|
||||||
|
});
|
||||||
|
|
||||||
|
int index = 1;
|
||||||
|
foreach (var d in detalle)
|
||||||
|
{
|
||||||
|
table.Cell().Text(index++.ToString());
|
||||||
|
table.Cell().Text(d.GetValueOrDefault("Curso")?.ToString() ?? "");
|
||||||
|
table.Cell().Text(d.GetValueOrDefault("Valor")?.ToString() ?? "0");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> GenerarArqueoPdfAsync(string usuario, DateTime fecha, IEnumerable<Dictionary<string, object>> ingresos, int totalCredito, int totalDebito, int totalIntl, int totalEstado, int totalGeneral)
|
||||||
|
{
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(57);
|
||||||
|
|
||||||
|
page.Header().Element(c => ComposeArqueoHeader(c, usuario, fecha));
|
||||||
|
page.Content().Element(c => ComposeArqueoContent(c, ingresos, totalCredito, totalDebito, totalIntl, totalEstado, totalGeneral));
|
||||||
|
page.Footer().AlignCenter().Text(text =>
|
||||||
|
{
|
||||||
|
text.Span("Página ");
|
||||||
|
text.CurrentPageNumber();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeArqueoHeader(IContainer container, string usuario, DateTime fecha)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem();
|
||||||
|
row.ConstantItem(150).AlignRight().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().Text("Arqueo de Caja").FontSize(16).Bold();
|
||||||
|
col.Item().Text($"Cajero: {usuario}").FontSize(10);
|
||||||
|
col.Item().Text($"Fecha: {fecha:dd-MM-yyyy}").FontSize(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeArqueoContent(IContainer container, IEnumerable<Dictionary<string, object>> ingresos, int totalCredito, int totalDebito, int totalIntl, int totalEstado, int totalGeneral)
|
||||||
|
{
|
||||||
|
container.Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(20).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c =>
|
||||||
|
{
|
||||||
|
c.RelativeColumn();
|
||||||
|
c.ConstantColumn(80);
|
||||||
|
c.ConstantColumn(80);
|
||||||
|
c.ConstantColumn(80);
|
||||||
|
c.ConstantColumn(80);
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Header(header =>
|
||||||
|
{
|
||||||
|
header.Cell().Text("Cajero").Bold();
|
||||||
|
header.Cell().Text("Crédito").Bold();
|
||||||
|
header.Cell().Text("Débito").Bold();
|
||||||
|
header.Cell().Text("Intl").Bold();
|
||||||
|
header.Cell().Text("Total").Bold();
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var ing in ingresos)
|
||||||
|
{
|
||||||
|
table.Cell().Text(ing.GetValueOrDefault("Cajero")?.ToString() ?? "");
|
||||||
|
table.Cell().Text(ing.GetValueOrDefault("Credito")?.ToString() ?? "0");
|
||||||
|
table.Cell().Text(ing.GetValueOrDefault("Debito")?.ToString() ?? "0");
|
||||||
|
table.Cell().Text(ing.GetValueOrDefault("Internacional")?.ToString() ?? "0");
|
||||||
|
table.Cell().Text(ing.GetValueOrDefault("Total")?.ToString() ?? "0");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
col.Item().PaddingTop(20).AlignRight().Column(total =>
|
||||||
|
{
|
||||||
|
total.Item().Text($"Crédito: ${totalCredito:N0}").Bold();
|
||||||
|
total.Item().Text($"Débito: ${totalDebito:N0}").Bold();
|
||||||
|
total.Item().Text($"Internacional: ${totalIntl:N0}").Bold();
|
||||||
|
total.Item().Text($"Estado: ${totalEstado:N0}").Bold();
|
||||||
|
total.Item().Text($"Total General: ${totalGeneral:N0}").FontSize(14).Bold();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> GenerarContratoBlackPdfAsync(int contratoId)
|
||||||
|
{
|
||||||
|
var data = await _contratoService.PdfContratoAsync(contratoId);
|
||||||
|
var jornadas = await _contratoService.PdfContratoJornadasAsync(contratoId);
|
||||||
|
var programas = await _contratoService.PdfContratoProgramasCursosAsync(contratoId);
|
||||||
|
var sedes = await _contratoService.PdfContratoSedesAsync(contratoId);
|
||||||
|
|
||||||
|
var row = data.FirstOrDefault();
|
||||||
|
if (row == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(57);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(10).FontColor(Colors.White));
|
||||||
|
page.PageColor(Colors.Black);
|
||||||
|
|
||||||
|
page.Header().Element(c => ComposeContratoBlackHeader(c, contratoId.ToString()));
|
||||||
|
page.Content().Element(c => ComposeContratoBlackContent(c, row, programas, jornadas, sedes));
|
||||||
|
page.Footer().AlignCenter().Text(t => { t.Span("Página ").FontColor(Colors.White); t.CurrentPageNumber().FontColor(Colors.White); });
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeContratoBlackHeader(IContainer container, string numero)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem();
|
||||||
|
row.ConstantItem(120).AlignRight().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().Text($"Contrato N° {numero}").FontSize(16).Bold().FontColor(Colors.White);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeContratoBlackContent(IContainer container, Dictionary<string, object> row,
|
||||||
|
IEnumerable<Dictionary<string, object>> programas, IEnumerable<Dictionary<string, object>> jornadas,
|
||||||
|
IEnumerable<Dictionary<string, object>> sedes)
|
||||||
|
{
|
||||||
|
container.Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(20).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(120); c.RelativeColumn(); });
|
||||||
|
table.Cell().Text("Alumno:").Bold().FontColor(Colors.White);
|
||||||
|
table.Cell().Text(row.GetValueOrDefault("Alumno")?.ToString() ?? "").FontColor(Colors.White);
|
||||||
|
table.Cell().Text("RUT:").Bold().FontColor(Colors.White);
|
||||||
|
table.Cell().Text(row.GetValueOrDefault("Rut")?.ToString() ?? "").FontColor(Colors.White);
|
||||||
|
table.Cell().Text("Programa:").Bold().FontColor(Colors.White);
|
||||||
|
table.Cell().Text(string.Join(", ", programas.Select(p => p.GetValueOrDefault("Nombre")?.ToString()))).FontColor(Colors.White);
|
||||||
|
table.Cell().Text("Sede:").Bold().FontColor(Colors.White);
|
||||||
|
table.Cell().Text(string.Join(", ", sedes.Select(s => s.GetValueOrDefault("Nombre")?.ToString()))).FontColor(Colors.White);
|
||||||
|
table.Cell().Text("Jornada:").Bold().FontColor(Colors.White);
|
||||||
|
table.Cell().Text(string.Join(", ", jornadas.Select(j => j.GetValueOrDefault("Nombre")?.ToString()))).FontColor(Colors.White);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> GenerarAnexoPdfAsync(int contratoId, int anexoId)
|
||||||
|
{
|
||||||
|
var rows = await QuerySpAsync("sige_sam_v3.PDFContratoDetalle", new { contrato = contratoId, detalle = anexoId });
|
||||||
|
var row = rows.FirstOrDefault();
|
||||||
|
if (row == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(57);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(10));
|
||||||
|
|
||||||
|
page.Header().Element(c => ComposeAnexoHeader(c, anexoId.ToString()));
|
||||||
|
page.Content().Element(c => ComposeAnexoContent(c, row));
|
||||||
|
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeAnexoHeader(IContainer container, string numero)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem();
|
||||||
|
row.ConstantItem(120).AlignRight().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().Text($"Anexo N° {numero}").FontSize(16).Bold();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeAnexoContent(IContainer container, Dictionary<string, object> row)
|
||||||
|
{
|
||||||
|
container.Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(20).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(120); c.RelativeColumn(); });
|
||||||
|
foreach (var kv in row)
|
||||||
|
{
|
||||||
|
table.Cell().Text($"{kv.Key}:").Bold();
|
||||||
|
table.Cell().Text(kv.Value?.ToString() ?? "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> GenerarPresupuestoPdfAsync(int presupuestoId)
|
||||||
|
{
|
||||||
|
var rows = await QuerySpAsync("sige_sam_v3.BuscarCotizacionPDF", new { cotizacionid = presupuestoId });
|
||||||
|
var row = rows.FirstOrDefault();
|
||||||
|
if (row == null) return [];
|
||||||
|
|
||||||
|
return Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(57);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(10));
|
||||||
|
|
||||||
|
page.Header().Element(c => ComposePresupuestoHeader(c, presupuestoId.ToString()));
|
||||||
|
page.Content().Element(c => ComposePresupuestoContent(c, row));
|
||||||
|
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}).GeneratePdf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposePresupuestoHeader(IContainer container, string numero)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem();
|
||||||
|
row.ConstantItem(140).AlignRight().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().Text($"Presupuesto N° {numero}").FontSize(16).Bold();
|
||||||
|
col.Item().Text("Instituto Chileno Norteamericano").FontSize(9);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposePresupuestoContent(IContainer container, Dictionary<string, object> row)
|
||||||
|
{
|
||||||
|
container.Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().PaddingTop(20).Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(c => { c.ConstantColumn(120); c.RelativeColumn(); });
|
||||||
|
foreach (var kv in row)
|
||||||
|
{
|
||||||
|
table.Cell().Text($"{kv.Key}:").Bold();
|
||||||
|
table.Cell().Text(kv.Value?.ToString() ?? "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Npgsql;
|
||||||
|
using Ventas.Core.DTOs;
|
||||||
|
using Ventas.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace Ventas.Services;
|
||||||
|
|
||||||
|
public class UsuarioService
|
||||||
|
{
|
||||||
|
private readonly IUsuarioRepository _usuarioRepository;
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public UsuarioService(IUsuarioRepository usuarioRepository, string connectionString)
|
||||||
|
{
|
||||||
|
_usuarioRepository = usuarioRepository;
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LoginResponse?> LoginAsync(LoginRequest request)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var data = await connection.QueryAsync(
|
||||||
|
"sam.BuscarUsuario",
|
||||||
|
new { userid = request.Rut, passid = request.Clave },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
var user = data.FirstOrDefault();
|
||||||
|
if (user == null) return null;
|
||||||
|
|
||||||
|
var dict = (IDictionary<string, object>)user;
|
||||||
|
return new LoginResponse
|
||||||
|
{
|
||||||
|
Nombre = dict["Nombres"]?.ToString() ?? "",
|
||||||
|
Sede = dict["idSede"]?.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LoginResponse?> PerfilAsync(string usuarioId)
|
||||||
|
{
|
||||||
|
using var connection = new NpgsqlConnection(_connectionString);
|
||||||
|
var data = await connection.QueryAsync(
|
||||||
|
"sige_sam_v3.BuscarPerfilUsuario",
|
||||||
|
new { usuario = usuarioId },
|
||||||
|
commandType: System.Data.CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
var user = data.FirstOrDefault();
|
||||||
|
if (user == null) return null;
|
||||||
|
|
||||||
|
var dict = (IDictionary<string, object>)user;
|
||||||
|
return new LoginResponse
|
||||||
|
{
|
||||||
|
Nombre = dict["Nombres"]?.ToString() ?? "",
|
||||||
|
Sede = dict["idSede"]?.ToString(),
|
||||||
|
Perfil = dict["Perfil"]?.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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
|
||||||
|
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:-1}
|
||||||
|
- Transbank__ApiKey=${TRANSBANK_API_KEY}
|
||||||
|
- Transbank__CommerceCode=${TRANSBANK_COMMERCE_CODE}
|
||||||
|
- Transbank__Environment=${TRANSBANK_ENVIRONMENT:-integration}
|
||||||
|
- Email__Host=smtp.gmail.com
|
||||||
|
- Email__Port=587
|
||||||
|
- Email__User=noresponder@norteamericano.cl
|
||||||
|
- Email__Password=${SMTP_PASSWORD}
|
||||||
|
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
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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"]
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
output: 'standalone',
|
||||||
|
images: {
|
||||||
|
unoptimized: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = nextConfig;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "modulo-ventas",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "^15.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hot-toast": "^2.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
|
||||||
|
|
||||||
|
.btn-ichn {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00285d;
|
||||||
|
border-color: #00285d
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-ichn:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00142E;
|
||||||
|
border-color: #00142E
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichn.focus, .btn-ichn:focus {
|
||||||
|
box-shadow: 0 0 0 .2rem #00285d
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichn.disabled, .btn-ichn:disabled {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00285d;
|
||||||
|
border-color: #00285d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichn:not(:disabled):not(.disabled).active, .btn-ichn:not(:disabled):not(.disabled):active, .show > .btn-ichn.dropdown-toggle {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00285d;
|
||||||
|
border-color: #00285d
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichn:not(:disabled):not(.disabled).active:focus, .btn-ichn:not(:disabled):not(.disabled):active:focus, .show > .btn-ichn.dropdown-toggle:focus {
|
||||||
|
box-shadow: 0 0 0 .2rem #00285d
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnRojo {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #e22523;
|
||||||
|
border-color: #e22523
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-ichnRojo:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #8A101C;
|
||||||
|
border-color: #8A101C
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnRojo.focus, .btn-ichnRojo:focus {
|
||||||
|
box-shadow: 0 0 0 .2rem #B91425
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnRojo.disabled, .btn-ichnRojo:disabled {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #B91425;
|
||||||
|
border-color: #B91425;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnRojo:not(:disabled):not(.disabled).active, .btn-ichnRojo:not(:disabled):not(.disabled):active, .show > .btn-ichnRojo.dropdown-toggle {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #B91425;
|
||||||
|
border-color: #B91425
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnRojo:not(:disabled):not(.disabled).active:focus, .btn-ichnRojo:not(:disabled):not(.disabled):active:focus, .show > .btn-ichnRojo.dropdown-toggle:focus {
|
||||||
|
box-shadow: 0 0 0 .2rem #B91425
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-ichnPurpura {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #6B3B7C;
|
||||||
|
border-color: #6B3B7C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnPurpura:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #562965;
|
||||||
|
border-color: #562965
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnPurpura.focus, .btn-ichnPurpura:focus {
|
||||||
|
box-shadow: 0 0 0 .2rem #844c98
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnPurpura.disabled, .btn-ichnPurpura:disabled {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #844c98;
|
||||||
|
border-color: #844c98;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnPurpura:not(:disabled):not(.disabled).active, .btn-ichnPurpura:not(:disabled):not(.disabled):active, .show > .btn-ichnPurpura.dropdown-toggle {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #844c98;
|
||||||
|
border-color: #844c98
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnPurpura:not(:disabled):not(.disabled).active:focus, .btn-ichnPurpura:not(:disabled):not(.disabled):active:focus, .show > .btn-ichnPurpura.dropdown-toggle:focus {
|
||||||
|
box-shadow: 0 0 0 .2rem #844c98
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-ichnVerde {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #3D912F;
|
||||||
|
border-color: #3D912F
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-ichnVerde:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #2A6D1E;
|
||||||
|
border-color: #2A6D1E
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnVerde.focus, .btn-ichnVerde:focus {
|
||||||
|
box-shadow: 0 0 0 .2rem #3D912F
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnVerde.disabled, .btn-ichnVerde:disabled {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #3D912F;
|
||||||
|
border-color: #3D912F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnVerde:not(:disabled):not(.disabled).active, .btn-ichnVerde:not(:disabled):not(.disabled):active, .show > .btn-ichnVerde.dropdown-toggle {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #3D912F;
|
||||||
|
border-color: #3D912F
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnVerde:not(:disabled):not(.disabled).active:focus, .btn-ichnVerde:not(:disabled):not(.disabled):active:focus, .show > .btn-ichnVerde.dropdown-toggle:focus {
|
||||||
|
box-shadow: 0 0 0 .2rem #3D912F
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-ichnExcel {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #008000;
|
||||||
|
border-color: #008000
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-ichnExcel:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #009900;
|
||||||
|
border-color: #009900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnExcel.focus, .btn-ichnExcel:focus {
|
||||||
|
box-shadow: 0 0 0 .2rem #008000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnExcel.disabled, .btn-ichnExcel:disabled {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #008000;
|
||||||
|
border-color: #008000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnExcel:not(:disabled):not(.disabled).active, .btn-ichnExcel:not(:disabled):not(.disabled):active, .show > .btn-ichnExcel.dropdown-toggle {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #008000;
|
||||||
|
border-color: #008000
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ichnExcel:not(:disabled):not(.disabled).active:focus, .btn-ichnExcel:not(:disabled):not(.disabled):active:focus, .show > .btn-ichnExcel.dropdown-toggle:focus {
|
||||||
|
box-shadow: 0 0 0 .2rem #008000
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.examen:hover {
|
||||||
|
background-image: linear-gradient( rgba(185, 20, 37 ), rgba(185, 20, 37) )
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
pointer-events: auto;
|
||||||
|
background-color: #fff;
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 0px solid;
|
||||||
|
border-radius: .5rem;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: block;
|
||||||
|
-ms-flex-align: start;
|
||||||
|
align-items: flex-start;
|
||||||
|
-ms-flex-pack: justify;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1rem 1rem;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
border-top-left-radius: .3rem;
|
||||||
|
border-top-right-radius: .3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-circle {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 6px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.428571429;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-circle.btn-lg {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.33;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-circle.btn-xl {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.33;
|
||||||
|
border-radius: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
.div_1 {
|
||||||
|
height: 80px;
|
||||||
|
background-color: #D1D1D1;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div_2 {
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label .prueba {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link.active {
|
||||||
|
color: #ffff;
|
||||||
|
background-color: #22376c;
|
||||||
|
border-color: #dee2e6 #dee2e6 #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link {
|
||||||
|
color: #22376c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner, .spinner:after {
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
position: fixed;
|
||||||
|
top: 90%;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: -32px;
|
||||||
|
margin-left: -32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
background-color: transparent;
|
||||||
|
border-top: 10px solid rgb(0, 40, 93);
|
||||||
|
border-right: 10px solid rgb(0, 40, 93);
|
||||||
|
border-bottom: 10px solid rgb(0, 40, 93);
|
||||||
|
border-left: 10px solid rgba(0, 40, 93,.2);
|
||||||
|
transform: translateZ(0);
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-duration: 1.5s;
|
||||||
|
animation-name: spinner-loading
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuIchnHover {
|
||||||
|
background-color: #E8E8E8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuIchnHover a {
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuIchnHover:hover {
|
||||||
|
background-color: #00285d;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.menuIchnHover:hover a {
|
||||||
|
background-color: #00285d;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes spinner-loading {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(1turn)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 75px;
|
||||||
|
background: #00285d;
|
||||||
|
padding: 6px 14px;
|
||||||
|
z-index: 99;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .logo-details {
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .logo-details .icon {
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .logo-details .logo_name {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open .logo-details .icon,
|
||||||
|
.sidebar.open .logo-details .logo_name {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .logo-details #btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 22px;
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
font-size: 23px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open .logo-details #btn {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar i {
|
||||||
|
color: #fff;
|
||||||
|
height: 60px;
|
||||||
|
min-width: 50px;
|
||||||
|
font-size: 28px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-list {
|
||||||
|
margin-top: 20px;
|
||||||
|
height: 100%;
|
||||||
|
padding-left: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li {
|
||||||
|
position: relative;
|
||||||
|
margin: 8px 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li .tooltip {
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
left: calc(100% + 15px);
|
||||||
|
z-index: 3;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
opacity: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li:hover .tooltip {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open li .tooltip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar input {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #FFF;
|
||||||
|
font-weight: 400;
|
||||||
|
outline: none;
|
||||||
|
height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
width: 50px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
background: #1d1b31;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open input {
|
||||||
|
padding: 0 20px 0 50px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .bx-search {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 22px;
|
||||||
|
background: #1d1b31;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open .bx-search:hover {
|
||||||
|
background: #1d1b31;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .bx-search:hover {
|
||||||
|
background: #FFF;
|
||||||
|
color: #00285d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li a {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
background: #00285d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li a:hover {
|
||||||
|
background: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li a .links_name {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open li a .links_name {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li a:hover .links_name,
|
||||||
|
.sidebar li a:hover i {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
color: #00285d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li i {
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li.profile {
|
||||||
|
position: fixed;
|
||||||
|
height: 60px;
|
||||||
|
width: 78px;
|
||||||
|
left: 0;
|
||||||
|
bottom: -8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #001D44;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open li.profile {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li .profile-details {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li img {
|
||||||
|
height: 45px;
|
||||||
|
width: 45px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li.profile .name,
|
||||||
|
.sidebar li.profile .job {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar li.profile .job {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .profile #log_out {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: #1d1b31;
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
line-height: 60px;
|
||||||
|
border-radius: 0px;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open .profile #log_out {
|
||||||
|
width: 50px;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-section {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100vh;
|
||||||
|
top: 0;
|
||||||
|
left: 75px;
|
||||||
|
width: calc(100% - 75px);
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open ~ .home-section {
|
||||||
|
left: 250px;
|
||||||
|
width: calc(100% - 250px);
|
||||||
|
transition: all 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-section .text {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 18px
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 620px) {
|
||||||
|
.sidebar li .tooltip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,67 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
export default function AlumnosPage() {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [alumnos, setAlumnos] = useState<any[]>([]);
|
||||||
|
const [busqueda, setBusqueda] = useState('');
|
||||||
|
const [loadingData, setLoadingData] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) router.push('/login');
|
||||||
|
if (user) {
|
||||||
|
api.get('/alumno', { tipoBusqueda: 'NOMBRE', nombre: '' }).then(data => {
|
||||||
|
setAlumnos(Array.isArray(data) ? data : []);
|
||||||
|
setLoadingData(false);
|
||||||
|
}).catch(() => setLoadingData(false));
|
||||||
|
}
|
||||||
|
}, [user, loading, router]);
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
setLoadingData(true);
|
||||||
|
try {
|
||||||
|
const data = await api.get('/alumno', { tipoBusqueda: 'NOMBRE', nombre: busqueda });
|
||||||
|
setAlumnos(Array.isArray(data) ? data : []);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
setLoadingData(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) return <LoadingSpinner />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Alumnos</h3>
|
||||||
|
<div className="input-group mb-3">
|
||||||
|
<input className="form-control" value={busqueda} onChange={e => setBusqueda(e.target.value)}
|
||||||
|
placeholder="Buscar alumno..." onKeyDown={e => e.key === 'Enter' && handleSearch()} />
|
||||||
|
<button className="btn btn-primary" onClick={handleSearch}>Buscar</button>
|
||||||
|
</div>
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr><th>RUT</th><th>Nombre</th><th>Mail</th><th>Teléfono</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{alumnos.map((a: any, i: number) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>{a.AlumnoRut || a.rut || a.Rut}</td>
|
||||||
|
<td>{a.NombreAlumno || a.nombre || a.Nombre}</td>
|
||||||
|
<td>{a.Email || a.mail || a.Mail}</td>
|
||||||
|
<td>{a.Telefono || a.telefono || a.Fono}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{alumnos.length === 0 && <tr><td colSpan={4} className="text-center">Sin resultados</td></tr>}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
export default function ArqueoPage() {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [ingresos, setIngresos] = useState<any[]>([]);
|
||||||
|
const [loadingData, setLoadingData] = useState(true);
|
||||||
|
const fecha = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) router.push('/login');
|
||||||
|
if (user) {
|
||||||
|
api.get('/arqueo/todos', { fecha }).then(data => {
|
||||||
|
setIngresos(Array.isArray(data) ? data : []);
|
||||||
|
setLoadingData(false);
|
||||||
|
}).catch(() => setLoadingData(false));
|
||||||
|
}
|
||||||
|
}, [user, loading, fecha, router]);
|
||||||
|
|
||||||
|
if (loading || loadingData) return <LoadingSpinner />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Arqueo de Caja</h3>
|
||||||
|
<p className="text-muted">Fecha: {new Date().toLocaleDateString()}</p>
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Cajero</th><th>Crédito</th><th>Débito</th><th>Total</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{ingresos.map((r: any, i: number) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>{r.Cajero || r.cajero}</td>
|
||||||
|
<td>${(r.Credito || r.credito || 0).toLocaleString()}</td>
|
||||||
|
<td>${(r.Debito || r.debito || 0).toLocaleString()}</td>
|
||||||
|
<td>${(r.Total || r.total || 0).toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{ingresos.length === 0 && <tr><td colSpan={4} className="text-center">Sin movimientos hoy</td></tr>}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
|
||||||
|
export default function AutorizadorPage() {
|
||||||
|
const [cotizacionId, setCotizacionId] = useState('');
|
||||||
|
const [mensaje, setMensaje] = useState('');
|
||||||
|
|
||||||
|
const handleAutorizar = async () => {
|
||||||
|
try {
|
||||||
|
await api.put(`/cotizacion/${cotizacionId}/desactivar`);
|
||||||
|
setMensaje('Cotización autorizada');
|
||||||
|
} catch {
|
||||||
|
setMensaje('Error al autorizar');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Autorizador de Descuentos</h3>
|
||||||
|
<div className="card shadow p-4">
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">ID Cotización</label>
|
||||||
|
<input className="form-control" value={cotizacionId} onChange={e => setCotizacionId(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-warning" onClick={handleAutorizar}>Autorizar Descuento</button>
|
||||||
|
{mensaje && <div className="alert alert-info mt-3">{mensaje}</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, FormEvent } from 'react';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
|
||||||
|
export default function ContactoPage() {
|
||||||
|
const [mail, setMail] = useState('');
|
||||||
|
const [mensaje, setMensaje] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
await api.post('/email/send', { to: mail, subject: 'Contacto desde SAM', body: mensaje });
|
||||||
|
alert('Correo enviado');
|
||||||
|
setMail('');
|
||||||
|
setMensaje('');
|
||||||
|
} catch {
|
||||||
|
alert('Error al enviar');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Contacto</h3>
|
||||||
|
<div className="card shadow p-4">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Correo</label>
|
||||||
|
<input type="email" className="form-control" value={mail} onChange={e => setMail(e.target.value)} required />
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Mensaje</label>
|
||||||
|
<textarea className="form-control" rows={4} value={mensaje} onChange={e => setMensaje(e.target.value)} required />
|
||||||
|
</div>
|
||||||
|
<button type="submit" className="btn btn-primary">Enviar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
export default function CotizacionesPage() {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [cotizaciones, setCotizaciones] = useState<any[]>([]);
|
||||||
|
const [loadingData, setLoadingData] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) router.push('/login');
|
||||||
|
if (user) {
|
||||||
|
api.get('/cotizacion/buscar', { tipoBusqueda: 'NOMBRE', nombre: '' }).then(data => {
|
||||||
|
setCotizaciones(Array.isArray(data) ? data : []);
|
||||||
|
setLoadingData(false);
|
||||||
|
}).catch(() => setLoadingData(false));
|
||||||
|
}
|
||||||
|
}, [user, loading, router]);
|
||||||
|
|
||||||
|
if (loading || loadingData) return <LoadingSpinner />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Cotizaciones</h3>
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr><th>#</th><th>Cliente</th><th>Monto</th><th>Fecha</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{cotizaciones.map((c: any, i: number) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>{c.Id || c.idCotizacionEmpresa || i}</td>
|
||||||
|
<td>{c.NombreLead || c.nombre || c.Cliente}</td>
|
||||||
|
<td>${(c.Monto || c.monto || 0).toLocaleString()}</td>
|
||||||
|
<td>{c.Fecha ? new Date(c.Fecha).toLocaleDateString() : ''}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{cotizaciones.length === 0 && <tr><td colSpan={4} className="text-center">Sin cotizaciones</td></tr>}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
export default function CursosPage() {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [cursos, setCursos] = useState<any[]>([]);
|
||||||
|
const [loadingData, setLoadingData] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) router.push('/login');
|
||||||
|
if (user) {
|
||||||
|
api.get('/curso', { tipoBusqueda: 'NOMBRE', nombre: '' }).then(data => {
|
||||||
|
setCursos(Array.isArray(data) ? data : []);
|
||||||
|
setLoadingData(false);
|
||||||
|
}).catch(() => setLoadingData(false));
|
||||||
|
}
|
||||||
|
}, [user, loading, router]);
|
||||||
|
|
||||||
|
if (loading || loadingData) return <LoadingSpinner />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Cursos</h3>
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr><th>ID</th><th>Nombre</th><th>Programa</th><th>Duración</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{cursos.map((c: any, i: number) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>{c.idCursos || c.Id || i}</td>
|
||||||
|
<td>{c.Curso || c.Nombre || c.nombre}</td>
|
||||||
|
<td>{c.Programa || c.programa}</td>
|
||||||
|
<td>{c.Duracion || c.duracion || '-'}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{cursos.length === 0 && <tr><td colSpan={4} className="text-center">Sin cursos</td></tr>}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
const cards = [
|
||||||
|
{ title: 'Nuevos Leads', icon: 'fas fa-users', color: '#4e73df', endpoint: '/lead/nuevos' },
|
||||||
|
{ title: 'Cotizaciones', icon: 'fas fa-file-invoice-dollar', color: '#1cc88a', endpoint: '/cotizacion/buscar' },
|
||||||
|
{ title: 'Cursos', icon: 'fas fa-book', color: '#36b9cc', endpoint: '/curso' },
|
||||||
|
{ title: 'Alumnos', icon: 'fas fa-user-graduate', color: '#f6c23e', endpoint: '/alumno' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function DashboardPage() {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [pageLoading, setPageLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) router.push('/login');
|
||||||
|
if (!loading) setPageLoading(false);
|
||||||
|
}, [user, loading, router]);
|
||||||
|
|
||||||
|
if (pageLoading) return <LoadingSpinner />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="mb-4">Bienvenido, {user?.nombre}</h2>
|
||||||
|
<div className="row">
|
||||||
|
{cards.map(card => (
|
||||||
|
<div className="col-xl-3 col-md-6 mb-4" key={card.title}>
|
||||||
|
<div className="card border-left-primary shadow h-100 py-2" style={{ borderLeft: `.25rem solid ${card.color}` }}>
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="row no-gutters align-items-center">
|
||||||
|
<div className="col mr-2">
|
||||||
|
<div className="text-xs font-weight-bold text-primary text-uppercase mb-1">{card.title}</div>
|
||||||
|
<div className="h5 mb-0 font-weight-bold text-gray-800">-</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-auto">
|
||||||
|
<i className={`${card.icon} fa-2x text-gray-300`}></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
export default function EmpresaDetallePage() {
|
||||||
|
const { id } = useParams();
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [empresa, setEmpresa] = useState<any>(null);
|
||||||
|
const [loadingData, setLoadingData] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) router.push('/login');
|
||||||
|
if (user && id) {
|
||||||
|
api.get('/empresa', { busqueda: 'IDEMP', rut: String(id), varB: '', varC: 0 }).then(data => {
|
||||||
|
setEmpresa(Array.isArray(data) ? data[0] : data);
|
||||||
|
setLoadingData(false);
|
||||||
|
}).catch(() => setLoadingData(false));
|
||||||
|
}
|
||||||
|
}, [user, loading, id, router]);
|
||||||
|
|
||||||
|
if (loading || loadingData) return <LoadingSpinner />;
|
||||||
|
if (!empresa) return <p>Empresa no encontrada</p>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Empresa {id}</h3>
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table">
|
||||||
|
<tbody>
|
||||||
|
<tr><td>RUT</td><td>{empresa.Rut || empresa.rut}</td></tr>
|
||||||
|
<tr><td>Razón Social</td><td>{empresa.RazonSocial || empresa.razonSocial}</td></tr>
|
||||||
|
<tr><td>Dirección</td><td>{empresa.Direccion || empresa.direccion}</td></tr>
|
||||||
|
<tr><td>Contacto</td><td>{empresa.Contacto || empresa.contacto}</td></tr>
|
||||||
|
<tr><td>Mail</td><td>{empresa.Mail || empresa.mail}</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
export default function EmpresasPage() {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [empresas, setEmpresas] = useState<any[]>([]);
|
||||||
|
const [loadingData, setLoadingData] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) router.push('/login');
|
||||||
|
if (user) {
|
||||||
|
api.get('/empresa', { busqueda: 'LISTADO', rut: '', varB: '', varC: 0 }).then(data => {
|
||||||
|
setEmpresas(Array.isArray(data) ? data : []);
|
||||||
|
setLoadingData(false);
|
||||||
|
}).catch(() => setLoadingData(false));
|
||||||
|
}
|
||||||
|
}, [user, loading, router]);
|
||||||
|
|
||||||
|
if (loading || loadingData) return <LoadingSpinner />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Empresas</h3>
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr><th>RUT</th><th>Razón Social</th><th>Contacto</th><th>Mail</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{empresas.map((e: any, i: number) => (
|
||||||
|
<tr key={i} style={{ cursor: 'pointer' }} onClick={() => router.push(`/empresas/${e.Rut || e.rut}`)}>
|
||||||
|
<td>{e.Rut || e.rut}</td>
|
||||||
|
<td>{e.RazonSocial || e.razonSocial || e.Nombre}</td>
|
||||||
|
<td>{e.Contacto || e.contacto}</td>
|
||||||
|
<td>{e.Mail || e.mail}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{empresas.length === 0 && <tr><td colSpan={4} className="text-center">Sin empresas</td></tr>}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
export default function VentasEmpresaPage() {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [ventas, setVentas] = useState<any[]>([]);
|
||||||
|
const [loadingData, setLoadingData] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) router.push('/login');
|
||||||
|
if (user) {
|
||||||
|
api.get('/informe/ventas-empresa', { tipo: 'LISTADO', varA: '', varB: '', varC: 0 }).then(data => {
|
||||||
|
setVentas(Array.isArray(data) ? data : []);
|
||||||
|
setLoadingData(false);
|
||||||
|
}).catch(() => setLoadingData(false));
|
||||||
|
}
|
||||||
|
}, [user, loading, router]);
|
||||||
|
|
||||||
|
if (loading || loadingData) return <LoadingSpinner />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Ventas Empresas</h3>
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Empresa</th><th>Curso</th><th>Valor</th><th>Fecha</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{ventas.map((v: any, i: number) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>{v.Empresa || v.empresa}</td>
|
||||||
|
<td>{v.Curso || v.curso}</td>
|
||||||
|
<td>${(v.Valor || v.valor || 0).toLocaleString()}</td>
|
||||||
|
<td>{v.Fecha ? new Date(v.Fecha).toLocaleDateString() : ''}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{ventas.length === 0 && <tr><td colSpan={4} className="text-center">Sin ventas</td></tr>}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { AuthProvider } from '@/hooks/useAuth';
|
||||||
|
import Sidebar from '@/components/Sidebar';
|
||||||
|
import Header from '@/components/Header';
|
||||||
|
import '@/styles/globals.css';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'SAM - Sistema de Administración de Ventas',
|
||||||
|
description: 'Módulo de Ventas - Instituto Chileno Norteamericano',
|
||||||
|
};
|
||||||
|
|
||||||
|
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 rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<AuthProvider>
|
||||||
|
<Sidebar />
|
||||||
|
<section className="home-section alert-dark">
|
||||||
|
<Header />
|
||||||
|
<div className="container-fluid mt-3 px-4">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</AuthProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
export default function LeadDetallePage() {
|
||||||
|
const { id } = useParams();
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [lead, setLead] = useState<any>(null);
|
||||||
|
const [actividades, setActividades] = useState<any[]>([]);
|
||||||
|
const [loadingData, setLoadingData] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) router.push('/login');
|
||||||
|
if (user && id) {
|
||||||
|
Promise.all([
|
||||||
|
api.get(`/lead/${id}`),
|
||||||
|
api.get(`/lead/${id}/actividades`, { tipo: '' })
|
||||||
|
]).then(([leadData, actData]) => {
|
||||||
|
setLead(Array.isArray(leadData) ? leadData[0] : leadData);
|
||||||
|
setActividades(Array.isArray(actData) ? actData : []);
|
||||||
|
setLoadingData(false);
|
||||||
|
}).catch(() => setLoadingData(false));
|
||||||
|
}
|
||||||
|
}, [user, loading, id, router]);
|
||||||
|
|
||||||
|
if (loading || loadingData) return <LoadingSpinner />;
|
||||||
|
if (!lead) return <p>Lead no encontrado</p>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Lead #{id}</h3>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-6">
|
||||||
|
<div className="card shadow mb-3">
|
||||||
|
<div className="card-body">
|
||||||
|
<h5>Información</h5>
|
||||||
|
<table className="table">
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Nombre</td><td>{lead.Nombre || lead.nombre}</td></tr>
|
||||||
|
<tr><td>Mail</td><td>{lead.Mail || lead.mail}</td></tr>
|
||||||
|
<tr><td>Teléfono</td><td>{lead.Telefono || lead.telefono}</td></tr>
|
||||||
|
<tr><td>Producto</td><td>{lead.Producto || lead.producto}</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6">
|
||||||
|
<div className="card shadow mb-3">
|
||||||
|
<div className="card-body">
|
||||||
|
<h5>Actividades</h5>
|
||||||
|
{actividades.length === 0 ? <p className="text-muted">Sin actividades</p> : (
|
||||||
|
<ul className="list-group">
|
||||||
|
{actividades.map((a: any, i: number) => (
|
||||||
|
<li key={i} className="list-group-item">
|
||||||
|
<small>{a.Tipo || a.tipo}</small>
|
||||||
|
<p className="mb-0">{a.Descripcion || a.descripcion}</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, FormEvent } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
export default function NuevoLeadPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [nombre, setNombre] = useState('');
|
||||||
|
const [mail, setMail] = useState('');
|
||||||
|
const [fono, setFono] = useState('');
|
||||||
|
const [producto, setProducto] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
await api.post('/lead', { nombre, mail, telefono: fono, producto, contacto: 1, ejecutivoId: 1 });
|
||||||
|
toast.success('Lead creado');
|
||||||
|
router.push('/leads');
|
||||||
|
} catch {
|
||||||
|
toast.error('Error al crear lead');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card shadow p-4">
|
||||||
|
<h3 className="mb-4">Nuevo Lead</h3>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Nombre</label>
|
||||||
|
<input className="form-control" value={nombre} onChange={e => setNombre(e.target.value)} required />
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Mail</label>
|
||||||
|
<input type="email" className="form-control" value={mail} onChange={e => setMail(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Teléfono</label>
|
||||||
|
<input className="form-control" value={fono} onChange={e => setFono(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Producto</label>
|
||||||
|
<input className="form-control" value={producto} onChange={e => setProducto(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<button type="submit" className="btn btn-primary">Guardar</button>
|
||||||
|
<button type="button" className="btn btn-secondary ms-2" onClick={() => router.back()}>Cancelar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
export default function LeadsPage() {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [leads, setLeads] = useState<any[]>([]);
|
||||||
|
const [loadingData, setLoadingData] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) router.push('/login');
|
||||||
|
if (user) {
|
||||||
|
api.get('/lead/nuevos', { ejecutivo: 1 }).then(data => {
|
||||||
|
setLeads(Array.isArray(data) ? data : []);
|
||||||
|
setLoadingData(false);
|
||||||
|
}).catch(() => setLoadingData(false));
|
||||||
|
}
|
||||||
|
}, [user, loading, router]);
|
||||||
|
|
||||||
|
if (loading || loadingData) return <LoadingSpinner />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h3>Leads</h3>
|
||||||
|
<button className="btn btn-primary" onClick={() => router.push('/leads/nuevo')}>Nuevo Lead</button>
|
||||||
|
</div>
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Mail</th>
|
||||||
|
<th>Teléfono</th>
|
||||||
|
<th>Producto</th>
|
||||||
|
<th>Fecha</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{leads.map((lead: any, i: number) => (
|
||||||
|
<tr key={i} style={{ cursor: 'pointer' }} onClick={() => router.push(`/leads/${lead.Id || i}`)}>
|
||||||
|
<td>{lead.Nombre || lead.nombre}</td>
|
||||||
|
<td>{lead.Mail || lead.mail}</td>
|
||||||
|
<td>{lead.Telefono || lead.telefono}</td>
|
||||||
|
<td>{lead.Producto || lead.producto}</td>
|
||||||
|
<td>{lead.FechaCreacion ? new Date(lead.FechaCreacion).toLocaleDateString() : ''}</td>
|
||||||
|
<td><button className="btn btn-sm btn-outline-primary">Ver</button></td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{leads.length === 0 && <tr><td colSpan={6} className="text-center">Sin leads</td></tr>}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, FormEvent } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { validarRut, formatRut } from '@/lib/validarRut';
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
const [rut, setRut] = useState('');
|
||||||
|
const [clave, setClave] = useState('');
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const { login } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleRutChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value.replace(/[^0-9kK-]/g, '');
|
||||||
|
setRut(value);
|
||||||
|
if (value.length > 2) {
|
||||||
|
setRut(formatRut(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
if (!validarRut(rut)) {
|
||||||
|
setError('RUT inválido');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await login(rut, clave);
|
||||||
|
router.push('/dashboard');
|
||||||
|
} catch {
|
||||||
|
setError('Credenciales inválidas');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mt-5">
|
||||||
|
<div className="row justify-content-center">
|
||||||
|
<div className="col-md-4">
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body p-4">
|
||||||
|
<div className="text-center mb-4">
|
||||||
|
<img src="/img/ichnHD.png" alt="ICHN" height="60" />
|
||||||
|
<h4 className="mt-2">Iniciar Sesión</h4>
|
||||||
|
</div>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">RUT</label>
|
||||||
|
<input className="form-control" value={rut} onChange={handleRutChange} placeholder="12.345.678-5" />
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Clave</label>
|
||||||
|
<input type="password" className="form-control" value={clave} onChange={e => setClave(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
{error && <div className="alert alert-danger py-2">{error}</div>}
|
||||||
|
<button type="submit" className="btn btn-primary w-100">Ingresar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
router.push(user ? '/dashboard' : '/login');
|
||||||
|
}
|
||||||
|
}, [user, loading, router]);
|
||||||
|
|
||||||
|
return <LoadingSpinner />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
|
||||||
|
export default function ReimpresionPage() {
|
||||||
|
const [tipo, setTipo] = useState('contrato');
|
||||||
|
const [id, setId] = useState('');
|
||||||
|
|
||||||
|
const handleReimprimir = () => {
|
||||||
|
if (tipo === 'contrato') {
|
||||||
|
window.open(`${process.env.NEXT_PUBLIC_API_URL}/report/contrato/${id}`, '_blank');
|
||||||
|
} else if (tipo === 'cotizacion') {
|
||||||
|
window.open(`${process.env.NEXT_PUBLIC_API_URL}/report/cotizacion/${id}`, '_blank');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Reimpresión de Documentos</h3>
|
||||||
|
<div className="card shadow p-4">
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Tipo</label>
|
||||||
|
<select className="form-select" value={tipo} onChange={e => setTipo(e.target.value)}>
|
||||||
|
<option value="contrato">Contrato</option>
|
||||||
|
<option value="cotizacion">Cotización</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">N° Documento</label>
|
||||||
|
<input className="form-control" value={id} onChange={e => setId(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-primary" onClick={handleReimprimir}>Reimprimir</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
|
||||||
|
export default function ReporteVentasEmpPage() {
|
||||||
|
const [data, setData] = useState<any[] | null>(null);
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
const result = await api.get('/informe/ventas-empresa', { tipo: 'LISTADO', varA: '', varB: '', varC: 0 });
|
||||||
|
setData(Array.isArray(result) ? result : []);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Ventas Empresas</h3>
|
||||||
|
<button className="btn btn-primary mb-3" onClick={load}>Cargar</button>
|
||||||
|
{data && (
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table">
|
||||||
|
<thead><tr><th>Empresa</th><th>Curso</th><th>Monto</th><th>Fecha</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{data.map((r: any, i: number) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>{r.Empresa || r.empresa}</td>
|
||||||
|
<td>{r.Curso || r.curso}</td>
|
||||||
|
<td>${(r.Monto || r.monto || 0).toLocaleString()}</td>
|
||||||
|
<td>{r.Fecha ? new Date(r.Fecha).toLocaleDateString() : ''}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{data.length === 0 && <tr><td colSpan={4} className="text-center">Sin datos</td></tr>}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
|
||||||
|
export default function ReporteVentasPage() {
|
||||||
|
const [mes, setMes] = useState(new Date().getMonth() + 1);
|
||||||
|
const [agno, setAgno] = useState(new Date().getFullYear());
|
||||||
|
const [data, setData] = useState<any[] | null>(null);
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
const result = await api.get('/informe/ventas-mensuales', { mes, agno, tipo: 'VENTA' });
|
||||||
|
setData(Array.isArray(result) ? result : []);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3">Reporte de Ventas</h3>
|
||||||
|
<div className="row mb-3">
|
||||||
|
<div className="col-auto">
|
||||||
|
<select className="form-select" value={mes} onChange={e => setMes(Number(e.target.value))}>
|
||||||
|
{Array.from({ length: 12 }, (_, i) => (
|
||||||
|
<option key={i + 1} value={i + 1}>{new Date(0, i).toLocaleString('es', { month: 'long' })}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="col-auto">
|
||||||
|
<input type="number" className="form-control" value={agno} onChange={e => setAgno(Number(e.target.value))} />
|
||||||
|
</div>
|
||||||
|
<div className="col-auto">
|
||||||
|
<button className="btn btn-primary" onClick={load}>Generar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{data && (
|
||||||
|
<div className="card shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Ejecutivo</th><th>Ventas</th><th>Monto</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.map((r: any, i: number) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>{r.Ejecutivo || r.ejecutivo || r.Nombre}</td>
|
||||||
|
<td>{r.Cantidad || r.cantidad || r.Ventas}</td>
|
||||||
|
<td>${(r.Monto || r.monto || r.Total || 0).toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{data.length === 0 && <tr><td colSpan={3} className="text-center">Sin datos</td></tr>}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
const { user, logout } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
logout();
|
||||||
|
router.push('/login');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<div className="header-content">
|
||||||
|
<span className="badge">Menu de Aplicaciones</span>
|
||||||
|
<div className="user-info">
|
||||||
|
<span className="user-name">
|
||||||
|
<i className="fas fa-user"></i> {user?.nombre || 'Usuario'}
|
||||||
|
</span>
|
||||||
|
<button className="btn-logout" onClick={handleLogout}>
|
||||||
|
<i className="fas fa-sign-out-alt"></i> Cerrar Sesión
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default function LoadingSpinner({ text = 'Cargando...' }: { text?: string }) {
|
||||||
|
return (
|
||||||
|
<div className="loading-spinner">
|
||||||
|
<div className="spinner"></div>
|
||||||
|
<p>{text}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
confirmText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ModalConfirmacion({ open, title, message, onConfirm, onCancel, confirmText = 'Aceptar' }: Props) {
|
||||||
|
if (!open) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal-overlay" onClick={onCancel}>
|
||||||
|
<div className="modal-content" onClick={e => e.stopPropagation()}>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
<p>{message}</p>
|
||||||
|
<div className="modal-actions">
|
||||||
|
<button className="btn btn-secondary" onClick={onCancel}>Cancelar</button>
|
||||||
|
<button className="btn btn-primary" onClick={onConfirm}>{confirmText}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{ href: '/dashboard', icon: 'fas fa-home', label: 'Dashboard' },
|
||||||
|
{ href: '/leads', icon: 'fas fa-users', label: 'Leads' },
|
||||||
|
{ href: '/cotizaciones', icon: 'fas fa-file-invoice-dollar', label: 'Cotizaciones' },
|
||||||
|
{ href: '/cursos', icon: 'fas fa-book', label: 'Cursos' },
|
||||||
|
{ href: '/alumnos', icon: 'fas fa-user-graduate', label: 'Alumnos' },
|
||||||
|
{ href: '/arqueo', icon: 'fas fa-cash-register', label: 'Arqueo' },
|
||||||
|
{ href: '/empresas', icon: 'fas fa-building', label: 'Empresas' },
|
||||||
|
{ href: '/reportes/ventas', icon: 'fas fa-chart-bar', label: 'Reportes' },
|
||||||
|
{ href: '/autorizador', icon: 'fas fa-check-double', label: 'Autorizador' },
|
||||||
|
{ href: '/contacto', icon: 'fas fa-envelope', label: 'Contacto' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Sidebar() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`sidebar ${collapsed ? 'collapsed' : ''}`}>
|
||||||
|
<div className="logo-details">
|
||||||
|
<img className="icon" src="/img/favicon.png" width="60" alt="SAM" />
|
||||||
|
<div className="logo_name">SAM</div>
|
||||||
|
<i className="bx bx-menu" id="btn" onClick={() => setCollapsed(!collapsed)}></i>
|
||||||
|
</div>
|
||||||
|
<ul className="nav-list">
|
||||||
|
{menuItems.map(item => (
|
||||||
|
<li key={item.href} className={pathname.startsWith(item.href) ? 'active' : ''}>
|
||||||
|
<Link href={item.href}>
|
||||||
|
<i className={item.icon}></i>
|
||||||
|
<span className="links_name">{item.label}</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
nombre: string;
|
||||||
|
sede?: string;
|
||||||
|
perfil?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthContextType {
|
||||||
|
user: User | null;
|
||||||
|
loading: boolean;
|
||||||
|
login: (rut: string, clave: string) => Promise<void>;
|
||||||
|
logout: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType>({} as AuthContextType);
|
||||||
|
|
||||||
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const stored = sessionStorage.getItem('sam_user');
|
||||||
|
if (stored) {
|
||||||
|
setUser(JSON.parse(stored));
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const login = async (rut: string, clave: string) => {
|
||||||
|
const res = await api.post<{ nombre: string; sede?: string; token: string }>('/auth/login', { rut, clave });
|
||||||
|
sessionStorage.setItem('sam_user', JSON.stringify({ nombre: res.nombre, sede: res.sede }));
|
||||||
|
setUser({ nombre: res.nombre, sede: res.sede });
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
sessionStorage.removeItem('sam_user');
|
||||||
|
setUser(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ user, loading, login, logout }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAuth = () => useContext(AuthContext);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
const TIMEOUT_MS = 30 * 60 * 1000;
|
||||||
|
|
||||||
|
export function useInactividad(onTimeout: () => void) {
|
||||||
|
const timerRef = useRef<NodeJS.Timeout>();
|
||||||
|
|
||||||
|
const resetTimer = () => {
|
||||||
|
if (timerRef.current) clearTimeout(timerRef.current);
|
||||||
|
timerRef.current = setTimeout(onTimeout, TIMEOUT_MS);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const events = ['mousedown', 'keydown', 'scroll', 'touchstart'];
|
||||||
|
resetTimer();
|
||||||
|
|
||||||
|
events.forEach(event => window.addEventListener(event, resetTimer));
|
||||||
|
return () => {
|
||||||
|
events.forEach(event => window.removeEventListener(event, resetTimer));
|
||||||
|
if (timerRef.current) clearTimeout(timerRef.current);
|
||||||
|
};
|
||||||
|
}, [onTimeout]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export function formatCurrency(amount: number): string {
|
||||||
|
return new Intl.NumberFormat('es-CL', { style: 'currency', currency: 'CLP', maximumFractionDigits: 0 }).format(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDate(date: string | Date): string {
|
||||||
|
const d = typeof date === 'string' ? new Date(date) : date;
|
||||||
|
return d.toLocaleDateString('es-CL', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function classNames(...classes: (string | boolean | undefined)[]): string {
|
||||||
|
return classes.filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
export function validarRut(rut: string): boolean {
|
||||||
|
const limpio = rut.replace(/\./g, '').replace(/-/g, '').toUpperCase();
|
||||||
|
if (!/^[0-9]+[0-9K]$/.test(limpio)) return false;
|
||||||
|
|
||||||
|
const cuerpo = limpio.slice(0, -1);
|
||||||
|
const dv = limpio.slice(-1);
|
||||||
|
|
||||||
|
let suma = 0;
|
||||||
|
let multiplo = 2;
|
||||||
|
|
||||||
|
for (let i = cuerpo.length - 1; i >= 0; i--) {
|
||||||
|
suma += parseInt(cuerpo[i]) * multiplo;
|
||||||
|
multiplo = multiplo < 7 ? multiplo + 1 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dvEsperado = 11 - (suma % 11);
|
||||||
|
const dvChar = dvEsperado === 11 ? '0' : dvEsperado === 10 ? 'K' : String(dvEsperado);
|
||||||
|
|
||||||
|
return dvChar === dv;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatRut(rut: string): string {
|
||||||
|
const limpio = rut.replace(/\./g, '').replace(/-/g, '');
|
||||||
|
if (limpio.length < 2) return limpio;
|
||||||
|
const cuerpo = limpio.slice(0, -1);
|
||||||
|
const dv = limpio.slice(-1);
|
||||||
|
const formateado = cuerpo.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||||
|
return `${formateado}-${dv}`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5087/api';
|
||||||
|
|
||||||
|
interface ApiOptions {
|
||||||
|
method?: string;
|
||||||
|
body?: unknown;
|
||||||
|
params?: Record<string, string | number | boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function request<T>(endpoint: string, options: ApiOptions = {}): Promise<T> {
|
||||||
|
const { method = 'GET', body, params } = options;
|
||||||
|
|
||||||
|
let url = `${API_URL}${endpoint}`;
|
||||||
|
if (params) {
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
Object.entries(params).forEach(([k, v]) => searchParams.append(k, String(v)));
|
||||||
|
url += `?${searchParams.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
credentials: 'include',
|
||||||
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const error = await res.text();
|
||||||
|
throw new Error(error || `HTTP ${res.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const api = {
|
||||||
|
get: <T>(endpoint: string, params?: Record<string, string | number | boolean>) =>
|
||||||
|
request<T>(endpoint, { params }),
|
||||||
|
|
||||||
|
post: <T>(endpoint: string, body?: unknown) =>
|
||||||
|
request<T>(endpoint, { method: 'POST', body }),
|
||||||
|
|
||||||
|
put: <T>(endpoint: string, body?: unknown) =>
|
||||||
|
request<T>(endpoint, { method: 'PUT', body }),
|
||||||
|
};
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css?family=Fira+Sans&display=swap');
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: 'Fira Sans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar (replica SAM.Master) */
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 78px;
|
||||||
|
background: #11101d;
|
||||||
|
padding: 6px 14px;
|
||||||
|
z-index: 99;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .logo-details {
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .logo-details .icon {
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open .logo-details .icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .logo-details .logo_name {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open .logo-details .logo_name {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .logo-details #btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 23px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open .logo-details #btn {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar i {
|
||||||
|
color: #fff;
|
||||||
|
height: 60px;
|
||||||
|
min-width: 50px;
|
||||||
|
font-size: 28px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-list {
|
||||||
|
margin-top: 20px;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-list li {
|
||||||
|
position: relative;
|
||||||
|
list-style: none;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-list li a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-list li a:hover {
|
||||||
|
background: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-list li a:hover i,
|
||||||
|
.sidebar .nav-list li a:hover .links_name {
|
||||||
|
color: #11101d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-list li.active a {
|
||||||
|
background: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-list li.active a i,
|
||||||
|
.sidebar .nav-list li.active a .links_name {
|
||||||
|
color: #11101d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-list li .links_name {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open .nav-list li .links_name {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Home section */
|
||||||
|
.home-section {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100vh;
|
||||||
|
left: 78px;
|
||||||
|
width: calc(100% - 78px);
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open ~ .home-section {
|
||||||
|
left: 250px;
|
||||||
|
width: calc(100% - 250px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-section header {
|
||||||
|
background: #fff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
background: #4e73df;
|
||||||
|
color: #fff;
|
||||||
|
padding: 6px 16px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-logout {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid #dc3545;
|
||||||
|
color: #dc3545;
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-logout:hover {
|
||||||
|
background: #dc3545;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading spinner */
|
||||||
|
.loading-spinner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #4e73df;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: #fff;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
max-width: 400px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card styles */
|
||||||
|
.card {
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.shadow {
|
||||||
|
box-shadow: 0 0.15rem 1.75rem 0 rgba(58,59,69,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
.sidebar.open {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.home-section {
|
||||||
|
left: 60px;
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [{ "name": "next" }],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<Solution>
|
||||||
|
<Folder Name="/src/">
|
||||||
|
<Project Path="src/ServicesExternos.API/ServicesExternos.API.csproj" />
|
||||||
|
</Folder>
|
||||||
|
</Solution>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using ServicesExternos.API.Models;
|
||||||
|
using ServicesExternos.API.Services;
|
||||||
|
|
||||||
|
namespace ServicesExternos.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/dte")]
|
||||||
|
public class DteController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly DteService _dteService;
|
||||||
|
|
||||||
|
public DteController(DteService dteService)
|
||||||
|
{
|
||||||
|
_dteService = dteService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("emitir")]
|
||||||
|
public async Task<IActionResult> Emitir([FromBody] DteEmissionRequest request)
|
||||||
|
{
|
||||||
|
var result = await _dteService.EmitirYDescargarPdfAsync(request);
|
||||||
|
if (result.PdfBytes != null)
|
||||||
|
return File(result.PdfBytes, "application/pdf", $"dte_{result.Folio}.pdf");
|
||||||
|
|
||||||
|
return BadRequest(new { error = result.Estado });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("estado/{dte}/{emisor}")]
|
||||||
|
public async Task<IActionResult> ConsultarEstado(int dte, long emisor)
|
||||||
|
{
|
||||||
|
var result = await _dteService.ConsultarSiguienteFolioAsync(dte, emisor);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using ServicesExternos.API.Models;
|
||||||
|
using ServicesExternos.API.Services;
|
||||||
|
|
||||||
|
namespace ServicesExternos.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/email")]
|
||||||
|
public class EmailController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly EmailService _emailService;
|
||||||
|
|
||||||
|
public EmailController(EmailService emailService)
|
||||||
|
{
|
||||||
|
_emailService = emailService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("send")]
|
||||||
|
public async Task<IActionResult> Send([FromBody] EmailRequest request)
|
||||||
|
{
|
||||||
|
var result = await _emailService.SendAsync(request);
|
||||||
|
if (result == "ok")
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
|
||||||
|
return BadRequest(new { error = result });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using ServicesExternos.API.Models;
|
||||||
|
using ServicesExternos.API.Services;
|
||||||
|
|
||||||
|
namespace ServicesExternos.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/pagos/transbank")]
|
||||||
|
public class TransbankController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly TransbankService _transbankService;
|
||||||
|
|
||||||
|
public TransbankController(TransbankService transbankService)
|
||||||
|
{
|
||||||
|
_transbankService = transbankService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("crear-transaccion")]
|
||||||
|
public async Task<IActionResult> CrearTransaccion([FromBody] TransbankTransactionRequest request)
|
||||||
|
{
|
||||||
|
var result = await _transbankService.CrearTransaccionAsync(request);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("confirmar")]
|
||||||
|
public async Task<IActionResult> Confirmar([FromBody] TransbankConfirmRequest request)
|
||||||
|
{
|
||||||
|
var result = await _transbankService.ConfirmarTransaccionAsync(request.TokenWs!);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("voucher")]
|
||||||
|
public async Task<IActionResult> GrabarVoucher([FromBody] VoucherRequest request)
|
||||||
|
{
|
||||||
|
var result = await _transbankService.GrabarVoucherAsync(
|
||||||
|
request.Token, request.AccountingDate, request.BuyOrder,
|
||||||
|
request.CardNumber, request.CardExpiration, request.AuthorizationCode,
|
||||||
|
request.PaymentType, request.ResponseCode, request.SharesNumber,
|
||||||
|
request.Amount, request.CommerceCode, request.DetailBuyOrder,
|
||||||
|
request.SessionId, request.TransactionDate, request.Vci);
|
||||||
|
return Ok(new { mensaje = result });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("buscar/{token}")]
|
||||||
|
public async Task<IActionResult> Buscar(string token)
|
||||||
|
{
|
||||||
|
var result = await _transbankService.BuscarTransaccionAsync(token);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
RUN dotnet restore ServicesExternos.slnx
|
||||||
|
RUN dotnet publish src/ServicesExternos.API/ServicesExternos.API.csproj -c Release -o /app
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app .
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["dotnet", "ServicesExternos.API.dll"]
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
namespace ServicesExternos.API.Models;
|
||||||
|
|
||||||
|
public class DteEmissionRequest
|
||||||
|
{
|
||||||
|
public int Dte { get; set; } = 33;
|
||||||
|
public int Emisor { get; set; }
|
||||||
|
public string? Fecha { get; set; }
|
||||||
|
public string? Sucursal { get; set; }
|
||||||
|
public int? SucursalSii { get; set; }
|
||||||
|
public string? Terminal { get; set; }
|
||||||
|
public string? IndicadorServicio { get; set; }
|
||||||
|
public string? TipoDespacho { get; set; }
|
||||||
|
public string? TipoTraslado { get; set; }
|
||||||
|
public string? TpoTranCompra { get; set; }
|
||||||
|
public string? TpoTranVenta { get; set; }
|
||||||
|
public string? FechaVencimiento { get; set; }
|
||||||
|
public Receptor? Receptor { get; set; }
|
||||||
|
public List<DetalleItem>? Detalles { get; set; }
|
||||||
|
public Referencia? Referencia { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Receptor
|
||||||
|
{
|
||||||
|
public string? RUT { get; set; }
|
||||||
|
public string? RazonSocial { get; set; }
|
||||||
|
public string? Direccion { get; set; }
|
||||||
|
public string? Comuna { get; set; }
|
||||||
|
public string? Ciudad { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DetalleItem
|
||||||
|
{
|
||||||
|
public string? Nombre { get; set; }
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
public int Precio { get; set; }
|
||||||
|
public string? Descuento { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Referencia
|
||||||
|
{
|
||||||
|
public int TipoDocReferencia { get; set; }
|
||||||
|
public int FolioReferencia { get; set; }
|
||||||
|
public string? FechaReferencia { get; set; }
|
||||||
|
public string? RazonReferencia { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DteGenerarPorCodigo
|
||||||
|
{
|
||||||
|
public string? Codigo { get; set; }
|
||||||
|
public int TipoDte { get; set; }
|
||||||
|
public int Emisor { get; set; }
|
||||||
|
public int Receptor { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DteFolioInfo
|
||||||
|
{
|
||||||
|
public int FoliosDisponibles { get; set; }
|
||||||
|
public int FolioActual { get; set; }
|
||||||
|
public int? Siguiente { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DteResultado
|
||||||
|
{
|
||||||
|
public byte[]? PdfBytes { get; set; }
|
||||||
|
public int Folio { get; set; }
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace ServicesExternos.API.Models;
|
||||||
|
|
||||||
|
public class EmailRequest
|
||||||
|
{
|
||||||
|
public string? To { get; set; }
|
||||||
|
public string? Subject { get; set; }
|
||||||
|
public string? Body { get; set; }
|
||||||
|
public bool IsHtml { get; set; } = true;
|
||||||
|
public List<string>? AttachmentPaths { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
namespace ServicesExternos.API.Models;
|
||||||
|
|
||||||
|
public class TransbankTransactionRequest
|
||||||
|
{
|
||||||
|
public string? BuyOrder { get; set; }
|
||||||
|
public string? SessionId { get; set; }
|
||||||
|
public int Amount { get; set; }
|
||||||
|
public string? ReturnUrl { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TransbankConfirmRequest
|
||||||
|
{
|
||||||
|
public string? TokenWs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VoucherRequest
|
||||||
|
{
|
||||||
|
public string Token { get; set; } = string.Empty;
|
||||||
|
public string AccountingDate { get; set; } = string.Empty;
|
||||||
|
public string BuyOrder { get; set; } = string.Empty;
|
||||||
|
public string CardNumber { get; set; } = string.Empty;
|
||||||
|
public string CardExpiration { get; set; } = string.Empty;
|
||||||
|
public string AuthorizationCode { get; set; } = string.Empty;
|
||||||
|
public string PaymentType { get; set; } = string.Empty;
|
||||||
|
public string ResponseCode { get; set; } = string.Empty;
|
||||||
|
public string SharesNumber { get; set; } = string.Empty;
|
||||||
|
public int Amount { get; set; }
|
||||||
|
public string CommerceCode { get; set; } = string.Empty;
|
||||||
|
public string DetailBuyOrder { get; set; } = string.Empty;
|
||||||
|
public string SessionId { get; set; } = string.Empty;
|
||||||
|
public string TransactionDate { get; set; } = string.Empty;
|
||||||
|
public string Vci { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using ServicesExternos.API.Services;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
builder.Services.AddOpenApi();
|
||||||
|
|
||||||
|
builder.Services.AddHttpClient<DteService>();
|
||||||
|
builder.Services.AddHttpClient<TransbankService>();
|
||||||
|
builder.Services.AddScoped<EmailService>();
|
||||||
|
|
||||||
|
builder.Services.AddCors(options =>
|
||||||
|
{
|
||||||
|
options.AddDefaultPolicy(policy =>
|
||||||
|
{
|
||||||
|
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
app.MapOpenApi();
|
||||||
|
|
||||||
|
app.UseCors();
|
||||||
|
app.MapControllers();
|
||||||
|
app.Run();
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "http://localhost:5194",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using ServicesExternos.API.Models;
|
||||||
|
|
||||||
|
namespace ServicesExternos.API.Services;
|
||||||
|
|
||||||
|
public class DteService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
private readonly string _userHash;
|
||||||
|
private readonly string _ambiente;
|
||||||
|
|
||||||
|
public DteService(HttpClient client, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
_client.BaseAddress = new Uri("https://libredte.cl");
|
||||||
|
_client.Timeout = TimeSpan.FromSeconds(60);
|
||||||
|
_userHash = configuration["LibreDTE:UserHash"] ?? throw new Exception("LibreDTE:UserHash required");
|
||||||
|
_ambiente = configuration["LibreDTE:Ambiente"] ?? "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetAuth()
|
||||||
|
{
|
||||||
|
var authString = Convert.ToBase64String(Encoding.ASCII.GetBytes("X:" + _userHash));
|
||||||
|
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DteResultado> EmitirYDescargarPdfAsync(DteEmissionRequest datos)
|
||||||
|
{
|
||||||
|
SetAuth();
|
||||||
|
string jsonFinal = JsonConvert.SerializeObject(datos, Formatting.Indented);
|
||||||
|
var jsonContent = new StringContent(jsonFinal, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var response = await _client.PostAsync("/api/dte/documentos/emitir", jsonContent);
|
||||||
|
string respuestaJson = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return new DteResultado { Estado = respuestaJson };
|
||||||
|
|
||||||
|
JObject jsonResp = JObject.Parse(respuestaJson);
|
||||||
|
string cod = (string)jsonResp["codigo"]!;
|
||||||
|
int emi = (int)jsonResp["emisor"]!;
|
||||||
|
int rec = (int)jsonResp["receptor"]!;
|
||||||
|
int dte = (int)jsonResp["dte"]!;
|
||||||
|
|
||||||
|
var payload = new DteGenerarPorCodigo
|
||||||
|
{
|
||||||
|
Codigo = cod,
|
||||||
|
TipoDte = dte,
|
||||||
|
Emisor = emi,
|
||||||
|
Receptor = rec
|
||||||
|
};
|
||||||
|
|
||||||
|
jsonFinal = JsonConvert.SerializeObject(payload);
|
||||||
|
jsonContent = new StringContent(jsonFinal, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
response = await _client.PostAsync("/api/dte/documentos/generar?getXML=0&links=1&email=1&retry=1&gzip=0", jsonContent);
|
||||||
|
respuestaJson = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return new DteResultado { Estado = respuestaJson };
|
||||||
|
|
||||||
|
jsonResp = JObject.Parse(respuestaJson);
|
||||||
|
int folio = (int)jsonResp["folio"]!;
|
||||||
|
|
||||||
|
var responsePdf = await _client.GetAsync($"/api/dte/dte_emitidos/pdf/{dte}/{folio}/{emi}?formato=general&papelContinuo=0&copias_tributarias=1&copias_cedibles=1&cedible=0&compress=0&base64=0");
|
||||||
|
|
||||||
|
if (!responsePdf.IsSuccessStatusCode)
|
||||||
|
throw new Exception("Se generó el DTE pero falló la descarga del PDF.");
|
||||||
|
|
||||||
|
await _client.GetAsync($"/api/dte/dte_emitidos/actualizar_estado/{dte}/{folio}/{emi}?usarWebservice=1");
|
||||||
|
|
||||||
|
byte[] pdfBytes = await responsePdf.Content.ReadAsByteArrayAsync();
|
||||||
|
return new DteResultado { PdfBytes = pdfBytes, Folio = folio, Estado = "ok" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DteFolioInfo> ConsultarSiguienteFolioAsync(int dte, long emisor)
|
||||||
|
{
|
||||||
|
SetAuth();
|
||||||
|
var response = await _client.GetAsync($"/api/dte/admin/dte_folios/info/{dte}/{emisor}");
|
||||||
|
string respuestaJson = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
throw new Exception($"Error al consultar folio ({response.StatusCode}): {respuestaJson}");
|
||||||
|
|
||||||
|
return JsonConvert.DeserializeObject<DteFolioInfo>(respuestaJson)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user