assemblerpdf 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. assemblerpdf-1.0.0/MANIFEST.in +4 -0
  2. assemblerpdf-1.0.0/PKG-INFO +86 -0
  3. assemblerpdf-1.0.0/README.md +63 -0
  4. assemblerpdf-1.0.0/pyproject.toml +47 -0
  5. assemblerpdf-1.0.0/setup.cfg +4 -0
  6. assemblerpdf-1.0.0/src/assemblerpdf/__init__.py +16 -0
  7. assemblerpdf-1.0.0/src/assemblerpdf/assemblers/__init__.py +9 -0
  8. assemblerpdf-1.0.0/src/assemblerpdf/assemblers/base.py +120 -0
  9. assemblerpdf-1.0.0/src/assemblerpdf/assemblers/form.py +257 -0
  10. assemblerpdf-1.0.0/src/assemblerpdf/assemblers/report.py +44 -0
  11. assemblerpdf-1.0.0/src/assemblerpdf/core.py +193 -0
  12. assemblerpdf-1.0.0/src/assemblerpdf/fonts/Inter-Bold.ttf +0 -0
  13. assemblerpdf-1.0.0/src/assemblerpdf/fonts/Inter-Medium.ttf +0 -0
  14. assemblerpdf-1.0.0/src/assemblerpdf/fonts/Inter-Regular.ttf +0 -0
  15. assemblerpdf-1.0.0/src/assemblerpdf/img/Logo-unp-rojo.png +0 -0
  16. assemblerpdf-1.0.0/src/assemblerpdf/img/logo-mininterior-rojo.jpg +0 -0
  17. assemblerpdf-1.0.0/src/assemblerpdf/templates/assemblerpdf/checkbox.html +16 -0
  18. assemblerpdf-1.0.0/src/assemblerpdf/templates/assemblerpdf/footer.html +16 -0
  19. assemblerpdf-1.0.0/src/assemblerpdf/templates/assemblerpdf/grid_row.html +7 -0
  20. assemblerpdf-1.0.0/src/assemblerpdf/templates/assemblerpdf/header.html +19 -0
  21. assemblerpdf-1.0.0/src/assemblerpdf/templates/assemblerpdf/input.html +12 -0
  22. assemblerpdf-1.0.0/src/assemblerpdf/templates/assemblerpdf/radio.html +14 -0
  23. assemblerpdf-1.0.0/src/assemblerpdf/templates/assemblerpdf/section.html +6 -0
  24. assemblerpdf-1.0.0/src/assemblerpdf/templates/assemblerpdf/signature.html +14 -0
  25. assemblerpdf-1.0.0/src/assemblerpdf/templates/assemblerpdf/style.html +318 -0
  26. assemblerpdf-1.0.0/src/assemblerpdf/templates/assemblerpdf/textarea.html +12 -0
  27. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/anexos.html +38 -0
  28. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/anexos_limpio.html +52 -0
  29. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/archivese.html +18 -0
  30. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/autoriza_envio.html +33 -0
  31. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/checkbox.html +41 -0
  32. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/control_cambios.html +43 -0
  33. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/datos_basicosinf.html +16 -0
  34. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/datos_ot.html +33 -0
  35. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/encaso_de.html +5 -0
  36. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fiila_dos_check.html +54 -0
  37. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_cinco_zona.html +54 -0
  38. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_cuatro.html +20 -0
  39. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_cuatro_si_no.html +46 -0
  40. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_cuatro_zona.html +48 -0
  41. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_dos.html +12 -0
  42. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_dos_zona.html +40 -0
  43. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_instructivo.html +16 -0
  44. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_tres.html +16 -0
  45. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_tres_checkbox.html +66 -0
  46. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_tres_zona.html +45 -0
  47. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/fila_uno.html +8 -0
  48. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/firma.html +10 -0
  49. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/firma_doble.html +32 -0
  50. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/firmaop2.html +17 -0
  51. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/firmas_oficializacion.html +66 -0
  52. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/hechos_sobrevinientes.html +261 -0
  53. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/hechos_sobrevinientes_vacio.html +209 -0
  54. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/iformfilacuatro.html +20 -0
  55. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/informacion_doble.html +0 -0
  56. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/input_2checks.html +98 -0
  57. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/input_check.html +40 -0
  58. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/input_checkbox.html +53 -0
  59. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/input_solitario.html +9 -0
  60. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/label_input_dos.html +24 -0
  61. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/leyenda_autoriza_correo.html +8 -0
  62. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/leyenda_superiol.html +5 -0
  63. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/logos.html +4 -0
  64. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/manifiesto.html +41 -0
  65. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/notificacion_correo.html +14 -0
  66. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/oficializacion.html +21 -0
  67. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/pregunta_si_no.html +34 -0
  68. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/pregunta_tresopciones.html +47 -0
  69. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/qr_oficializacion.html +7 -0
  70. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/situacion_amenaza.html +82 -0
  71. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/style.html +228 -0
  72. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/subtitulo_icono.html +6 -0
  73. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/subtitulo_icono_largo.html +6 -0
  74. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/tabla_doble.html +15 -0
  75. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/tabla_instructivo.html +12 -0
  76. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/tabla_matriz_riesgo.html +135 -0
  77. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/tabla_proyeccion.html +37 -0
  78. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/tabla_simple.html +187 -0
  79. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/texarea.html +9 -0
  80. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/tipo_identificacion.html +55 -0
  81. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/tipos_checkbox.html +47 -0
  82. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/titulo.html +6 -0
  83. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/titulo_logo.html +22 -0
  84. assemblerpdf-1.0.0/src/assemblerpdf/templates/sistema/titulo_principal.html +5 -0
  85. assemblerpdf-1.0.0/src/assemblerpdf/utils/__init__.py +1 -0
  86. assemblerpdf-1.0.0/src/assemblerpdf/utils/template_loader.py +48 -0
  87. assemblerpdf-1.0.0/src/assemblerpdf.egg-info/PKG-INFO +86 -0
  88. assemblerpdf-1.0.0/src/assemblerpdf.egg-info/SOURCES.txt +89 -0
  89. assemblerpdf-1.0.0/src/assemblerpdf.egg-info/dependency_links.txt +1 -0
  90. assemblerpdf-1.0.0/src/assemblerpdf.egg-info/requires.txt +4 -0
  91. assemblerpdf-1.0.0/src/assemblerpdf.egg-info/top_level.txt +1 -0
@@ -0,0 +1,4 @@
1
+ recursive-include src/assemblerpdf/templates *.html
2
+ recursive-include src/assemblerpdf/fonts *.ttf
3
+ recursive-include src/assemblerpdf/img *.png *.jpg
4
+ include README.md
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: assemblerpdf
3
+ Version: 1.0.0
4
+ Summary: Generador de PDFs usando WeasyPrint, diseñado para formularios y reportes con componentes genéricos.
5
+ Author-email: EcosistemaUNP <ecosistema@unp.gov.co>
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: django>=3.2
20
+ Requires-Dist: weasyprint>=60.0
21
+ Requires-Dist: jinja2>=3.0.0
22
+ Requires-Dist: requests>=2.25.0
23
+
24
+ # 🚀 assemblerpdf
25
+
26
+ > ✨ **Generador de PDFs dinámicos utilizando WeasyPrint con componentes HTML genéricos y reutilizables.**
27
+
28
+ Esta librería permite ensamblar PDFs complejos utilizando un conjunto de componentes reutilizables (`grid_row`, `input`, `textarea`, `checkbox`, `radio`, etc.), con soporte para la fuente **Inter** descargada automáticamente, y con instalador automatizado de dependencias del sistema operativo (Pango, Cairo, etc.).
29
+
30
+ ---
31
+
32
+ ## ⚡ Instalación y Configuración
33
+
34
+ Puedes instalar la librería localmente para desarrollo:
35
+
36
+ ```bash
37
+ pip install -e .
38
+ ```
39
+
40
+ E instalar las dependencias del sistema (como Cairo/Pango para WeasyPrint) y descargar las fuentes corriendo el siguiente comando en Python:
41
+
42
+ ```python
43
+ import assemblerpdf
44
+ assemblerpdf.install_dependencies()
45
+ ```
46
+
47
+ ---
48
+
49
+ ## 🛠️ Uso Básico
50
+
51
+ ```python
52
+ from assemblerpdf.assemblers.base import BaseFormAssembler
53
+
54
+ # Crear contexto de datos
55
+ context = {
56
+ "nombreCompleto": "Juan Pérez",
57
+ "fechaDiligenciamiento": "2026-06-03"
58
+ }
59
+
60
+ # Inicializar ensamblador
61
+ assembler = BaseFormAssembler(context)
62
+
63
+ # Agregar componentes genéricos
64
+ assembler.add_component("header.html", {
65
+ "titulo1": "FORMULARIO DE PRUEBA",
66
+ "titulo2": "SUBTÍTULO",
67
+ "titulo3": "UNIDAD NACIONAL DE PROTECCIÓN"
68
+ })
69
+
70
+ assembler.add_component("section.html", {
71
+ "titulo": "Datos de Identificación",
72
+ "icono": "fa-user"
73
+ })
74
+
75
+ # Agregar fila de inputs
76
+ assembler.add_grid_row([
77
+ {"template": "input.html", "context": {"label": "Nombre Completo", "valor": context["nombreCompleto"], "ancho": "70%"}},
78
+ {"template": "input.html", "context": {"label": "Fecha", "valor": context["fechaDiligenciamiento"], "ancho": "30%"}}
79
+ ])
80
+
81
+ # Construir PDF
82
+ pdf_bytes = assembler.build()
83
+
84
+ with open("test.pdf", "wb") as f:
85
+ f.write(pdf_bytes)
86
+ ```
@@ -0,0 +1,63 @@
1
+ # 🚀 assemblerpdf
2
+
3
+ > ✨ **Generador de PDFs dinámicos utilizando WeasyPrint con componentes HTML genéricos y reutilizables.**
4
+
5
+ Esta librería permite ensamblar PDFs complejos utilizando un conjunto de componentes reutilizables (`grid_row`, `input`, `textarea`, `checkbox`, `radio`, etc.), con soporte para la fuente **Inter** descargada automáticamente, y con instalador automatizado de dependencias del sistema operativo (Pango, Cairo, etc.).
6
+
7
+ ---
8
+
9
+ ## ⚡ Instalación y Configuración
10
+
11
+ Puedes instalar la librería localmente para desarrollo:
12
+
13
+ ```bash
14
+ pip install -e .
15
+ ```
16
+
17
+ E instalar las dependencias del sistema (como Cairo/Pango para WeasyPrint) y descargar las fuentes corriendo el siguiente comando en Python:
18
+
19
+ ```python
20
+ import assemblerpdf
21
+ assemblerpdf.install_dependencies()
22
+ ```
23
+
24
+ ---
25
+
26
+ ## 🛠️ Uso Básico
27
+
28
+ ```python
29
+ from assemblerpdf.assemblers.base import BaseFormAssembler
30
+
31
+ # Crear contexto de datos
32
+ context = {
33
+ "nombreCompleto": "Juan Pérez",
34
+ "fechaDiligenciamiento": "2026-06-03"
35
+ }
36
+
37
+ # Inicializar ensamblador
38
+ assembler = BaseFormAssembler(context)
39
+
40
+ # Agregar componentes genéricos
41
+ assembler.add_component("header.html", {
42
+ "titulo1": "FORMULARIO DE PRUEBA",
43
+ "titulo2": "SUBTÍTULO",
44
+ "titulo3": "UNIDAD NACIONAL DE PROTECCIÓN"
45
+ })
46
+
47
+ assembler.add_component("section.html", {
48
+ "titulo": "Datos de Identificación",
49
+ "icono": "fa-user"
50
+ })
51
+
52
+ # Agregar fila de inputs
53
+ assembler.add_grid_row([
54
+ {"template": "input.html", "context": {"label": "Nombre Completo", "valor": context["nombreCompleto"], "ancho": "70%"}},
55
+ {"template": "input.html", "context": {"label": "Fecha", "valor": context["fechaDiligenciamiento"], "ancho": "30%"}}
56
+ ])
57
+
58
+ # Construir PDF
59
+ pdf_bytes = assembler.build()
60
+
61
+ with open("test.pdf", "wb") as f:
62
+ f.write(pdf_bytes)
63
+ ```
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "assemblerpdf"
7
+ version = "1.0.0"
8
+ description = "Generador de PDFs usando WeasyPrint, diseñado para formularios y reportes con componentes genéricos."
9
+ readme = "README.md"
10
+ authors = [
11
+ {name = "EcosistemaUNP", email = "ecosistema@unp.gov.co"},
12
+ ]
13
+ license = {text = "MIT"}
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ ]
26
+ requires-python = ">=3.8"
27
+ dependencies = [
28
+ "django>=3.2",
29
+ "weasyprint>=60.0",
30
+ "jinja2>=3.0.0",
31
+ "requests>=2.25.0",
32
+ ]
33
+
34
+ [tool.setuptools]
35
+ package-dir = {"" = "src"}
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.setuptools.package-data]
41
+ assemblerpdf = [
42
+ "templates/assemblerpdf/*.html",
43
+ "templates/sistema/*.html",
44
+ "fonts/*.ttf",
45
+ "img/*.png",
46
+ "img/*.jpg"
47
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,16 @@
1
+ from .core import (
2
+ generate_pdf,
3
+ generate_pdf_from_string,
4
+ install_dependencies,
5
+ download_inter_font,
6
+ check_weasyprint_installed
7
+ )
8
+
9
+ __version__ = "1.0.0"
10
+ __all__ = [
11
+ "generate_pdf",
12
+ "generate_pdf_from_string",
13
+ "install_dependencies",
14
+ "download_inter_font",
15
+ "check_weasyprint_installed",
16
+ ]
@@ -0,0 +1,9 @@
1
+ from .base import BaseFormAssembler
2
+ from .form import FormularioReposicionAssembler
3
+ from .report import TwoColumnReportAssembler
4
+
5
+ __all__ = [
6
+ "BaseFormAssembler",
7
+ "FormularioReposicionAssembler",
8
+ "TwoColumnReportAssembler"
9
+ ]
@@ -0,0 +1,120 @@
1
+ import os
2
+ from assemblerpdf.core import generate_pdf_from_string
3
+
4
+ class BaseFormAssembler:
5
+ def __init__(self, context=None):
6
+ self.context = context or {}
7
+ self.components = []
8
+
9
+ def _get_img_base(self):
10
+ import assemblerpdf
11
+ package_path = os.path.dirname(assemblerpdf.__file__)
12
+ img_dir = os.path.join(package_path, 'img')
13
+ img_dir = img_dir.replace('\\', '/')
14
+ return f'file:///{img_dir}'
15
+
16
+ def _render(self, template_name, extra_context=None):
17
+ """Renderiza un componente usando Django (si está disponible) o Jinja2."""
18
+ # Unificar contexto
19
+ full_context = {
20
+ 'img_base': self._get_img_base(),
21
+ **self.context,
22
+ **(extra_context or {})
23
+ }
24
+
25
+ # Intentar usar Django primero si está configurado
26
+ try:
27
+ from django.template.loader import render_to_string
28
+ # Asegurar prefijo de carpeta
29
+ if not template_name.startswith("assemblerpdf/"):
30
+ django_path = f"assemblerpdf/{template_name}"
31
+ else:
32
+ django_path = template_name
33
+ return render_to_string(django_path, full_context)
34
+ except Exception:
35
+ # Fallback a Jinja2
36
+ from assemblerpdf.utils.template_loader import render_template
37
+ return render_template(template_name, full_context)
38
+
39
+ def add_component(self, template_name, extra_context=None, wrapper_html=None):
40
+ """
41
+ Agrega un componente individual al ensamblador.
42
+
43
+ Args:
44
+ template_name (str): Nombre del template (ej: 'input.html')
45
+ extra_context (dict): Contexto adicional para el template
46
+ wrapper_html (str): HTML envolvente opcional
47
+ """
48
+ rendered = self._render(template_name, extra_context)
49
+
50
+ if wrapper_html:
51
+ rendered = wrapper_html.replace('{{component}}', rendered)
52
+
53
+ self.components.append(rendered)
54
+ return self
55
+
56
+ def add_grid_row(self, fields):
57
+ """
58
+ Agrega una fila (grid_row) con múltiples campos alineados horizontalmente.
59
+
60
+ Args:
61
+ fields (list): Lista de diccionarios, cada uno representa un campo:
62
+ [
63
+ {"template": "input.html", "context": {"label": "Nombre", "valor": "Juan"}, "ancho": "50%"},
64
+ {"template": "input.html", "context": {"label": "Cédula", "valor": "1234"}, "ancho": "50%"}
65
+ ]
66
+ """
67
+ columns = []
68
+ for field in fields:
69
+ tpl = field.get("template")
70
+ ctx = field.get("context", {})
71
+ ancho = field.get("ancho", "100%")
72
+ flex = field.get("flex", "none")
73
+
74
+ # Renderizar el campo
75
+ rendered_field = self._render(tpl, ctx)
76
+ columns.append({
77
+ "ancho": ancho,
78
+ "flex": flex,
79
+ "content": rendered_field
80
+ })
81
+
82
+ # Renderizar la fila contenedora grid_row.html
83
+ row_html = self._render("grid_row.html", {"columns": columns})
84
+ self.components.append(row_html)
85
+ return self
86
+
87
+ def add_raw_html(self, html):
88
+ """Agrega HTML crudo al documento."""
89
+ self.components.append(html)
90
+ return self
91
+
92
+ def add_page_break(self):
93
+ """Inserta un salto de página."""
94
+ self.components.append('<div class="page-break"></div>')
95
+ return self
96
+
97
+ def build(self):
98
+ """Une todos los componentes renderizados y compila el PDF."""
99
+ # 1. Obtener estilos globales (style.html)
100
+ head = self._render("style.html")
101
+
102
+ # 2. Combinar componentes
103
+ html_completo = "\n".join(self.components)
104
+
105
+ # 3. Construir estructura HTML final
106
+ html_final = f"""
107
+ <!DOCTYPE html>
108
+ <html lang="es">
109
+ <head>
110
+ <meta charset="UTF-8">
111
+ <title>Documento PDF</title>
112
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
113
+ {head}
114
+ </head>
115
+ <body>
116
+ {html_completo}
117
+ </body>
118
+ </html>
119
+ """
120
+ return generate_pdf_from_string(html_final)
@@ -0,0 +1,257 @@
1
+ from .base import BaseFormAssembler
2
+
3
+ class FormularioReposicionAssembler(BaseFormAssembler):
4
+ def __init__(self, context=None):
5
+ super().__init__(context)
6
+ self.page_number = 1
7
+ self.total_pages = 4
8
+
9
+ def build(self):
10
+ # ==========================================
11
+ # === PÁGINA 1: Datos y Radicado =========
12
+ # ==========================================
13
+ self.add_component('header.html', {
14
+ 'titulo1': "FORMULARIO DE RECURSO DE REPOSICIÓN",
15
+ 'titulo2': "CONTRA RESOLUCIONES DEL COMITÉ CERREM INDIVIDUAL Y COLECTIVO",
16
+ 'titulo3': "UNIDAD NACIONAL DE PROTECCIÓN"
17
+ })
18
+
19
+ self.add_component('section.html', {'titulo': "Datos del radicado (Espacio para uso exclusivo de la UNP)"})
20
+ self.add_grid_row([
21
+ {"template": "input.html", "context": {"label": "1. Número del radicado *", "valor": self.context.get('numRadicado', '')}, "ancho": "33%"},
22
+ {"template": "input.html", "context": {"label": "2. Fecha de radicado *", "valor": self.context.get('fechaRadicado', '')}, "ancho": "33%"},
23
+ {"template": "input.html", "context": {"label": "3. Sede/Oficina", "valor": self.context.get('sedeRadicado', '')}, "ancho": "34%"}
24
+ ])
25
+
26
+ self.add_component('section.html', {'titulo': "Fecha y lugar de diligenciamiento"})
27
+ self.add_grid_row([
28
+ {"template": "input.html", "context": {"label": "4. Fecha de diligenciamiento *", "valor": self.context.get('fechaDiligenciamiento', '')}, "ancho": "30%"},
29
+ {"template": "input.html", "context": {"label": "5. País *", "valor": self.context.get('paisDiligenciamiento', 'Colombia')}, "ancho": "20%"},
30
+ {"template": "input.html", "context": {"label": "6. Departamento *", "valor": self.context.get('dptoDiligenciamiento', '')}, "ancho": "25%"},
31
+ {"template": "input.html", "context": {"label": "7. Municipio *", "valor": self.context.get('mnpioDiligenciamiento', '')}, "ancho": "25%"}
32
+ ])
33
+
34
+ self.add_component('section.html', {'titulo': "Resolución CERREM", 'icono': "fa-file-alt"})
35
+ self.add_grid_row([
36
+ {"template": "input.html", "context": {"label": "8. Número del radicado", "valor": self.context.get('numResolucion', '')}, "ancho": "50%"},
37
+ {"template": "input.html", "context": {"label": "9. Fecha de radicado", "valor": self.context.get('fechaResolucion', '')}, "ancho": "50%"}
38
+ ])
39
+
40
+ self.add_component('section.html', {'titulo': "Datos Básicos", 'icono': "fa-user"})
41
+ self.add_grid_row([
42
+ {"template": "input.html", "context": {"label": "10. Primer nombre *", "valor": self.context.get('primerNombre', '')}, "ancho": "25%"},
43
+ {"template": "input.html", "context": {"label": "11. Segundo nombre", "valor": self.context.get('segundoNombre', '')}, "ancho": "25%"},
44
+ {"template": "input.html", "context": {"label": "12. Primer apellido *", "valor": self.context.get('primerApellido', '')}, "ancho": "25%"},
45
+ {"template": "input.html", "context": {"label": "13. Segundo apellido", "valor": self.context.get('segundoApellido', '')}, "ancho": "25%"}
46
+ ])
47
+
48
+ self.add_component('checkbox.html', {
49
+ 'label': "15. Género *",
50
+ 'opciones': [
51
+ {'label': "Femenino", 'valor': "Femenino"},
52
+ {'label': "Masculino", 'valor': "Masculino"},
53
+ {'label': "No binario", 'valor': "No binario"}
54
+ ],
55
+ 'seleccionado': self.context.get('genero', '')
56
+ })
57
+
58
+ self.add_component('section.html', {'titulo': "Número único de identificación personal (NUIP)", 'icono': "fa-id-card"})
59
+ self.add_grid_row([
60
+ {
61
+ "template": "checkbox.html",
62
+ "context": {
63
+ "label": "16. Tipo de Identificación *",
64
+ "opciones": [
65
+ {"label": "C.C.", "valor": "Cédula de ciudadanía"},
66
+ {"label": "C.E.", "valor": "Cédula de extranjería"},
67
+ {"label": "P.S.", "valor": "Pasaporte"},
68
+ {"label": "Otro", "valor": "Otro"}
69
+ ],
70
+ "seleccionado": self.context.get('tipoIdentificacion', '')
71
+ },
72
+ "ancho": "45%"
73
+ },
74
+ {
75
+ "template": "input.html",
76
+ "context": {"label": "17. Número de identificación *", "valor": self.context.get('NumeroIdentificacion', '')},
77
+ "ancho": "27%"
78
+ },
79
+ {
80
+ "template": "input.html",
81
+ "context": {"label": "18. Fecha de expedición *", "valor": self.context.get('fechaExpedicion', '')},
82
+ "ancho": "28%"
83
+ }
84
+ ])
85
+
86
+ self.add_component('section.html', {'titulo': "Datos de contacto", 'icono': "fa-envelope"})
87
+ self.add_grid_row([
88
+ {"template": "input.html", "context": {"label": "19. Celular uno *", "valor": self.context.get('celularUno', '')}, "ancho": "20%"},
89
+ {"template": "input.html", "context": {"label": "20. Celular dos", "valor": self.context.get('celularDos', '')}, "ancho": "20%"},
90
+ {"template": "input.html", "context": {"label": "21. Teléfono", "valor": self.context.get('telefono', '')}, "ancho": "20%"},
91
+ {"template": "input.html", "context": {"label": "22. Correo electrónico *", "valor": self.context.get('correo', '')}, "ancho": "40%"}
92
+ ])
93
+
94
+ self.add_component('radio.html', {
95
+ 'label': "23. ¿Autoriza el envío de notificaciones a través del correo electrónico inscrito?",
96
+ 'opciones': [{'label': "Sí", 'valor': "Sí"}, {'label': "No", 'valor': "No"}],
97
+ 'seleccionado': self.context.get('autoriza', '')
98
+ })
99
+ self.add_raw_html('<p style="font-size: 8px; color: #64748b; margin-top: -4px; margin-bottom: 12px;">* De conformidad con el artículo 56 de la Ley 1437 de 2011.</p>')
100
+
101
+ self.add_grid_row([
102
+ {
103
+ "template": "radio.html",
104
+ "context": {
105
+ "label": "24. ¿Presenta hechos sobrevinientes?",
106
+ "opciones": [{"label": "Sí", "valor": "Sí"}, {"label": "No", "valor": "No"}],
107
+ "seleccionado": self.context.get('presenta_hechos', '')
108
+ },
109
+ "ancho": "50%"
110
+ }
111
+ ])
112
+
113
+ self.add_component('footer.html', {
114
+ 'codigo': "GJU-FT-52-VX", 'fecha': "Oficialización: 00/00/0000", 'pagina': "Pág. 1 de 4"
115
+ })
116
+
117
+ # ==========================================
118
+ # === PÁGINA 2: Hechos Sobrevinientes =====
119
+ # ==========================================
120
+ self.add_page_break()
121
+ self.add_raw_html('<div class="bordered-page-container">')
122
+ self.add_component('section.html', {'titulo': "Hechos sobrevinientes que fundamentan el recurso", 'icono': "fa-edit"})
123
+ self.add_component('textarea.html', {
124
+ 'alto': "780px",
125
+ 'valor': self.context.get('hechosSobrevinientes', '')
126
+ })
127
+ self.add_raw_html('</div>')
128
+ self.add_component('footer.html', {
129
+ 'codigo': "GJU-FT-52-VX", 'fecha': "Oficialización: 00/00/0000", 'pagina': "Pág. 2 de 4"
130
+ })
131
+
132
+ # ==========================================
133
+ # === PÁGINA 3: Medidas y Apoderado =======
134
+ # ==========================================
135
+ self.add_page_break()
136
+ self.add_raw_html('<div class="bordered-page-container">')
137
+
138
+ self.add_component('section.html', {'titulo': "Medidas de Protección", 'icono': "fa-shield-alt"})
139
+ self.add_component('radio.html', {
140
+ 'label': "32. ¿Es beneficiario de medidas de protección por la UNP?",
141
+ 'opciones': [{'label': "Sí", 'valor': "Sí"}, {'label': "No", 'valor': "No"}],
142
+ 'seleccionado': self.context.get('tiene_medidas', '')
143
+ })
144
+
145
+ self.add_component('section.html', {'titulo': "Datos apoderado"})
146
+ self.add_component('radio.html', {
147
+ 'label': "33. ¿Presenta recurso mediante apoderado?",
148
+ 'opciones': [{'label': "Sí", 'valor': "Sí"}, {'label': "No", 'valor': "No"}],
149
+ 'seleccionado': self.context.get('presentaApoderado', '')
150
+ })
151
+
152
+ self.add_component('section.html', {'titulo': "Nombre(s) y apellido(s) del apoderado", 'icono': "fa-user-tie"})
153
+ self.add_grid_row([
154
+ {"template": "input.html", "context": {"label": "34. Primer nombre *", "valor": self.context.get('primerNombreApoderado', '')}, "ancho": "25%"},
155
+ {"template": "input.html", "context": {"label": "35. Segundo nombre", "valor": self.context.get('segundoNombreApoderado', '')}, "ancho": "25%"},
156
+ {"template": "input.html", "context": {"label": "36. Primer apellido *", "valor": self.context.get('primerApellidoApoderado', '')}, "ancho": "25%"},
157
+ {"template": "input.html", "context": {"label": "37. Segundo apellido", "valor": self.context.get('segundoApellidoApoderado', '')}, "ancho": "25%"}
158
+ ])
159
+
160
+ self.add_grid_row([
161
+ {
162
+ "template": "checkbox.html",
163
+ "context": {
164
+ "label": "38. Tipo de Identificación",
165
+ "opciones": [
166
+ {"label": "C.C.", "valor": "Cédula de ciudadanía"},
167
+ {"label": "C.E.", "valor": "Cédula de extranjería"},
168
+ {"label": "Pasaporte", "valor": "Pasaporte"}
169
+ ],
170
+ "seleccionado": self.context.get('tipoIdentificacionApoderado', '')
171
+ },
172
+ "ancho": "40%"
173
+ },
174
+ {
175
+ "template": "input.html",
176
+ "context": {"label": "39. Número de identificación *", "valor": self.context.get('numeroIdentificacionApoderado', '')},
177
+ "ancho": "30%"
178
+ },
179
+ {
180
+ "template": "input.html",
181
+ "context": {"label": "40. Tarjeta Profesional *", "valor": self.context.get('tarjetaProfesional', '')},
182
+ "ancho": "30%"
183
+ }
184
+ ])
185
+
186
+ self.add_component('section.html', {'titulo': "Inconformidad", 'icono': "fa-exclamation-circle"})
187
+ self.add_component('textarea.html', {
188
+ 'label': "41. Descripción clara de los motivos de inconformidad",
189
+ 'alto': "380px",
190
+ 'valor': self.context.get('motivoInconformidad', '')
191
+ })
192
+
193
+ self.add_raw_html('</div>')
194
+ self.add_component('footer.html', {
195
+ 'codigo': "GJU-FT-52-VX", 'fecha': "Oficialización: 00/00/0000", 'pagina': "Pág. 3 de 4"
196
+ })
197
+
198
+ # ==========================================
199
+ # === PÁGINA 4: Anexos y Firma ============
200
+ # ==========================================
201
+ self.add_page_break()
202
+ self.add_raw_html('<div class="bordered-page-container">')
203
+
204
+ self.add_component('section.html', {'titulo': "Anexos adjuntos", 'icono': "fa-paperclip"})
205
+ self.add_component('checkbox.html', {
206
+ 'label': "Marque los anexos que adjunta a este recurso:",
207
+ 'opciones': [
208
+ {'label': "Copia del documento de identidad", 'valor': "identidad"},
209
+ {'label': "Poder debidamente otorgado (si aplica)", 'valor': "poder"},
210
+ {'label': "Pruebas documentales que sustentan la inconformidad", 'valor': "pruebas"},
211
+ {'label': "Otros documentos de interés", 'valor': "otros"}
212
+ ],
213
+ 'seleccionados': self.context.get('anexos_seleccionados', [])
214
+ })
215
+
216
+ self.add_raw_html('''
217
+ <div style="margin-top: 25px; margin-bottom: 25px; padding: 12px; border: 1px solid #cbd5e1; border-radius: 4px; font-size: 10px; color: #334155; background-color: #f8fafc; line-height: 1.4;">
218
+ <strong>42. MANIFIESTO BAJO LA GRAVEDAD DEL JURAMENTO *</strong><br>
219
+ Manifiesto bajo la gravedad del juramento que la información suministrada en este formulario es verídica y que los documentos adjuntos son copia fiel de sus originales.
220
+ </div>
221
+ ''')
222
+
223
+ self.add_component('section.html', {'titulo': "Firma del solicitante / apoderado"})
224
+
225
+ firmas = [
226
+ {
227
+ 'nombre': self.context.get('nombreCompleto', 'Firma del Solicitante'),
228
+ 'cargo': "Solicitante",
229
+ 'documento': self.context.get('NumeroIdentificacion', '')
230
+ }
231
+ ]
232
+
233
+ if self.context.get('presentaApoderado') == 'Sí':
234
+ firmas.append({
235
+ 'nombre': self.context.get('nombreCompletoApoderado', 'Firma del Apoderado'),
236
+ 'cargo': "Apoderado",
237
+ 'documento': self.context.get('numeroIdentificacionApoderado', '')
238
+ })
239
+
240
+ self.add_component('signature.html', {
241
+ 'firmas': firmas,
242
+ 'margin_top': "60px"
243
+ })
244
+
245
+ self.add_raw_html('</div>')
246
+
247
+ self.add_raw_html('''
248
+ <div style="font-size: 8px; color: #64748b; text-align: center; margin-top: 10px;">
249
+ Archívese en: Carpeta única del solicitante - Unidad Nacional de Protección.
250
+ </div>
251
+ ''')
252
+
253
+ self.add_component('footer.html', {
254
+ 'codigo': "GJU-FT-52-VX", 'fecha': "Oficialización: 00/00/0000", 'pagina': "Pág. 4 de 4"
255
+ })
256
+
257
+ return super().build()
@@ -0,0 +1,44 @@
1
+ from .base import BaseFormAssembler
2
+ from assemblerpdf.core import generate_pdf_from_string
3
+
4
+ class TwoColumnReportAssembler(BaseFormAssembler):
5
+ def __init__(self, context=None):
6
+ super().__init__(context)
7
+
8
+ def add_section(self, title, content_html):
9
+ """Agrega una sección con título y contenido en dos columnas."""
10
+ section_html = f"""
11
+ <div style="margin: 20px 0; padding: 15px; border: 1px solid #cbd5e1; border-radius: 8px; background-color: #ffffff;">
12
+ <h3 style="color: #002857; margin-top: 0; margin-bottom: 10px; font-size: 14px; font-weight: bold; border-bottom: 1px solid #cbd5e1; padding-bottom: 5px;">{title}</h3>
13
+ <div style="display: flex; gap: 20px;">
14
+ {content_html}
15
+ </div>
16
+ </div>
17
+ """
18
+ self.components.append(section_html)
19
+ return self
20
+
21
+ def build(self):
22
+ # Renderizar head global (style.html)
23
+ head = self._render("style.html")
24
+
25
+ # Combinar todos los componentes
26
+ html_completo = "\n".join(self.components)
27
+
28
+ # Plantilla base del informe
29
+ html_final = f"""
30
+ <!DOCTYPE html>
31
+ <html lang="es">
32
+ <head>
33
+ <meta charset="UTF-8">
34
+ <title>Informe</title>
35
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
36
+ {head}
37
+ </head>
38
+ <body style="padding: 20px; background-color: #f8fafc;">
39
+ <h1 style="color: #002857; text-align: center; margin-bottom: 20px; font-size: 18px; font-weight: bold; border-bottom: 2px solid #002857; padding-bottom: 10px;">Informe</h1>
40
+ {html_completo}
41
+ </body>
42
+ </html>
43
+ """
44
+ return generate_pdf_from_string(html_final)