Se editaba HTML a ciegas: había que guardar y generar un documento real para
saber si estaba bien. Ahora el modal tiene un panel de vista previa al lado del
editor, que se actualiza mientras se escribe y se abre solo al editar una
plantilla o al importar una con IA.
Se ejecuta la plantilla de verdad contra datos de ejemplo, no se reemplaza
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 en
vez de al generar el PDF. Los datos de ejemplo salen de DatosBaseDocumento,
igual que en producción, para que la vista previa no muestre una cosa y el
documento otra.
El iframe va en sandbox sin scripts ni same-origin: el HTML lo escribe un
admin, pero no tiene por qué correr con los permisos del panel.
De paso, la ayuda de variables ofrecía {{.Cliente.Nit}}, que no existe en el
modelo — el campo es Documento.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
95 lines
3.5 KiB
Go
95 lines
3.5 KiB
Go
package services
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
|
)
|
|
|
|
// variablesPorTipo documenta, para la IA, qué campos recibe cada plantilla al
|
|
// 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.Documento}}, {{.Cliente.Email}}, {{.Cliente.Telefono}},
|
|
{{.Alcance}}, {{.TipoProyecto}}, {{.Total}},
|
|
{{range .Items}} … {{.Descripcion}} {{.Cantidad}} {{.Unidad}} {{.ValorUnitario}} … {{end}}`,
|
|
"contrato": `{{.Cliente.Nombre}}, {{.Cliente.Documento}}, {{.Alcance}}, {{.Total}}, {{.Servicio}}, {{.Periodicidad}}`,
|
|
"acta": `{{.Cliente.Nombre}}, {{.Proyecto}}, {{.Alcance}}, {{.Entregables}}`,
|
|
"cuenta_cobro": `{{.Cliente.Nombre}}, {{.Cliente.Documento}}, {{.Concepto}}, {{.Total}}, {{.Numero}}`,
|
|
}
|
|
|
|
const variablesComunes = `{{.Fecha}}, {{.EmpresaNombre}}, {{.EmpresaWeb}}`
|
|
|
|
// ExtraerTextoDePlantilla lee el archivo que subió el admin como referencia.
|
|
// agenteID 0: es una acción del staff, no se le cobra a ningún cliente.
|
|
func ExtraerTextoDePlantilla(nombreArchivo string, datos []byte) (string, error) {
|
|
return ExtraerTextoDeArchivo(0, nombreArchivo, datos)
|
|
}
|
|
|
|
// ConvertirEnPlantilla le pide a la IA que rearme el documento como plantilla
|
|
// HTML con las variables Go que usa el generador. Lo que devuelve va al editor
|
|
// para que el admin lo revise antes de guardar: no se guarda solo.
|
|
func ConvertirEnPlantilla(tipo, textoDocumento string) (string, error) {
|
|
if strings.TrimSpace(textoDocumento) == "" {
|
|
return "", fmt.Errorf("no se pudo leer texto del archivo")
|
|
}
|
|
if len(textoDocumento) > 20000 {
|
|
textoDocumento = textoDocumento[:20000]
|
|
}
|
|
vars := variablesPorTipo[tipo]
|
|
if vars == "" {
|
|
vars = variablesComunes
|
|
}
|
|
|
|
sistema := `Sos un asistente que convierte documentos en plantillas HTML para Go text/template.
|
|
Reglas:
|
|
- Devolvé SOLO el HTML de la plantilla, sin explicaciones y sin bloques de código markdown.
|
|
- Reemplazá los datos concretos del documento (nombres, NITs, fechas, montos, ítems) por las variables de la lista. Lo que sea texto fijo del formato se deja tal cual.
|
|
- Si el documento tiene una tabla de ítems, usá {{range .Items}}…{{end}} para las filas.
|
|
- Usá estilos inline (style="…"), sin CSS externo ni <script>: el HTML se convierte a PDF.
|
|
- No inventes variables que no estén en la lista.`
|
|
|
|
usuario := fmt.Sprintf(`Tipo de documento: %s
|
|
|
|
Variables disponibles:
|
|
%s
|
|
%s
|
|
|
|
Documento de referencia:
|
|
---
|
|
%s
|
|
---`, tipo, variablesComunes, vars, textoDocumento)
|
|
|
|
// Si el admin asignó una config al módulo "plantillas" se usa esa; si no,
|
|
// sigue saliendo por "ia" como hasta ahora. Convertir un documento entero
|
|
// pide un modelo más capaz que el resto de las tareas, y esto deja
|
|
// elegirlo sin tocar lo que ya usa la vCard.
|
|
modulo := "ia"
|
|
if models.HayAiConfigParaModulo("plantillas") {
|
|
modulo = "plantillas"
|
|
}
|
|
|
|
salida, err := CompletarTextoIA(modulo, sistema, usuario)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return limpiarCercaDeCodigo(salida), nil
|
|
}
|
|
|
|
// limpiarCercaDeCodigo saca el ```html … ``` que los modelos agregan aunque se
|
|
// les pida que no lo hagan.
|
|
func limpiarCercaDeCodigo(s string) string {
|
|
s = strings.TrimSpace(s)
|
|
if !strings.HasPrefix(s, "```") {
|
|
return s
|
|
}
|
|
if i := strings.Index(s, "\n"); i >= 0 {
|
|
s = s[i+1:]
|
|
}
|
|
if i := strings.LastIndex(s, "```"); i >= 0 {
|
|
s = s[:i]
|
|
}
|
|
return strings.TrimSpace(s)
|
|
}
|