"""Utilidades de exportación a Excel y PDF.""" import io import os from datetime import datetime import pandas as pd try: from fpdf import FPDF PDF_AVAILABLE = True except ImportError: PDF_AVAILABLE = False try: import openpyxl # noqa: F401 — solo para verificar que está disponible EXCEL_AVAILABLE = True except ImportError: EXCEL_AVAILABLE = False def _pdf_safe(s) -> str: """La fuente core del PDF (Helvetica) solo soporta latin-1. Quitamos emojis y reemplazamos caracteres no soportados (guiones largos, etc.).""" if s is None: return "" s = (str(s) .replace("–", "-").replace("—", "-").replace("•", "-") .replace("✅", "[OK]").replace("👤", "(C)").replace("🆘", "").replace("…", "...")) return s.encode("latin-1", "ignore").decode("latin-1").strip() def _df_para_export(rows: list[dict]) -> pd.DataFrame: if not rows: return pd.DataFrame() df = pd.DataFrame(rows) cols_rename = { "nombre": "Nombre", "cedula": "Cédula", "edad": "Edad", "hospital": "Hospital", "condicion": "Condición", "descripcion": "Descripción (IA)", "fecha_ingreso": "Fecha de ingreso", "fecha_update": "Última actualización", "verificado": "Verificado", "contacto": "Contacto familiar", "notas": "Notas", "fuente": "Fuente del reporte", } df = df.rename(columns={k: v for k, v in cols_rename.items() if k in df.columns}) df["Verificado"] = df["Verificado"].apply(lambda x: "Sí" if x else "No") cols_orden = ["Nombre", "Cédula", "Edad", "Hospital", "Condición", "Última actualización", "Verificado", "Contacto familiar", "Notas", "Descripción (IA)", "Fuente del reporte", "Fecha de ingreso"] cols_presentes = [c for c in cols_orden if c in df.columns] return df[cols_presentes] def exportar_excel(rows: list[dict]) -> str | None: if not rows: return None df = _df_para_export(rows) ts = datetime.now().strftime("%Y%m%d_%H%M%S") path = f"/tmp/busqueda_familiar_{ts}.xlsx" with pd.ExcelWriter(path, engine="openpyxl") as writer: df.to_excel(writer, index=False, sheet_name="Personas") ws = writer.sheets["Personas"] # ajustar anchos anchos = {"Nombre": 28, "Cédula": 14, "Edad": 6, "Hospital": 32, "Condición": 16, "Última actualización": 20, "Verificado": 10, "Contacto familiar": 18, "Notas": 30, "Descripción (IA)": 40, "Fuente del reporte": 18, "Fecha de ingreso": 20} for i, col in enumerate(df.columns, 1): ws.column_dimensions[ws.cell(1, i).column_letter].width = anchos.get(col, 15) return path class _PDF(FPDF): def __init__(self, titulo: str): super().__init__(orientation="L", unit="mm", format="A4") self._titulo = titulo def header(self): self.set_font("Helvetica", "B", 13) self.set_fill_color(185, 28, 28) self.set_text_color(255, 255, 255) self.cell(0, 10, _pdf_safe(self._titulo), align="C", fill=True, new_x="LMARGIN", new_y="NEXT") self.set_text_color(0, 0, 0) self.set_font("Helvetica", size=8) self.cell(0, 6, _pdf_safe(f"Generado: {datetime.now().strftime('%d/%m/%Y %H:%M')}"), align="R", new_x="LMARGIN", new_y="NEXT") self.ln(2) def footer(self): self.set_y(-12) self.set_font("Helvetica", "I", 8) self.set_text_color(120, 120, 120) self.cell(0, 6, _pdf_safe(f"Plataforma Buscador Familiar - Terremoto Venezuela | Pag. {self.page_no()}"), align="C") def exportar_pdf(rows: list[dict]) -> str | None: if not rows or not PDF_AVAILABLE: return None df = _df_para_export(rows) # columnas que caben bien en landscape A4 cols_pdf = ["Nombre", "Cédula", "Edad", "Hospital", "Condición", "Última actualización", "Verificado"] cols_pdf = [c for c in cols_pdf if c in df.columns] anchos_pdf = { "Nombre": 55, "Cédula": 26, "Edad": 12, "Hospital": 60, "Condición": 28, "Última actualización": 36, "Verificado": 18, } pdf = _PDF("Busqueda de Familiares - Terremoto Venezuela") pdf.add_page() # cabecera de tabla pdf.set_font("Helvetica", "B", 9) pdf.set_fill_color(220, 38, 38) pdf.set_text_color(255, 255, 255) for col in cols_pdf: pdf.cell(anchos_pdf.get(col, 30), 8, _pdf_safe(col), border=1, fill=True, align="C") pdf.ln() # filas pdf.set_font("Helvetica", size=8) pdf.set_text_color(0, 0, 0) fill = False for _, row in df.iterrows(): pdf.set_fill_color(255, 235, 235) if fill else pdf.set_fill_color(255, 255, 255) for col in cols_pdf: val = _pdf_safe(row.get(col, "")) if len(val) > 35: val = val[:33] + "..." pdf.cell(anchos_pdf.get(col, 30), 7, val, border=1, fill=True) pdf.ln() fill = not fill # nota al pie del contenido pdf.ln(4) pdf.set_font("Helvetica", "I", 7) pdf.set_text_color(100, 100, 100) pdf.multi_cell(0, 5, _pdf_safe( "AVISO: Esta informacion es de caracter orientativo. Verifique siempre con el personal " "del hospital. [OK] = Verificado por personal medico - No = Reportado por ciudadano.")) ts = datetime.now().strftime("%Y%m%d_%H%M%S") path = f"/tmp/busqueda_familiar_{ts}.pdf" pdf.output(path) return path