Compare commits

...

2 Commits

Author SHA1 Message Date
684036875c Merge branch 'master' of http://srv01.saludlab.com.ar:3000/leandro/phronCare
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 8m37s
2025-05-14 18:38:33 -03:00
e7069b3848 Update Transversal PDFGenerator 2025-05-14 18:16:31 -03:00
31 changed files with 14348 additions and 381 deletions

1
.gitignore vendored
View File

@ -423,3 +423,4 @@ FodyWeavers.xsd
/Domain/obj/Domain.csproj.nuget.g.props
/Models/obj/Models.csproj.nuget.g.props
/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json
/phronCare.API/.local-chromium/Win64-884014/chrome-win

View File

@ -16,4 +16,7 @@
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore\8.0.10\buildTransitive\net8.0\Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore\8.0.10\buildTransitive\net8.0\Microsoft.EntityFrameworkCore.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<PkgNewtonsoft_Json Condition=" '$(PkgNewtonsoft_Json)' == '' ">C:\Users\maski\.nuget\packages\newtonsoft.json\10.0.3</PkgNewtonsoft_Json>
</PropertyGroup>
</Project>

View File

@ -7,7 +7,7 @@
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.1</NuGetToolVersion>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />

View File

@ -7,7 +7,7 @@
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.1</NuGetToolVersion>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />

View File

@ -0,0 +1,26 @@
using Transversal.Models;
namespace Transversal.Interfaces
{
/// <summary>
/// Define el contrato para un servicio generador de PDFs a partir de contenido HTML.
/// </summary>
public interface IPdfGeneratorService
{
/// <summary>
/// Genera un documento PDF a partir de una cadena HTML.
/// </summary>
/// <param name="htmlContent">
/// Contenido HTML completo (incluyendo etiquetas <html>, <head>, <body>, etc.) que se desea convertir a PDF.
/// </param>
/// <param name="options">
/// Opcional: configuración personalizada para el documento PDF (tamaño de papel, orientación, márgenes, encabezados, pies de página, etc.).
/// Si se deja en null, se aplica la configuración por defecto (A4, vertical, sin márgenes personalizados).
/// </param>
/// <returns>
/// Un array de bytes que representa el documento PDF generado.
/// Puede ser utilizado para guardar en disco, devolver en una API como FileContentResult, etc.
/// </returns>
Task<byte[]> GeneratePdfFromHtmlAsync(string htmlContent, PdfGenerationOptions options = null);
}
}

View File

@ -0,0 +1,15 @@
using PuppeteerSharp.Media;
namespace Transversal.Models
{
public class PdfGenerationOptions
{
public PaperFormat Format { get; set; } = PaperFormat.A4;
public bool Landscape { get; set; } = false;
public bool PrintBackground { get; set; } = true;
public decimal Scale { get; set; } = 1.0m;
public MarginOptions Margins { get; set; } = null;
public string HeaderTemplate { get; set; } = null;
public string FooterTemplate { get; set; } = null;
}
}

View File

@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Transversal.Services
{
internal class Class1
{
}
}

View File

@ -0,0 +1,57 @@
using Transversal.Interfaces;
using Transversal.Models;
using PuppeteerSharp;
using PuppeteerSharp.Media;
namespace Transversal.Services
{
public class PuppeteerPdfGeneratorService : IPdfGeneratorService, IAsyncDisposable
{
private readonly Browser _browser;
public PuppeteerPdfGeneratorService()
{
// Descargar Chromium si no está
new BrowserFetcher().DownloadAsync().GetAwaiter().GetResult();
// Lanzar Chromium persistente
_browser = Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[]
{
"--no-sandbox",
"--disable-setuid-sandbox"
}
}).GetAwaiter().GetResult();
}
public async Task<byte[]> GeneratePdfFromHtmlAsync(string htmlContent, PdfGenerationOptions options = null)
{
options ??= new PdfGenerationOptions();
using var page = await _browser.NewPageAsync();
await page.SetContentAsync(htmlContent);
var pdfOptions = new PdfOptions
{
Format = options.Format,
Landscape = options.Landscape,
PrintBackground = options.PrintBackground,
Scale = options.Scale,
MarginOptions = options.Margins ?? new MarginOptions(),
DisplayHeaderFooter = !string.IsNullOrEmpty(options.HeaderTemplate)
|| !string.IsNullOrEmpty(options.FooterTemplate),
HeaderTemplate = options.HeaderTemplate,
FooterTemplate = options.FooterTemplate
};
return await page.PdfDataAsync(pdfOptions);
}
public async ValueTask DisposeAsync()
{
if (_browser != null)
await _browser.CloseAsync();
}
}
}

View File

@ -7,7 +7,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EPPlus" Version="7.5.2" />
<PackageReference Include="EPPlus" Version="8.0.4" />
<PackageReference Include="PuppeteerSharp" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@ -7,10 +7,13 @@
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.1</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
</ItemGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<PkgNewtonsoft_Json Condition=" '$(PkgNewtonsoft_Json)' == '' ">C:\Users\maski\.nuget\packages\newtonsoft.json\10.0.3</PkgNewtonsoft_Json>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,103 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Transversal.Interfaces;
namespace phronCare.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PdfTestController : ControllerBase
{
private readonly IPdfGeneratorService _pdfGeneratorService;
public PdfTestController(IPdfGeneratorService pdfGeneratorService)
{
_pdfGeneratorService = pdfGeneratorService;
}
[HttpGet("test")]
public async Task<IActionResult> GetTestPdf()
{
// HTML de ejemplo
string html = @"
<html>
<head>
<style>
body { font-family: Arial, sans-serif; font-size: 12px; margin: 20px; }
.header { text-align: center; border-bottom: 3px solid #4CAF50; padding-bottom: 10px; margin-bottom: 30px; }
.header h1 { color: #4CAF50; margin: 0; }
.info-table, .details-table, .totals-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
.info-table td { padding: 5px; }
.details-table th, .details-table td { border: 1px solid #ddd; padding: 8px; text-align: center; }
.details-table th { background-color: #4CAF50; color: white; }
.totals-table th, .totals-table td { border: 1px solid #ddd; padding: 8px; text-align: right; }
.totals-table th { background-color: #4CAF50; color: white; }
.footer { position: fixed; bottom: 15px; left: 0; right: 0; text-align: center; font-size: 10px; color: gray; border-top: 1px solid #ddd; padding-top: 5px; }
</style>
</head>
<body>
<div class='header'>
<h1>PhronCare Ortopedia</h1>
<p>Presupuesto médico de prueba</p>
</div>
<table class='info-table'>
<tr>
<td><strong>Cliente:</strong> Juan Pérez</td>
<td><strong>Presupuesto N°:</strong> 000123</td>
</tr>
<tr>
<td><strong>Fecha:</strong> 13/05/2025</td>
<td><strong>Profesional:</strong> Dr. Carlos López</td>
</tr>
</table>
<table class='details-table'>
<tr>
<th>Cantidad</th>
<th>Producto</th>
<th>Precio Unitario</th>
<th>Subtotal</th>
</tr>
<tr>
<td>2</td>
<td>Rodillera ortopédica</td>
<td>$5.000</td>
<td>$10.000</td>
</tr>
<tr>
<td>1</td>
<td>Férula de inmovilización</td>
<td>$8.000</td>
<td>$8.000</td>
</tr>
</table>
<table class='totals-table'>
<tr>
<th>Subtotal</th>
<td>$18.000</td>
</tr>
<tr>
<th>IVA (21%)</th>
<td>$3.780</td>
</tr>
<tr>
<th>Total</th>
<td><strong>$21.780</strong></td>
</tr>
</table>
<div class='footer'>
Documento generado automáticamente por PhronCare - Solo para fines demostrativos. No válido como factura.
</div>
</body>
</html>";
byte[] pdfBytes = await _pdfGeneratorService.GeneratePdfFromHtmlAsync(html);
return File(pdfBytes, "application/pdf", "Presupuesto.pdf");
}
}
}

View File

@ -15,6 +15,8 @@ using Core.Services;
using phronCare.API.Models.Security;
using Models.Repositories;
using Models.Models;
using Transversal.Interfaces;
using Transversal.Services;
var builder = WebApplication.CreateBuilder(args);
@ -174,6 +176,9 @@ app.Run();
static void RepositorysAndServices(WebApplicationBuilder builder)
{
// Registro servicio PDF transversal
builder.Services.AddScoped<IPdfGeneratorService, PuppeteerPdfGeneratorService>();
builder.Services.AddScoped<ITicketDom, TicketService>();
builder.Services.AddScoped<ITicketRepository, TicketRepository>();

View File

@ -1056,6 +1056,16 @@
],
"ReturnTypes": []
},
{
"ContainingType": "phronCare.API.Controllers.PdfTestController",
"Method": "GetTestPdf",
"RelativePath": "api/PdfTest/test",
"HttpMethod": "GET",
"IsController": true,
"Order": 0,
"Parameters": [],
"ReturnTypes": []
},
{
"ContainingType": "phronCare.API.Controllers.Sales.PeopleController",
"Method": "GetById",

View File

@ -501,7 +501,11 @@
"dependencies": {
"EPPlus": {
"target": "Package",
"version": "[7.5.2, )"
"version": "[8.0.4, )"
},
"PuppeteerSharp": {
"target": "Package",
"version": "[6.0.0, )"
}
},
"imports": [

View File

@ -22,6 +22,7 @@
<Import Project="$(NuGetPackageRoot)entityframework\6.5.1\buildTransitive\net6.0\EntityFramework.props" Condition="Exists('$(NuGetPackageRoot)entityframework\6.5.1\buildTransitive\net6.0\EntityFramework.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<PkgNewtonsoft_Json Condition=" '$(PkgNewtonsoft_Json)' == '' ">C:\Users\maski\.nuget\packages\newtonsoft.json\10.0.3</PkgNewtonsoft_Json>
<PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">C:\Users\maski\.nuget\packages\microsoft.extensions.apidescription.server\6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server>
<PkgMicrosoft_CodeAnalysis_Analyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_Analyzers)' == '' ">C:\Users\maski\.nuget\packages\microsoft.codeanalysis.analyzers\3.3.3</PkgMicrosoft_CodeAnalysis_Analyzers>
<PkgMicrosoft_VisualStudio_Azure_Containers_Tools_Targets Condition=" '$(PkgMicrosoft_VisualStudio_Azure_Containers_Tools_Targets)' == '' ">C:\Users\maski\.nuget\packages\microsoft.visualstudio.azure.containers.tools.targets\1.21.0</PkgMicrosoft_VisualStudio_Azure_Containers_Tools_Targets>

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)system.text.json\8.0.5\buildTransitive\net6.0\System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json\8.0.5\buildTransitive\net6.0\System.Text.Json.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Options.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
<Import Project="$(NuGetPackageRoot)system.text.json\8.0.5\buildTransitive\net6.0\System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json\8.0.5\buildTransitive\net6.0\System.Text.Json.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server\6.0.5\build\Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server\6.0.5\build\Microsoft.Extensions.ApiDescription.Server.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder\8.0.0\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder\8.0.0\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Options.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.visualstudio.azure.containers.tools.targets\1.21.0\build\Microsoft.VisualStudio.Azure.Containers.Tools.Targets.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.visualstudio.azure.containers.tools.targets\1.21.0\build\Microsoft.VisualStudio.Azure.Containers.Tools.Targets.targets')" />
<Import Project="$(NuGetPackageRoot)entityframework\6.5.1\buildTransitive\net6.0\EntityFramework.targets" Condition="Exists('$(NuGetPackageRoot)entityframework\6.5.1\buildTransitive\net6.0\EntityFramework.targets')" />
</ImportGroup>

File diff suppressed because it is too large Load Diff

View File

@ -1,73 +0,0 @@
using Microsoft.EntityFrameworkCore;
using System.Configuration;
using System.Reflection;
using Models.Models;
using Models.Repositories;
namespace phronCare.Test
{
[TestFixture]
public class TicketRepositoryTests
{
private TicketRepository _ticketRepository;
private PhronCareOperationsHubContext _context;
[SetUp]
public void Setup()
{
#if NETCOREAPP
// Copiar el archivo config para asegurarse de que ConfigurationManager lo encuentre
string configFile = $"{Assembly.GetExecutingAssembly().Location}.config";
string outputConfigFile = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;
File.Copy(configFile, outputConfigFile, true);
#endif
// Obtener la cadena de conexión desde app.config usando ConfigurationManager
var connectionString = ConfigurationManager.ConnectionStrings["OperationsHub"]?.ConnectionString;
if (string.IsNullOrEmpty(connectionString))
{
throw new InvalidOperationException("No se encontró la cadena de conexión 'OperationsHub'.");
}
var optionsBuilder = new DbContextOptionsBuilder<PhronCareOperationsHubContext>();
optionsBuilder.UseSqlServer(connectionString);
_context = new PhronCareOperationsHubContext(optionsBuilder.Options);
_ticketRepository = new TicketRepository(_context);
}
[TearDown]
public void TearDown()
{
// Verifica que _context no sea null antes de llamarlo
_context?.Dispose();
}
[Test]
public async Task GetSummaryAsync_ShouldReturnValidSummaryData()
{
// Act
var summaryData = await _ticketRepository.GetSummaryAsync();
// Assert
Assert.That(summaryData, Is.Not.Null, "El resumen no debe ser nulo");
Assert.That(summaryData.Any(), Is.True, "El resumen debe contener al menos un elemento");
foreach (var item in summaryData)
{
Assert.Multiple(() =>
{
Assert.That(item.Estado, Is.Not.Null, "El campo Estado no debe ser nulo");
Assert.That(item.Cantidad, Is.GreaterThanOrEqualTo(0), "La cantidad debe ser mayor o igual a cero");
});
}
}
[Test]
public async Task GetAllAsync_ReturnsAllTickets()
{
var tickets = await _ticketRepository.GetAllAsync();
Assert.That(tickets, Is.Not.Null);
Assert.That(tickets.Any(), Is.True);
}
}
}

View File

@ -0,0 +1,142 @@
using Transversal.Services;
using Transversal.Models;
namespace phronCare.Test
{
[TestFixture]
public class PdfGeneratorServiceTests
{
private PuppeteerPdfGeneratorService _pdfService;
[OneTimeSetUp]
public void Setup()
{
// Instancia real del servicio (recomendado para test manual/local)
_pdfService = new PuppeteerPdfGeneratorService();
}
[OneTimeTearDown]
public async Task TearDown()
{
// Liberar Chromium al finalizar los tests
if (_pdfService != null)
await _pdfService.DisposeAsync();
}
[Test]
public async Task GeneratePdfFromHtml_ShouldCreatePdfFile()
{
// Arrange
string html = @"
<html>
<head>
<style>
body { font-family: Arial, sans-serif; font-size: 12px; margin: 20px; }
.header { text-align: center; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; margin-bottom: 20px; }
.header h1 { color: #4CAF50; }
.info { margin-bottom: 20px; }
.info table { width: 100%; }
.info td { padding: 5px; }
.details table { width: 100%; border-collapse: collapse; }
.details th, .details td { border: 1px solid #dddddd; text-align: center; padding: 8px; }
.details th { background-color: #4CAF50; color: white; }
.totals { margin-top: 20px; float: right; width: 300px; }
.totals table { width: 100%; border-collapse: collapse; }
.totals th, .totals td { border: 1px solid #dddddd; text-align: right; padding: 8px; }
.totals th { background-color: #4CAF50; color: white; }
.footer { position: fixed; bottom: 20px; left: 0; right: 0; text-align: center; font-size: 10px; color: gray; }
</style>
</head>
<body>
<div class='header'>
<h1>PhronCare Ortopedia</h1>
<p>Presupuesto Médico</p>
</div>
<div class='info'>
<table>
<tr>
<td><strong>Cliente:</strong> Juan Pérez</td>
<td><strong>Presupuesto N°:</strong> 000123</td>
</tr>
<tr>
<td><strong>Fecha:</strong> 13/05/2025</td>
<td><strong>Profesional:</strong> Dr. Carlos López</td>
</tr>
</table>
</div>
<div class='details'>
<table>
<tr>
<th>Cantidad</th>
<th>Producto</th>
<th>Precio Unitario</th>
<th>Subtotal</th>
</tr>
<tr>
<td>2</td>
<td>Rodillera ortopédica</td>
<td>$5.000</td>
<td>$10.000</td>
</tr>
<tr>
<td>1</td>
<td>Férula de inmovilización</td>
<td>$8.000</td>
<td>$8.000</td>
</tr>
</table>
</div>
<div class='totals'>
<table>
<tr>
<th>Subtotal</th>
<td>$18.000</td>
</tr>
<tr>
<th>IVA (21%)</th>
<td>$3.780</td>
</tr>
<tr>
<th>Total</th>
<td><strong>$21.780</strong></td>
</tr>
</table>
</div>
<div class='footer'>
Presupuesto generado automáticamente por PhronCare - No válido como factura.
</div>
</body>
</html>";
string outputFolder = @"C:\temp";
if (!Directory.Exists(outputFolder))
Directory.CreateDirectory(outputFolder);
string outputPath = Path.Combine(outputFolder, "DemoTest_Puppeteer.pdf");
// Opcional: podés probar pasando o no opciones
var options = new PdfGenerationOptions
{
Format = PuppeteerSharp.Media.PaperFormat.A4,
Landscape = false,
PrintBackground = true,
Scale = 1.0m,
HeaderTemplate = "<div style='font-size:10px; text-align:center;'>Presupuesto</div>",
FooterTemplate = "<div style='font-size:10px; text-align:center;'>Página <span class='pageNumber'></span> de <span class='totalPages'></span></div>"
};
// Act
byte[] pdfBytes = await _pdfService.GeneratePdfFromHtmlAsync(html, options);
await File.WriteAllBytesAsync(outputPath, pdfBytes);
// Assert
Assert.IsTrue(File.Exists(outputPath));
Assert.IsTrue(new FileInfo(outputPath).Length > 0);
TestContext.WriteLine($"PDF generado correctamente en: {outputPath}");
}
}
}

View File

@ -31,7 +31,11 @@
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"projectReferences": {}
"projectReferences": {
"C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Transversal\\Transversal.csproj": {
"projectPath": "C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Transversal\\Transversal.csproj"
}
}
}
},
"warningProperties": {
@ -98,6 +102,81 @@
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.201/PortableRuntimeIdentifierGraph.json"
}
}
},
"C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Transversal\\Transversal.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Transversal\\Transversal.csproj",
"projectName": "Transversal",
"projectPath": "C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Transversal\\Transversal.csproj",
"packagesPath": "C:\\Users\\maski\\.nuget\\packages\\",
"outputPath": "C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Transversal\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\maski\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net8.0"
],
"sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.200"
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"dependencies": {
"EPPlus": {
"target": "Package",
"version": "[8.0.4, )"
},
"PuppeteerSharp": {
"target": "Package",
"version": "[6.0.0, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.201/PortableRuntimeIdentifierGraph.json"
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@
</ItemGroup>
<ItemGroup>
<!--<ProjectReference Include="..\Models\Models.csproj" />-->
<ProjectReference Include="..\Transversal\Transversal.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,46 @@
@page "/testpdf"
@inject HttpClient Http
@inject IJSRuntime JS
<h3>Visualizador PDF de prueba (servidor + local + descarga)</h3>
<div class="mb-3">
<button class="btn btn-primary me-2" @onclick="SavePdf">
<i class="fas fa-download me-1"></i> Guardar
</button>
<button class="btn btn-success me-2" @onclick="ViewPdf">
<i class="fas fa-eye me-1"></i> Ver
</button>
</div>
@if (!string.IsNullOrEmpty(LocalPdfPath))
{
<iframe src="@LocalPdfPath" width="100%" height="800px" style="border: 1px solid #ddd;"></iframe>
}
@code {
private string LocalPdfPath;
private async Task SavePdf()
{
string path = @"C:\temp\PresupuestoTest.pdf";
if (!Directory.Exists(@"C:\temp"))
Directory.CreateDirectory(@"C:\temp");
// ✅ 1. Llamar al endpoint de API para generar PDF
var pdfBytes = await Http.GetByteArrayAsync("http://localhost:5243/api/PdfTest/test"); // Cambia a tu URL real
// ✅ 2. Guardar en disco
System.IO.File.WriteAllBytes(path, pdfBytes);
// ✅ 3. Descargar al usuario
await JS.InvokeVoidAsync("saveAsFile", "PresupuestoTest.pdf", Convert.ToBase64String(pdfBytes));
}
private void ViewPdf()
{
// 👉 Cargar el archivo guardado
LocalPdfPath = "/pdf/DemoTest.pdf";
}
}

View File

@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Blazored.Modal;
using Blazored.Toast;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
@ -48,7 +47,6 @@ static void InjectDependencies(WebAssemblyHostBuilder builder)
builder.Services.AddScoped<ISalesLookupService, SalesLookupService>();
builder.Services.AddScoped<IExchangeRateService, ExchangeRateService>();
builder.Services.AddScoped<ExchangeRateService>();
builder.Services.AddScoped<QuoteService>();
builder.Services.AddScoped<TicketsService>();
@ -64,4 +62,5 @@ static void InjectDependencies(WebAssemblyHostBuilder builder)
builder.Services.AddScoped<ProfessionalSpecialtyService>();
builder.Services.AddScoped<ProductCategoryService>();
builder.Services.AddScoped<PatientService>();
}

View File

@ -67,7 +67,7 @@
{
<ul class="nav-flex-column">
<div class="nav-item px-1">
<NavLink class="nav-link" href="sales/customers/">
<NavLink class="nav-link" href="Testpdf/">
<li aria-hidden="true"></li> Clientes
</NavLink>
</div>

View File

@ -239,7 +239,11 @@
"dependencies": {
"EPPlus": {
"target": "Package",
"version": "[7.5.2, )"
"version": "[8.0.4, )"
},
"PuppeteerSharp": {
"target": "Package",
"version": "[6.0.0, )"
}
},
"imports": [

View File

@ -22,6 +22,7 @@
<Import Project="$(NuGetPackageRoot)blazored.modal\7.3.1\buildTransitive\Blazored.Modal.props" Condition="Exists('$(NuGetPackageRoot)blazored.modal\7.3.1\buildTransitive\Blazored.Modal.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<PkgNewtonsoft_Json Condition=" '$(PkgNewtonsoft_Json)' == '' ">C:\Users\maski\.nuget\packages\newtonsoft.json\10.0.3</PkgNewtonsoft_Json>
<PkgMicrosoft_NET_Sdk_WebAssembly_Pack Condition=" '$(PkgMicrosoft_NET_Sdk_WebAssembly_Pack)' == '' ">C:\Users\maski\.nuget\packages\microsoft.net.sdk.webassembly.pack\9.0.3</PkgMicrosoft_NET_Sdk_WebAssembly_Pack>
<PkgMicrosoft_NET_ILLink_Tasks Condition=" '$(PkgMicrosoft_NET_ILLink_Tasks)' == '' ">C:\Users\maski\.nuget\packages\microsoft.net.illink.tasks\8.0.14</PkgMicrosoft_NET_ILLink_Tasks>
<PkgMicrosoft_AspNetCore_Components_WebAssembly_DevServer Condition=" '$(PkgMicrosoft_AspNetCore_Components_WebAssembly_DevServer)' == '' ">C:\Users\maski\.nuget\packages\microsoft.aspnetcore.components.webassembly.devserver\8.0.6</PkgMicrosoft_AspNetCore_Components_WebAssembly_DevServer>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.net.sdk.webassembly.pack\9.0.3\build\Microsoft.NET.Sdk.WebAssembly.Pack.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.net.sdk.webassembly.pack\9.0.3\build\Microsoft.NET.Sdk.WebAssembly.Pack.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\8.0.2\buildTransitive\net6.0\Microsoft.Extensions.Options.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.1\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.1\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.net.sdk.webassembly.pack\9.0.3\build\Microsoft.NET.Sdk.WebAssembly.Pack.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.net.sdk.webassembly.pack\9.0.3\build\Microsoft.NET.Sdk.WebAssembly.Pack.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder\8.0.1\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder\8.0.1\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.aspnetcore.components.webassembly.devserver\8.0.6\build\Microsoft.AspNetCore.Components.WebAssembly.DevServer.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.aspnetcore.components.webassembly.devserver\8.0.6\build\Microsoft.AspNetCore.Components.WebAssembly.DevServer.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.aspnetcore.components.analyzers\8.0.6\buildTransitive\netstandard2.0\Microsoft.AspNetCore.Components.Analyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.aspnetcore.components.analyzers\8.0.6\buildTransitive\netstandard2.0\Microsoft.AspNetCore.Components.Analyzers.targets')" />

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,27 +0,0 @@
[
{
"date": "2022-01-06",
"temperatureC": 1,
"summary": "Freezing"
},
{
"date": "2022-01-07",
"temperatureC": 14,
"summary": "Bracing"
},
{
"date": "2022-01-08",
"temperatureC": -13,
"summary": "Freezing"
},
{
"date": "2022-01-09",
"temperatureC": -16,
"summary": "Balmy"
},
{
"date": "2022-01-10",
"temperatureC": -2,
"summary": "Chilly"
}
]