Files
hidalyx-drluiscastro/lib/features/insurance_auth/services/insurance_auth_pdf_service.dart
T

402 lines
15 KiB
Dart
Raw Normal View History

2026-09-21 16:33:51 -04:00
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).
2026-09-21 16:33:51 -04:00
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.
2026-09-21 16:33:51 -04:00
const pageFormat = PdfPageFormat(
8.5 * PdfPageFormat.inch,
11.0 * PdfPageFormat.inch,
marginLeft: 28,
marginRight: 28,
marginTop: 16,
marginBottom: 16,
2026-09-21 16:33:51 -04:00
);
// 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 - 32,
),
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(
2026-09-21 16:33:51 -04:00
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: [
2026-09-21 16:33:51 -04:00
// 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'),
2026-09-21 16:33:51 -04:00
),
pw.SizedBox(height: 5),
2026-09-21 16:33:51 -04:00
// 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'),
),
),
),
],
),
),
],
);
2026-09-21 16:33:51 -04:00
},
),
);
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(' ', '_')}',
);
}
}