The number regex /:(\s*)(\d+)/ was matching colons inside time strings
like "14:51:26" turning them into "14: 51: 26". Changed to require the
space that JSON.stringify always adds after key-value colons (": \d+")
so colons inside string values are not affected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
131 lines
6.3 KiB
HTML
131 lines
6.3 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Terceros{% endblock %}
|
|
{% block header %}Envío de Terceros{% endblock %}
|
|
{% block content %}
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="font-semibold text-gray-800"><i class="fas fa-cog mr-2 text-blue-500"></i>Generar y Enviar</h3>
|
|
</div>
|
|
<div class="p-6">
|
|
<form id="form-terceros" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Consulta SQL</label>
|
|
<select name="query_id" required
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
|
<option value="">Seleccionar consulta...</option>
|
|
{% for q in queries %}
|
|
<option value="{{ q.id }}">{{ q.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
|
Número de Documento <span class="text-xs text-red-400">* requerido</span>
|
|
</label>
|
|
<input type="text" name="doc_num" required
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
|
placeholder="Ej: 27567400">
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<button type="button" onclick="previewTerceros()"
|
|
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200">
|
|
<i class="fas fa-eye mr-1"></i> Vista Previa
|
|
</button>
|
|
<button type="button" onclick="sendTerceros()"
|
|
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium">
|
|
<i class="fas fa-paper-plane mr-1"></i> Enviar a API
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 bg-white rounded-xl shadow-sm border border-gray-200">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="font-semibold text-gray-800"><i class="fas fa-eye mr-2 text-blue-500"></i>JSON Generado</h3>
|
|
</div>
|
|
<div class="p-6">
|
|
<pre id="json-preview" class="text-xs font-mono bg-gray-50 rounded-lg p-4 overflow-x-auto max-h-96 text-gray-600"><i class="fas fa-info-circle mr-1"></i> Haz clic en "Vista Previa" para ver el JSON</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<h3 class="font-semibold text-gray-800"><i class="fas fa-history mr-2 text-blue-500"></i>Últimos Envíos</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
{% if envios %}
|
|
<div class="space-y-2">
|
|
{% for e in envios %}
|
|
<div class="p-3 rounded-lg border border-gray-100 text-sm">
|
|
<div class="flex justify-between items-center">
|
|
<span class="px-2 py-0.5 rounded text-xs font-medium {% if e.status == 'success' %}bg-green-100 text-green-700{% else %}bg-red-100 text-red-700{% endif %}">
|
|
{{ 'Exitoso' if e.status == 'success' else 'Error' }}
|
|
</span>
|
|
<span class="text-xs text-gray-400">{{ e.created_at[:19] }}</span>
|
|
</div>
|
|
<p class="text-xs text-gray-500 mt-1 truncate">{{ e.respuesta_api[:100] if e.respuesta_api else 'Sin respuesta' }}</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<p class="text-center text-gray-400 py-4 text-sm">No hay envíos de terceros aún</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
async function previewTerceros() {
|
|
const form = document.getElementById('form-terceros');
|
|
const data = new FormData(form);
|
|
const btn = form.querySelector('[onclick="previewTerceros()"]');
|
|
showLoading(btn);
|
|
|
|
const resp = await fetch('/terceros/preview', {method:'POST', body: data, credentials: 'include'});
|
|
const result = await resp.json();
|
|
hideLoading(btn, '<i class="fas fa-eye mr-1"></i> Vista Previa');
|
|
|
|
const pre = document.getElementById('json-preview');
|
|
if (result.success && result.generated_json) {
|
|
pre.innerHTML = syntaxHighlight(result.generated_json);
|
|
} else {
|
|
pre.innerHTML = '<span class="text-red-600">Error: ' + (result.message || 'Sin datos') + '</span>';
|
|
}
|
|
}
|
|
|
|
async function sendTerceros() {
|
|
if (!confirm('¿Enviar terceros a la API?')) return;
|
|
const form = document.getElementById('form-terceros');
|
|
const data = new FormData(form);
|
|
const btn = form.querySelector('[onclick="sendTerceros()"]');
|
|
showLoading(btn);
|
|
|
|
const resp = await fetch('/terceros/send', {method:'POST', body: data, credentials: 'include'});
|
|
const result = await resp.json();
|
|
hideLoading(btn, '<i class="fas fa-paper-plane mr-1"></i> Enviar a API');
|
|
|
|
if (result.success) {
|
|
showToast('Envío exitoso! CUV: ' + (result.cuv || ''), 'success');
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
showToast('Error: ' + result.message, 'error');
|
|
}
|
|
}
|
|
|
|
function syntaxHighlight(obj) {
|
|
let json = JSON.stringify(obj, null, 2);
|
|
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
return json.replace(/("(?:[^"\\]|\\.)*")(?=\s*:)/g, '<span class="text-blue-600">$1</span>')
|
|
.replace(/:(\s*)("(?:[^"\\]|\\.)*")/g, ': $1<span class="text-green-600">$2</span>')
|
|
.replace(/: (\d+)/g, ': <span class="text-orange-600">$1</span>')
|
|
.replace(/: (null|true|false)/g, ': <span class="text-purple-600">$1</span>');
|
|
}
|
|
</script>
|
|
{% endblock %}
|