402 lines
15 KiB
Dart
402 lines
15 KiB
Dart
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 Vertical
|
|
/// (5.5 x 8.5 pulgadas / 396 x 612 pt).
|
|
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 Carta Vertical (8.5 x 11.0 pulgadas / 612 x 792 pt):
|
|
// Todo el contenido abarca estrictamente la mitad superior de la hoja (5.5 pulgadas / 396 pt),
|
|
// dejando la mitad inferior libre para corte o papel pre-cortado.
|
|
const pageFormat = PdfPageFormat(
|
|
8.5 * PdfPageFormat.inch,
|
|
11.0 * PdfPageFormat.inch,
|
|
marginLeft: 54,
|
|
marginRight: 54,
|
|
marginTop: 20,
|
|
marginBottom: 20,
|
|
);
|
|
|
|
// 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: [
|
|
// Mitad superior de la hoja (máximo 5.5 pulgadas / 396 pt)
|
|
pw.ConstrainedBox(
|
|
constraints: const pw.BoxConstraints(
|
|
maxHeight: 5.5 * PdfPageFormat.inch - 36,
|
|
),
|
|
child: pw.Column(
|
|
mainAxisSize: pw.MainAxisSize.min,
|
|
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.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.stretch,
|
|
mainAxisSize: pw.MainAxisSize.min,
|
|
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: 10),
|
|
|
|
// Línea divisoria decorativa previa al pie
|
|
pw.Container(
|
|
height: 0.5,
|
|
color: PdfColor.fromHex('CBD5E1'),
|
|
),
|
|
pw.SizedBox(height: 5),
|
|
|
|
// 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(' ', '_')}',
|
|
);
|
|
}
|
|
}
|