first commit
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:pdf/pdf.dart';
|
||||
import 'package:pdf/widgets.dart' as pw;
|
||||
import 'package:printing/printing.dart';
|
||||
|
||||
class InsuranceAuthPdfService {
|
||||
/// Genera el documento PDF de Autorización de Seguro en formato Media Carta Horizontal
|
||||
/// (8.5 x 5.5 pulgadas / 612 x 396 pt), idéntico a AlfaMedical.
|
||||
static Future<Uint8List> buildInsuranceAuthPdf({
|
||||
required String doctorName,
|
||||
required String specialty,
|
||||
required String subspecialty,
|
||||
required String generalContact,
|
||||
required String arsName,
|
||||
required String patientName,
|
||||
required String surgeryText,
|
||||
required String diagnosisText,
|
||||
String? anesthesia,
|
||||
String? daysOfStay,
|
||||
DateTime? date,
|
||||
pw.Font? fontRegular,
|
||||
pw.Font? fontBold,
|
||||
pw.Font? fontTitle,
|
||||
String? logoSvg,
|
||||
}) async {
|
||||
final pdf = pw.Document(title: 'Autorización de seguro - $patientName');
|
||||
final formattedDate = DateFormat('dd/MM/yyyy').format(date ?? DateTime.now());
|
||||
|
||||
// Formato exacto e idéntico a AlfaMedical (ReportLab):
|
||||
// pagesize = (8.5*inch, 5.5*inch)
|
||||
// leftMargin = 72, rightMargin = 72, topMargin = 18, bottomMargin = 18.
|
||||
const pageFormat = PdfPageFormat(
|
||||
8.5 * PdfPageFormat.inch,
|
||||
5.5 * PdfPageFormat.inch,
|
||||
marginLeft: 72,
|
||||
marginRight: 72,
|
||||
marginTop: 18,
|
||||
marginBottom: 18,
|
||||
);
|
||||
|
||||
// Tipografías estándar con fallback a fuentes base PDF si no hay conexión a internet
|
||||
pw.Font regular = fontRegular ?? pw.Font.helvetica();
|
||||
pw.Font bold = fontBold ?? pw.Font.helveticaBold();
|
||||
pw.Font titleFont = fontTitle ?? pw.Font.timesBoldItalic();
|
||||
|
||||
if (fontRegular == null || fontBold == null || fontTitle == null) {
|
||||
try {
|
||||
regular = await PdfGoogleFonts.robotoRegular();
|
||||
bold = await PdfGoogleFonts.robotoBold();
|
||||
titleFont = await PdfGoogleFonts.playfairDisplayItalic();
|
||||
} catch (_) {
|
||||
regular = pw.Font.helvetica();
|
||||
bold = pw.Font.helveticaBold();
|
||||
titleFont = pw.Font.timesBoldItalic();
|
||||
}
|
||||
}
|
||||
|
||||
String? svgContent = logoSvg;
|
||||
if (svgContent == null) {
|
||||
try {
|
||||
svgContent = await rootBundle.loadString('assets/images/hidalyx_logo_blue.svg');
|
||||
} catch (_) {
|
||||
svgContent = null;
|
||||
}
|
||||
}
|
||||
|
||||
pdf.addPage(
|
||||
pw.Page(
|
||||
pageFormat: pageFormat,
|
||||
build: (pw.Context context) {
|
||||
return pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 1. Cabecera con Nombre del Doctor y Badge Sutil de Hidalyx Med
|
||||
pw.Stack(
|
||||
children: [
|
||||
// Nombre y especialidad del doctor centrado
|
||||
pw.Align(
|
||||
alignment: pw.Alignment.center,
|
||||
child: pw.Column(
|
||||
children: [
|
||||
pw.Text(
|
||||
doctorName.isNotEmpty ? doctorName : 'Dr. Luis A. Castro Sosa',
|
||||
style: pw.TextStyle(
|
||||
font: titleFont,
|
||||
fontSize: 22,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColor.fromHex('1E293B'),
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 3),
|
||||
if (specialty.isNotEmpty)
|
||||
pw.Text(
|
||||
specialty.toUpperCase(),
|
||||
style: pw.TextStyle(
|
||||
font: bold,
|
||||
fontSize: 10.5,
|
||||
letterSpacing: 0.5,
|
||||
color: PdfColor.fromHex('0F172A'),
|
||||
),
|
||||
),
|
||||
if (subspecialty.isNotEmpty) ...[
|
||||
pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
subspecialty.toUpperCase(),
|
||||
textAlign: pw.TextAlign.center,
|
||||
style: pw.TextStyle(
|
||||
font: regular,
|
||||
fontSize: 8.5,
|
||||
color: PdfColor.fromHex('475569'),
|
||||
),
|
||||
),
|
||||
],
|
||||
pw.SizedBox(height: 3),
|
||||
pw.Text(
|
||||
'Fecha: $formattedDate',
|
||||
style: pw.TextStyle(
|
||||
font: regular,
|
||||
fontSize: 9.5,
|
||||
color: PdfColor.fromHex('334155'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Badge sutil de Hidalyx Med en la esquina superior derecha
|
||||
pw.Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: pw.Row(
|
||||
mainAxisSize: pw.MainAxisSize.min,
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (svgContent != null && svgContent.isNotEmpty) ...[
|
||||
pw.Opacity(
|
||||
opacity: 0.6,
|
||||
child: pw.SvgImage(
|
||||
svg: svgContent,
|
||||
width: 7.5,
|
||||
height: 7.5,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(width: 3),
|
||||
],
|
||||
pw.Text(
|
||||
'hidalyx.com',
|
||||
style: pw.TextStyle(
|
||||
font: regular,
|
||||
fontSize: 6.0,
|
||||
color: PdfColor.fromHex('94A3B8'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
pw.SizedBox(height: 6),
|
||||
|
||||
// Línea divisoria decorativa del membrete
|
||||
pw.Container(
|
||||
height: 1,
|
||||
color: PdfColor.fromHex('CBD5E1'),
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
|
||||
// 2. Encabezado de la Orden Médica (Símbolo Rx)
|
||||
pw.Row(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||
children: [
|
||||
pw.Container(
|
||||
padding: const pw.EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: pw.BoxDecoration(
|
||||
color: PdfColor.fromHex('E2E8F0'),
|
||||
borderRadius: pw.BorderRadius.circular(4),
|
||||
),
|
||||
child: pw.Text(
|
||||
'Rx',
|
||||
style: pw.TextStyle(
|
||||
font: bold,
|
||||
fontSize: 12,
|
||||
color: PdfColor.fromHex('00A65A'),
|
||||
),
|
||||
),
|
||||
),
|
||||
pw.SizedBox(width: 8),
|
||||
pw.Text(
|
||||
'SOLICITUD DE AUTORIZACIÓN QUIRÚRGICA',
|
||||
style: pw.TextStyle(
|
||||
font: bold,
|
||||
fontSize: 10,
|
||||
letterSpacing: 0.5,
|
||||
color: PdfColor.fromHex('475569'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
|
||||
// 3. Contenido Formal idéntico a AlfaMedical
|
||||
pw.Expanded(
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Señores ARS
|
||||
pw.RichText(
|
||||
text: pw.TextSpan(
|
||||
children: [
|
||||
pw.TextSpan(
|
||||
text: 'Señores ARS: ',
|
||||
style: pw.TextStyle(font: bold, fontSize: 11, color: PdfColor.fromHex('1E293B')),
|
||||
),
|
||||
pw.TextSpan(
|
||||
text: arsName.isNotEmpty ? arsName : '____________________________________',
|
||||
style: pw.TextStyle(font: regular, fontSize: 11, color: PdfColor.fromHex('0F172A')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 6),
|
||||
|
||||
// Favor autorizar al Sr. (a)
|
||||
pw.RichText(
|
||||
text: pw.TextSpan(
|
||||
children: [
|
||||
pw.TextSpan(
|
||||
text: 'Favor autorizar al Sr. (a): ',
|
||||
style: pw.TextStyle(font: bold, fontSize: 11, color: PdfColor.fromHex('1E293B')),
|
||||
),
|
||||
pw.TextSpan(
|
||||
text: patientName.isNotEmpty ? patientName : '___________________________',
|
||||
style: pw.TextStyle(font: regular, fontSize: 11, color: PdfColor.fromHex('0F172A')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 6),
|
||||
|
||||
// La cirugía de:
|
||||
pw.RichText(
|
||||
text: pw.TextSpan(
|
||||
children: [
|
||||
pw.TextSpan(
|
||||
text: 'La cirugía de: ',
|
||||
style: pw.TextStyle(font: bold, fontSize: 11, color: PdfColor.fromHex('1E293B')),
|
||||
),
|
||||
pw.TextSpan(
|
||||
text: surgeryText.trim().isNotEmpty ? surgeryText.trim() : '___________________________',
|
||||
style: pw.TextStyle(font: regular, fontSize: 11, color: PdfColor.fromHex('0F172A')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 6),
|
||||
|
||||
// Diagnóstico
|
||||
pw.RichText(
|
||||
text: pw.TextSpan(
|
||||
children: [
|
||||
pw.TextSpan(
|
||||
text: 'Diagnóstico: ',
|
||||
style: pw.TextStyle(font: bold, fontSize: 11, color: PdfColor.fromHex('1E293B')),
|
||||
),
|
||||
pw.TextSpan(
|
||||
text: diagnosisText.trim().isNotEmpty ? diagnosisText.trim() : '___________________________',
|
||||
style: pw.TextStyle(font: regular, fontSize: 11, color: PdfColor.fromHex('0F172A')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 6),
|
||||
|
||||
// Bajo Anestesia y Días de ingreso
|
||||
if ((anesthesia != null && anesthesia.trim().isNotEmpty) ||
|
||||
(daysOfStay != null && daysOfStay.trim().isNotEmpty)) ...[
|
||||
pw.RichText(
|
||||
text: pw.TextSpan(
|
||||
children: [
|
||||
if (anesthesia != null && anesthesia.trim().isNotEmpty) ...[
|
||||
pw.TextSpan(
|
||||
text: 'Bajo Anestesia: ',
|
||||
style: pw.TextStyle(font: bold, fontSize: 11, color: PdfColor.fromHex('1E293B')),
|
||||
),
|
||||
pw.TextSpan(
|
||||
text: anesthesia.trim(),
|
||||
style: pw.TextStyle(font: regular, fontSize: 11, color: PdfColor.fromHex('0F172A')),
|
||||
),
|
||||
],
|
||||
if (daysOfStay != null && daysOfStay.trim().isNotEmpty) ...[
|
||||
pw.TextSpan(
|
||||
text: ' Con ',
|
||||
style: pw.TextStyle(font: bold, fontSize: 11, color: PdfColor.fromHex('1E293B')),
|
||||
),
|
||||
pw.TextSpan(
|
||||
text: '${daysOfStay.trim()} Días de ingreso',
|
||||
style: pw.TextStyle(font: regular, fontSize: 11, color: PdfColor.fromHex('0F172A')),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 6),
|
||||
|
||||
// 4. Pie de página con Contacto del Consultorio
|
||||
if (generalContact.isNotEmpty) ...[
|
||||
pw.Container(
|
||||
padding: const pw.EdgeInsets.only(top: 4),
|
||||
child: pw.Text(
|
||||
generalContact.toUpperCase(),
|
||||
textAlign: pw.TextAlign.center,
|
||||
style: pw.TextStyle(
|
||||
font: regular,
|
||||
fontSize: 6.8,
|
||||
color: PdfColor.fromHex('64748B'),
|
||||
lineSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
pw.SizedBox(height: 3),
|
||||
|
||||
// Confirmación institucional
|
||||
pw.Center(
|
||||
child: pw.Text(
|
||||
'Documento médico emitido electrónicamente',
|
||||
style: pw.TextStyle(
|
||||
font: regular,
|
||||
fontSize: 6.0,
|
||||
color: PdfColor.fromHex('94A3B8'),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return pdf.save();
|
||||
}
|
||||
|
||||
/// Envía a imprimir directamente o abre el diálogo nativo de impresión/PDF.
|
||||
static Future<void> printInsuranceAuth({
|
||||
required String doctorName,
|
||||
required String specialty,
|
||||
required String subspecialty,
|
||||
required String generalContact,
|
||||
required String arsName,
|
||||
required String patientName,
|
||||
required String surgeryText,
|
||||
required String diagnosisText,
|
||||
String? anesthesia,
|
||||
String? daysOfStay,
|
||||
DateTime? date,
|
||||
}) async {
|
||||
final pdfBytes = await buildInsuranceAuthPdf(
|
||||
doctorName: doctorName,
|
||||
specialty: specialty,
|
||||
subspecialty: subspecialty,
|
||||
generalContact: generalContact,
|
||||
arsName: arsName,
|
||||
patientName: patientName,
|
||||
surgeryText: surgeryText,
|
||||
diagnosisText: diagnosisText,
|
||||
anesthesia: anesthesia,
|
||||
daysOfStay: daysOfStay,
|
||||
date: date,
|
||||
);
|
||||
|
||||
await Printing.layoutPdf(
|
||||
onLayout: (PdfPageFormat format) async => pdfBytes,
|
||||
name: 'Autorizacion_Seguro_${patientName.replaceAll(' ', '_')}',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user