Files
hidalyx-drluiscastro/lib/features/prescription/services/prescription_pdf_service.dart
T

298 lines
12 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 PrescriptionPdfService {
/// Genera el documento PDF de la receta médica 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> buildPrescriptionPdf({
required String doctorName,
required String specialty,
required String subspecialty,
required String generalContact,
required String patientName,
required String prescriptionText,
DateTime? date,
pw.Font? fontRegular,
pw.Font? fontBold,
pw.Font? fontTitle,
String? logoSvg,
}) async {
final pdf = pw.Document(title: 'Receta Médica - $patientName');
final formattedDate = DateFormat('dd/MM/yyyy').format(date ?? DateTime.now());
// Formato Media Carta Vertical (5.5 x 8.5 pulgadas / 396 x 612 pt):
// Margen equilibrado de media pulgada (36 pt) a los lados y 20 pt vertical
2026-09-21 16:33:51 -04:00
const pageFormat = PdfPageFormat(
5.5 * PdfPageFormat.inch,
8.5 * PdfPageFormat.inch,
marginLeft: 36,
marginRight: 36,
marginTop: 20,
marginBottom: 20,
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 (_) {
// Fallback garantizado sin crash si está offline o en test
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 Superior 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 Prescripción: Símbolo Rx y Paciente
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.Expanded(
child: pw.RichText(
text: pw.TextSpan(
children: [
pw.TextSpan(
text: 'Paciente: ',
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: 8),
// 3. Contenido de la Receta / Prescripción Médica
pw.Expanded(
child: pw.Container(
padding: const pw.EdgeInsets.symmetric(vertical: 4),
child: pw.Text(
prescriptionText.trim().isNotEmpty
? prescriptionText.trim()
: 'Sin prescripciones anotadas.',
style: pw.TextStyle(
font: regular,
fontSize: 10,
lineSpacing: 3,
color: PdfColor.fromHex('1E293B'),
),
),
),
),
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> printPrescription({
required String doctorName,
required String specialty,
required String subspecialty,
required String generalContact,
required String patientName,
required String prescriptionText,
DateTime? date,
}) async {
final pdfBytes = await buildPrescriptionPdf(
doctorName: doctorName,
specialty: specialty,
subspecialty: subspecialty,
generalContact: generalContact,
patientName: patientName,
prescriptionText: prescriptionText,
date: date,
);
await Printing.layoutPdf(
onLayout: (PdfPageFormat format) async => pdfBytes,
name: 'Receta_${patientName.replaceAll(' ', '_')}',
);
}
}