Files

401 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: 28,
marginRight: 28,
marginTop: 16,
marginBottom: 16,
);
// 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: altura fija de media carta (5.5 pulgadas / 396 pt)
// para que el pie de página siempre quede anclado al final de la media hoja.
pw.SizedBox(
height: 5.5 * PdfPageFormat.inch - 32,
child: 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 (ocupa el espacio central disponible)
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),
// 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(' ', '_')}',
);
}
}