diff --git a/pkg/services/plantilla_import_service.go b/pkg/services/plantilla_import_service.go
index a1e56b6..6c719ee 100644
--- a/pkg/services/plantilla_import_service.go
+++ b/pkg/services/plantilla_import_service.go
@@ -11,12 +11,12 @@ import (
// renderizarse. Sale de DatosBaseDocumento + lo que arma cada generador
// (ver CrearCotizacion, contrato_documento_service, cuenta_cobro_documento_service).
var variablesPorTipo = map[string]string{
- "cotizacion": `{{.Cliente.Nombre}}, {{.Cliente.Nit}}, {{.Cliente.Email}}, {{.Cliente.Telefono}},
+ "cotizacion": `{{.Cliente.Nombre}}, {{.Cliente.Documento}}, {{.Cliente.Email}}, {{.Cliente.Telefono}},
{{.Alcance}}, {{.TipoProyecto}}, {{.Total}},
{{range .Items}} … {{.Descripcion}} {{.Cantidad}} {{.Unidad}} {{.ValorUnitario}} … {{end}}`,
- "contrato": `{{.Cliente.Nombre}}, {{.Cliente.Nit}}, {{.Alcance}}, {{.Total}}, {{.Servicio}}, {{.Periodicidad}}`,
+ "contrato": `{{.Cliente.Nombre}}, {{.Cliente.Documento}}, {{.Alcance}}, {{.Total}}, {{.Servicio}}, {{.Periodicidad}}`,
"acta": `{{.Cliente.Nombre}}, {{.Proyecto}}, {{.Alcance}}, {{.Entregables}}`,
- "cuenta_cobro": `{{.Cliente.Nombre}}, {{.Cliente.Nit}}, {{.Concepto}}, {{.Total}}, {{.Numero}}`,
+ "cuenta_cobro": `{{.Cliente.Nombre}}, {{.Cliente.Documento}}, {{.Concepto}}, {{.Total}}, {{.Numero}}`,
}
const variablesComunes = `{{.Fecha}}, {{.EmpresaNombre}}, {{.EmpresaWeb}}`
diff --git a/pkg/services/plantilla_preview_service.go b/pkg/services/plantilla_preview_service.go
new file mode 100644
index 0000000..3b280cd
--- /dev/null
+++ b/pkg/services/plantilla_preview_service.go
@@ -0,0 +1,67 @@
+package services
+
+import (
+ "bytes"
+ "fmt"
+ "text/template"
+
+ "github.com/sujit-baniya/fiber-boilerplate/pkg/models"
+)
+
+// RenderizarPlantillaEjemplo ejecuta la plantilla con datos de muestra y
+// devuelve el HTML resultante.
+//
+// Se ejecuta de verdad, no se hace un reemplazo de texto: es la única forma de
+// que {{range .Items}} y los campos anidados se vean como van a salir, y de que
+// un error de sintaxis aparezca mientras se edita y no al generar el documento.
+func RenderizarPlantillaEjemplo(tipo, contenidoHTML string) (string, error) {
+ tmpl, err := template.New("preview").Parse(contenidoHTML)
+ if err != nil {
+ return "", fmt.Errorf("sintaxis inválida: %w", err)
+ }
+ var buf bytes.Buffer
+ if err := tmpl.Execute(&buf, DatosDeEjemploPlantilla(tipo)); err != nil {
+ return "", fmt.Errorf("no se pudo renderizar: %w", err)
+ }
+ return buf.String(), nil
+}
+
+// DatosDeEjemploPlantilla arma los mismos campos que le pasa cada generador
+// real (ver CrearCotizacion, contrato_documento_service, cuenta_cobro), con
+// valores inventados. Si esto se desincroniza de los generadores, la vista
+// previa miente — por eso sale de DatosBaseDocumento, igual que en producción.
+func DatosDeEjemploPlantilla(tipo string) map[string]interface{} {
+ cliente := &models.Cliente{
+ Nombre: "Acme S.A.S.",
+ Empresa: "Acme S.A.S.",
+ Documento: "900.123.456-7",
+ Email: "contacto@acme.com",
+ Telefono: "+57 300 123 4567",
+ }
+
+ extra := map[string]interface{}{
+ "Cliente": cliente,
+ "Alcance": "Desarrollo del sitio web institucional, con panel de administración y tres integraciones.",
+ "TipoProyecto": "Sitio web",
+ "Total": 4500000.0,
+ "Items": []ItemCotizacion{
+ {Descripcion: "Diseño de interfaz", Cantidad: 1, ValorUnitario: 1500000, Unidad: "servicio"},
+ {Descripcion: "Desarrollo frontend", Cantidad: 40, ValorUnitario: 50000, Unidad: "hora"},
+ {Descripcion: "Integración con pasarela de pagos", Cantidad: 1, ValorUnitario: 1000000, Unidad: "servicio"},
+ },
+ }
+
+ switch tipo {
+ case "contrato":
+ extra["Servicio"] = "Mantenimiento mensual del sitio"
+ extra["Periodicidad"] = "mensual"
+ case "acta":
+ extra["Proyecto"] = "Sitio web Acme"
+ extra["Entregables"] = "Sitio publicado, manual de uso y capacitación."
+ case "cuenta_cobro":
+ extra["Concepto"] = "Mantenimiento mensual — marzo"
+ extra["Numero"] = "CC-0042"
+ }
+
+ return DatosBaseDocumento(extra)
+}
diff --git a/pkg/services/plantilla_preview_test.go b/pkg/services/plantilla_preview_test.go
new file mode 100644
index 0000000..1692e56
--- /dev/null
+++ b/pkg/services/plantilla_preview_test.go
@@ -0,0 +1,68 @@
+package services
+
+import (
+ "strings"
+ "testing"
+)
+
+// La vista previa ejecuta la plantilla de verdad: es lo que hace que un
+// {{range}} se vea como va a salir y que un error de sintaxis aparezca mientras
+// se edita, y no recién al generar el documento.
+func TestRenderizarPlantillaEjemplo(t *testing.T) {
+ html := `
`
+
+ out, err := RenderizarPlantillaEjemplo("cotizacion", html)
+ if err != nil {
+ t.Fatalf("RenderizarPlantillaEjemplo: %v", err)
+ }
+ for _, quiero := range []string{"Acme S.A.S.", "900.123.456-7", "Diseño de interfaz", "Integración con pasarela", "U-SITE"} {
+ if !strings.Contains(out, quiero) {
+ t.Errorf("falta %q en:\n%s", quiero, out)
+ }
+ }
+ if strings.Contains(out, "{{") {
+ t.Errorf("quedaron variables sin resolver:\n%s", out)
+ }
+}
+
+func TestRenderizarPlantillaEjemploAvisaDeErrores(t *testing.T) {
+ if _, err := RenderizarPlantillaEjemplo("cotizacion", "{{range .Items}}sin fin"); err == nil {
+ t.Error("un {{range}} sin {{end}} debería dar error")
+ }
+ // Una variable que no existe no rompe: se renderiza como "", igual
+ // que en la generación real. Eso se ve en la vista previa, que es el punto —
+ // hacerla más estricta que producción rechazaría plantillas que sí andan.
+ out, err := RenderizarPlantillaEjemplo("cotizacion", "Hola {{.NoExiste}}")
+ if err != nil {
+ t.Fatalf("no debería fallar: %v", err)
+ }
+ if !strings.Contains(out, "no value") {
+ t.Errorf("una variable inexistente tendría que notarse en la vista previa: %q", out)
+ }
+}
+
+// Cada tipo agrega sus propios campos; si esto se desincroniza de los
+// generadores, la vista previa muestra una cosa y el PDF sale con otra.
+func TestDatosDeEjemploPorTipo(t *testing.T) {
+ casos := map[string][]string{
+ "contrato": {"Servicio", "Periodicidad"},
+ "acta": {"Proyecto", "Entregables"},
+ "cuenta_cobro": {"Concepto", "Numero"},
+ }
+ for tipo, campos := range casos {
+ datos := DatosDeEjemploPlantilla(tipo)
+ for _, campo := range campos {
+ if _, ok := datos[campo]; !ok {
+ t.Errorf("faltan datos de ejemplo de %q para el tipo %q", campo, tipo)
+ }
+ }
+ for _, comun := range []string{"Cliente", "Fecha", "EmpresaNombre", "Total"} {
+ if _, ok := datos[comun]; !ok {
+ t.Errorf("falta el campo común %q en el tipo %q", comun, tipo)
+ }
+ }
+ }
+}
diff --git a/resources/views/automatizacion/plantillas_documento.html b/resources/views/automatizacion/plantillas_documento.html
index 8b8410d..c57f010 100644
--- a/resources/views/automatizacion/plantillas_documento.html
+++ b/resources/views/automatizacion/plantillas_documento.html
@@ -76,7 +76,8 @@
-
+
@@ -134,13 +135,36 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ actualizando…
+
+
+
+
+
+ Cliente, ítems y totales son inventados; al generar el documento real se reemplazan por los del cliente.
+