Depuración de errores y mejora de UX

main
root 1 year ago
parent 71c0688f96
commit 7fb04a9706

@ -0,0 +1,11 @@
{
"tabId": "5",
"tabName": "IVR_Principal",
"node3_conditions": [
{
"nodeId": "node1733230736190",
"title": "Prompt y Condiciones",
"conditions": []
}
]
}

@ -0,0 +1,27 @@
{
"tabId": "6",
"tabName": "Prueba",
"node3_conditions": [
{
"nodeId": "node1733232084628",
"title": "Prompt y Condiciones",
"conditions": [
{
"condition_string": "*",
"target_node_id": "node1733232156012",
"extensionNumber": "1000"
},
{
"condition_string": "**",
"target_node_id": "node1733232177548",
"extensionNumber": "2000"
},
{
"condition_string": "***",
"target_node_id": "node1733232195165",
"extensionNumber": "3000"
}
]
}
]
}

@ -0,0 +1,27 @@
{
"tabId": "7",
"tabName": "Prueba2",
"node3_conditions": [
{
"nodeId": "node1733433983845",
"title": "Prompt y Condiciones",
"conditions": [
{
"condition_string": "*",
"target_node_id": "node1733434056630",
"extensionNumber": "1000"
},
{
"condition_string": "**",
"target_node_id": "node1733434058542",
"extensionNumber": "2000"
},
{
"condition_string": "***",
"target_node_id": "node1733434061078",
"extensionNumber": "3000"
}
]
}
]
}

@ -0,0 +1,11 @@
{
"tabId": "8",
"tabName": "Prueba3",
"node3_conditions": [
{
"nodeId": "node1733754870530",
"title": "Prompt y Condiciones",
"conditions": []
}
]
}

@ -1,53 +0,0 @@
{
"tabName": "IVR_1",
"nodes": [
{
"id": "node1732650780573",
"title": "DID",
"extensionNumber": "1234",
"position": {
"top": "100px",
"left": "100px"
},
"nodeType": "node1"
},
{
"id": "node1732655323939",
"title": "Bienvenida",
"audioFile": "/media/tabs/tab_1/audio_files/Bienvenida_node2_node1732655323939.wav",
"position": {
"top": "101px",
"left": "373px"
},
"nodeType": "node2"
},
{
"id": "node1732657836004",
"title": "Prompt y Condiciones",
"content": "Chao",
"position": {
"top": "100px",
"left": "681px"
},
"nodeType": "node3",
"textFile": "",
"conditions": []
}
],
"connections": [
{
"startNodeId": "node1732650780573",
"startPortClass": "out",
"endNodeId": "node1732655323939",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1732655323939",
"startPortClass": "out",
"endNodeId": "node1732657836004",
"endPortClass": "in",
"isActive": false
}
]
}

@ -0,0 +1,121 @@
{
"tabName": "IVR_Principal",
"nodes": [
{
"id": "node1733230735070",
"title": "Extension",
"extensionNumber": "1000",
"position": {
"top": "149px",
"left": "79px"
},
"nodeType": "node1"
},
{
"id": "node1733230735781",
"title": "IVR",
"audioFile": "",
"position": {
"top": "149px",
"left": "325px"
},
"nodeType": "node2"
},
{
"id": "node1733230736190",
"title": "Prompt y Condiciones",
"content": "",
"position": {
"top": "149px",
"left": "567px"
},
"nodeType": "node3",
"textFile": "",
"conditions": []
},
{
"id": "node1733230736965",
"title": "Administración",
"position": {
"top": "149px",
"left": "865px"
},
"nodeType": "node4",
"extensionNumber": "2000"
},
{
"id": "node1733230754214",
"title": "Tecnología",
"position": {
"top": "57px",
"left": "865px"
},
"nodeType": "node4",
"extensionNumber": "1000"
},
{
"id": "node1733230754725",
"title": "Comercial",
"position": {
"top": "243px",
"left": "864px"
},
"nodeType": "node4",
"extensionNumber": "3000"
},
{
"id": "node1733408772925",
"title": "Asesor",
"position": {
"top": "345px",
"left": "868px"
},
"nodeType": "node4",
"extensionNumber": "4000"
}
],
"connections": [
{
"startNodeId": "node1733230735070",
"startPortClass": "out",
"endNodeId": "node1733230735781",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733230735781",
"startPortClass": "out",
"endNodeId": "node1733230736190",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733230736190",
"startPortClass": "out",
"endNodeId": "node1733230754214",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733230736190",
"startPortClass": "out",
"endNodeId": "node1733230754725",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733230736190",
"startPortClass": "out",
"endNodeId": "node1733230736965",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733230736190",
"startPortClass": "out",
"endNodeId": "node1733408772925",
"endPortClass": "in",
"isActive": false
}
]
}

@ -0,0 +1,117 @@
{
"tabName": "Prueba",
"nodes": [
{
"id": "node1733232048812",
"title": "DID o Extensión",
"extensionNumber": "7890",
"position": {
"top": "157px",
"left": "106px"
},
"nodeType": "node1"
},
{
"id": "node1733232060675",
"title": "IVR",
"audioFile": "/media/tabs/tab_6/audio_files/IVR_node2_node1733232060675.wav",
"position": {
"top": "156px",
"left": "379px"
},
"nodeType": "node2"
},
{
"id": "node1733232084628",
"title": "Prompt y Condiciones",
"content": "Eres un agente de servicio al cliente de telefonía y debes identificar si la persona quiere ser transferida al área de tecnología, administración o comercial. \nEn caso de tecnología devuelve 1 *.\nEn caso de Administración devuelve **.\nEn caso de Comercial devuelve ***.\n\nEs de suma importacia que los asteriscos deben ir al inicio de tú respuesta y siempre debes indicar a cuál área será transferido el cliente e indicar que fue un gusto atenderlo. Nunca puedes enviar los asteriscos solos, tienes que decir que fue un placer atenderte.",
"position": {
"top": "155px",
"left": "654px"
},
"nodeType": "node3",
"textFile": "",
"conditions": [
{
"condition_string": "*",
"target_node_id": "node1733232156012"
},
{
"condition_string": "**",
"target_node_id": "node1733232177548"
},
{
"condition_string": "***",
"target_node_id": "node1733232195165"
}
]
},
{
"id": "node1733232156012",
"title": "Tecnología",
"position": {
"top": "62px",
"left": "1010px"
},
"nodeType": "node4",
"extensionNumber": "1000"
},
{
"id": "node1733232177548",
"title": "Administración",
"position": {
"top": "181px",
"left": "1013px"
},
"nodeType": "node4",
"extensionNumber": "2000"
},
{
"id": "node1733232195165",
"title": "Comercial",
"position": {
"top": "290px",
"left": "1015px"
},
"nodeType": "node4",
"extensionNumber": "3000"
}
],
"connections": [
{
"startNodeId": "node1733232048812",
"startPortClass": "out",
"endNodeId": "node1733232060675",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733232060675",
"startPortClass": "out",
"endNodeId": "node1733232084628",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733232084628",
"startPortClass": "out",
"endNodeId": "node1733232156012",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733232084628",
"startPortClass": "out",
"endNodeId": "node1733232177548",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733232084628",
"startPortClass": "out",
"endNodeId": "node1733232195165",
"endPortClass": "in",
"isActive": false
}
]
}

@ -0,0 +1,117 @@
{
"tabName": "Prueba2",
"nodes": [
{
"id": "node1733433839548",
"title": "Número extensión",
"extensionNumber": "4562",
"position": {
"top": "147px",
"left": "111px"
},
"nodeType": "node1"
},
{
"id": "node1733433894348",
"title": "Cupula",
"audioFile": "/media/tabs/tab_7/audio_files/Cupula_node2_node1733433894348.wav",
"position": {
"top": "147px",
"left": "378px"
},
"nodeType": "node2"
},
{
"id": "node1733433983845",
"title": "Prompt y Condiciones",
"content": "Eres un agente de servicio al cliente de telefonía y debes identificar si la persona quiere ser transferida al área de tecnología, administración o comercial. \nEn caso de tecnología devuelve 1 *.\nEn caso de Administración devuelve **.\nEn caso de Comercial devuelve ***.\n\nEs de suma importacia que los asteriscos deben ir al inicio de tú respuesta y siempre debes indicar a cuál área será transferido el cliente e indicar que fue un gusto atenderlo. Nunca puedes enviar los asteriscos solos, tienes que decir que fue un placer atenderte.",
"position": {
"top": "147px",
"left": "629px"
},
"nodeType": "node3",
"textFile": "",
"conditions": [
{
"condition_string": "*",
"target_node_id": "node1733434056630"
},
{
"condition_string": "**",
"target_node_id": "node1733434058542"
},
{
"condition_string": "***",
"target_node_id": "node1733434061078"
}
]
},
{
"id": "node1733434056630",
"title": "Tecnología",
"position": {
"top": "55px",
"left": "947px"
},
"nodeType": "node4",
"extensionNumber": "1000"
},
{
"id": "node1733434058542",
"title": "Administración",
"position": {
"top": "157px",
"left": "947px"
},
"nodeType": "node4",
"extensionNumber": "2000"
},
{
"id": "node1733434061078",
"title": "Comercial",
"position": {
"top": "253px",
"left": "947px"
},
"nodeType": "node4",
"extensionNumber": "3000"
}
],
"connections": [
{
"startNodeId": "node1733433839548",
"startPortClass": "out",
"endNodeId": "node1733433894348",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733433894348",
"startPortClass": "out",
"endNodeId": "node1733433983845",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733433983845",
"startPortClass": "out",
"endNodeId": "node1733434056630",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733433983845",
"startPortClass": "out",
"endNodeId": "node1733434058542",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733433983845",
"startPortClass": "out",
"endNodeId": "node1733434061078",
"endPortClass": "in",
"isActive": false
}
]
}

@ -0,0 +1,70 @@
{
"tabName": "Prueba3",
"nodes": [
{
"id": "node1733497381839",
"title": "Extensión",
"extensionNumber": "9999",
"position": {
"top": "100px",
"left": "100px"
},
"nodeType": "node1"
},
{
"id": "node1733497412734",
"title": "IVR",
"audioFile": "/media/tabs/tab_8/audio_files/IVR_node2_node1733497412734.wav",
"position": {
"top": "98px",
"left": "378px"
},
"nodeType": "node2"
},
{
"id": "node1733754870530",
"title": "Prompt y Condiciones",
"content": "",
"position": {
"top": "95px",
"left": "690px"
},
"nodeType": "node3",
"textFile": "",
"conditions": []
},
{
"id": "node1733754876681",
"title": "Pruebita 1",
"position": {
"top": "94px",
"left": "983px"
},
"nodeType": "node4",
"extensionNumber": "1000"
}
],
"connections": [
{
"startNodeId": "node1733497381839",
"startPortClass": "out",
"endNodeId": "node1733497412734",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733497412734",
"startPortClass": "out",
"endNodeId": "node1733754870530",
"endPortClass": "in",
"isActive": false
},
{
"startNodeId": "node1733754870530",
"startPortClass": "out",
"endNodeId": "node1733754876681",
"endPortClass": "in",
"isActive": false
}
]
}

@ -1,20 +1,30 @@
/* flow.css */
body {
background-color: black;
--grid-size: 50px;
background-color: white;
background-image:
linear-gradient(to right, rgba(0,0,0,0.2) 1px, transparent 1px),
linear-gradient(to bottom, rgba(0,0,0,0.2) 1px, transparent 1px);
background-size: var(--grid-size) var(--grid-size);
min-height: 100vh;
margin: 0;
}
.right_sidebar{
height: 99.9%;
height: 100%;
width: 180px;
background: black;
background: rgba(255,255,255,0.5);
position: absolute;
top: 0px;
right: 0;
padding: 0 10px;
z-index: 2;
border-left: 1px solid white;
border-bottom: 1px solid white;
border-left: 1px solid black;
}
.node {
@ -30,16 +40,20 @@ body {
z-index: 1;
}
#node1, [data-node-id="node1"] {
background-color: lightblue;
#node1, [data-node-type="node1"] {
background-color: #004D40;
}
#node2, [data-node-id="node2"] {
background-color: lightgreen;
#node2, [data-node-type="node2"] {
background-color: #00BFA5;
}
#node3, [data-node-id="node3"] {
background-color: lightsalmon;
#node3, [data-node-type="node3"] {
background-color: #DDDDDD;
}
#node4, [data-node-type="node4"] {
background: #1DE9B6;
}
.slice_node {
@ -59,7 +73,7 @@ body {
.port {
width: 10px;
height: 10px;
background-color: white;
background-color: black;
border-radius: 50%;
position: absolute;
user-select: none;

@ -1,11 +1,14 @@
/* tabs.css */
body {
font-family: Arial, sans-serif;
font-family: "Markazi Text", serif;
font-size: 20px;
}
.container_create {
margin: 5px 3px 0 5px;
display: flex;
max-height: 55px;
max-height: 45px;
position: absolute;
}
#tabsContainer {
@ -16,10 +19,11 @@ body {
list-style-type: none;
display: flex;
padding: 0;
margin: 0;
}
#tabsList li {
margin: 0 5px;
margin: 0 2px;
padding: 10px;
background-color: #f0f0f0;
cursor: pointer;
@ -44,17 +48,18 @@ body {
#createTabButton {
padding: 10px 20px;
background-color: #4CAF50;
background-color: #00796B;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
height: auto;
z-index: 1;
transition: all 0.5s ease;
}
#createTabButton:hover {
background-color: #45a049;
background-color: #004139;
}
button {
@ -74,6 +79,7 @@ button {
}
.modal {
z-index: 1;
display: flex;
flex-wrap: wrap;
width: 300px;
@ -110,6 +116,7 @@ button {
}
.background {
z-index: 2;
position: fixed;
top: 0;
left: 0;
@ -131,36 +138,39 @@ button {
}
.btnGuardar {
width: 100px;
height: 30px;
font-size: 18px;
width: 110px;
height: 50px;
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
position: fixed;
bottom: 30px;
right: 50px;
right: 45px;
background: #155b40;
transition: all 1s ease;
}
.btnGuardar:hover {
background: radial-gradient(white, lightgoldenrodyellow);
filter: drop-shadow(lightgoldenrodyellow 10px 10px 10px);
background: radial-gradient(#10a169, #155b40);
filter: drop-shadow(#10a169 10px 10px 10px);
}
[data-node-type="node1"] {
background: lightblue;
background-color: #004D40;
}
[data-node-type="node2"] {
background: lightgreen;
background-color: #00BFA5;
}
[data-node-type="node3"] {
background: lightsalmon;
background-color: #DDDDDD;
}
[data-node-type="node4"] {
background: lightseagreen;
background: #1DE9B6;
}
.conditions-container {

@ -168,7 +168,7 @@ function drawLine(x1, y1, x2, y2, tabId) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = '#FFFFFF';
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.stroke();
ctx.closePath();

@ -118,13 +118,13 @@ function createNode(e, tabId) {
// Asignar puertos en base al tipo de nodo
if (node_type === "node1") {
element.style.background = "lightblue";
element.style.background = "#004D40";
element.setAttribute('data-node-type', 'node1');
const portOut = document.createElement('div');
portOut.className = 'port port-out';
element.appendChild(portOut);
} else if (node_type === "node2") {
element.style.background = "lightgreen";
element.style.background = "#00BFA5";
element.setAttribute('data-node-type', 'node2');
const portIn = document.createElement('div');
portIn.className = 'port port-in';
@ -133,7 +133,7 @@ function createNode(e, tabId) {
portOut.className = 'port port-out';
element.appendChild(portOut);
} else if (node_type === "node3") {
element.style.background = "lightsalmon";
element.style.background = "#DDDDDD";
element.setAttribute('data-node-type', 'node3');
const portIn = document.createElement("div");
portIn.className = "port port-in";
@ -142,7 +142,7 @@ function createNode(e, tabId) {
portOut.className = "port port-out";
element.appendChild(portOut); // Añadimos un puerto de salida
} else if (node_type === "node4") {
element.style.background = "lightseagreen"; // Cambiar color a lightseagreen
element.style.background = "#1DE9B6"; // Cambiar color a lightseagreen
element.setAttribute('data-node-type', 'node4');
const displayElement = document.createElement('div');
displayElement.className = 'display-extension';
@ -834,9 +834,14 @@ function openModal(e, node) {
// Obtener el tabId del nodo
const tabId = getTabIdFromNode(node);
const tabName = getTabName()
// Obtener nodeType desde el atributo data-node-type
const nodeType = node.getAttribute('data-node-type');
// Guardar los datos de la pestaña para incluir el título y la extensión
await saveTabData(tabId);
// Enviar los datos al servidor para actualizar node4
try {
const response = await fetch('/update_node4/', { // Vista ya creada en views.py
@ -847,6 +852,7 @@ function openModal(e, node) {
},
body: JSON.stringify({
tabId: tabId,
tabName: tabName,
nodeId: node.id, // Incluir nodeId
nodeType: nodeType, // Incluir nodeType
extensionNumber: newExtensionNumber
@ -865,16 +871,6 @@ function openModal(e, node) {
alert('Ocurrió un error al actualizar node4.');
}
// Actualizar el título en el backend si cambió
if (newAreaName !== oldTitle) {
// Enviar solicitud para actualizar el título del nodo4
// Esto podría hacerse a través de otra vista si es necesario
// Por ahora, asumimos que el título ya se actualizó en el frontend
}
// Guardar los datos de la pestaña para incluir el título y la extensión
await saveTabData(tabId);
// Cerrar el modal
modal.classList.remove("show");
background.classList.remove("show");

@ -73,9 +73,12 @@ async function loadExistingTabs() {
const response = await fetch('/list-tabs/');
if (response.ok) {
const data = await response.json();
let maxTabId = 0;
for (const tab of data.tabs) {
await createNewTab(tab.name, tab.id);
maxTabId = Math.max(maxTabId, parseInt(tab.id));
}
tabCount = maxTabId; // Establecer tabCount al ID máximo existente
} else {
console.error('Error al cargar las pestañas existentes');
}
@ -85,8 +88,15 @@ async function loadExistingTabs() {
}
async function createNewTab(tabName, existingTabId = null) {
tabCount++;
const tabId = existingTabId || tabCount;
let tabId;
if (existingTabId) {
// Usar el ID existente sin incrementar tabCount
tabId = existingTabId;
} else {
// Incrementar tabCount y asignarlo como nuevo ID
tabCount++;
tabId = tabCount;
}
const tabList = document.getElementById("tabsList");
const tabContent = document.getElementById("tabContent");
@ -126,7 +136,7 @@ async function createNewTab(tabName, existingTabId = null) {
</div>
<div class="right_sidebar">
<div class="node" data-node-type="node1">DID o Extensión</div>
<div class="node" data-node-type="node2">IVR</div>
<div class="node" data-node-type="node2">Audio</div>
<div class="node" data-node-type="node3">Prompt y Condiciones</div>
<div class="node" data-node-type="node4">Transferencia llamada</div>
<button class="btnGuardar" onclick="saveTabData(${tabId})">Guardar</button>
@ -148,15 +158,18 @@ async function createNewTab(tabName, existingTabId = null) {
});
});
// Activar la pestaña antes de inicializar el canvas y cargar los datos
activateTab(newTab); // Activar la pestaña recién creada
// Activar la pestaña recién creada
activateTab(newTab);
// Esperamos a que el DOM se actualice antes de inicializar el canvas y cargar los datos
// Esperar a que el DOM se actualice
await new Promise(resolve => requestAnimationFrame(resolve));
initializeCanvas(tabId); // Aseguramos que el canvas se inicialice antes de cargar los datos
initializeCanvas(tabId);
await loadTabData(tabId); // Cargar datos si ya existen
// Solo llamar loadTabData si es una pestaña existente (ya guardada en el servidor)
if (existingTabId) {
await loadTabData(tabId);
}
}
function activateTab(selectedTab) {
@ -207,7 +220,6 @@ async function renameTab(tabElement) {
if (result.status === 'success') {
tabElement.textContent = newName;
tabElement.setAttribute('data-tab-name', newName);
// También se puede actualizar el contenido si es necesario
alert("Pestaña renombrada exitosamente.");
} else {
alert(`Error al renombrar la pestaña: ${result.message}`);
@ -407,21 +419,23 @@ async function saveTabData(tabId) {
}
}
async function loadTabData(tabId) {
async function loadTabData(tabId, tabName = null) {
try {
const response = await fetch(`/load-tab-data/${tabId}/`);
if (response.ok) {
const data = await response.json();
// Asegurarse de que data.nodes y data.connections sean arrays
const nodesData = Array.isArray(data.nodes) ? data.nodes : [];
const connectionsData = Array.isArray(data.connections) ? data.connections : [];
const tabName = data.tabName || `Pestaña ${tabId}`;
// Utilizar el tabName proporcionado si data.tabName no está disponible
const effectiveTabName = data.tabName || tabName || `Pestaña ${tabId}`;
// Actualizar el nombre de la pestaña
const tabElement = document.getElementById(`tab${tabId}`);
if (tabElement) {
tabElement.textContent = tabName;
tabElement.setAttribute('data-tab-name', tabName);
tabElement.textContent = effectiveTabName;
tabElement.setAttribute('data-tab-name', effectiveTabName);
}
const contentElement = document.getElementById(`nodeContainer${tabId}`);
@ -506,7 +520,7 @@ async function loadTabData(tabId) {
const portOut = document.createElement('div');
portOut.className = "port port-out";
node.appendChild(portOut);
node.style.background = "lightblue";
node.style.background = "#004D40";
} else if (nodeData.nodeType === "node2") {
const portIn = document.createElement('div');
portIn.className = "port port-in";
@ -514,7 +528,7 @@ async function loadTabData(tabId) {
const portOut = document.createElement('div');
portOut.className = "port port-out";
node.appendChild(portOut);
node.style.background = "lightgreen";
node.style.background = "#00BFA5";
} else if (nodeData.nodeType === "node3") {
const portIn1 = document.createElement("div");
portIn1.className = "port port-in";
@ -529,7 +543,7 @@ async function loadTabData(tabId) {
const portOut2 = document.createElement("div");
portOut2.className = "port port-out";
node.appendChild(portOut2);
node.style.background = "lightsalmon";
node.style.background = "#DDDDDD";
} else if (nodeData.nodeType === "node4") {
const portIn1 = document.createElement("div");
portIn1.className = "port port-in";
@ -537,7 +551,7 @@ async function loadTabData(tabId) {
const portIn2 = document.createElement("div");
portIn2.className = "port port-in";
node.appendChild(portIn2);
node.style.background = "lightseagreen"; // Color actualizado
node.style.background = "#1DE9B6"; // Color actualizado
}
node.addEventListener('mousedown', (event) => {

@ -10,6 +10,9 @@
<title>Flow System</title>
<link rel="stylesheet" href="{% static 'css/flow.css' %}">
<link rel="stylesheet" href="{% static 'css/tabs.css' %}">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Markazi+Text:wght@400..700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container_create">

@ -751,319 +751,21 @@ def delete_node(request):
# Cargar el archivo de datos de la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
file_path = os.path.join(data_dir, f'tab_{tab_id}.json')
if not os.path.exists(file_path):
return JsonResponse({'status': 'error', 'message': 'Archivo de pestaña no encontrado'}, status=404)
with open(file_path, 'r', encoding='utf-8') as f:
tab_data = json.load(f)
# Eliminar el nodo y sus conexiones
tab_data['nodes'] = [node for node in tab_data.get('nodes', []) if node['id'] != node_id]
tab_data['connections'] = [conn for conn in tab_data.get('connections', [])
if conn['startNodeId'] != node_id and conn['endNodeId'] != node_id]
# Si es un nodo3 o nodo2, manejar eliminación de archivos
if node_type == 'node3':
# Lógica para eliminar archivos de texto
text_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}', 'text_files')
node_title = next((n['title'] for n in tab_data['nodes'] if n['id'] == node_id), None)
if node_title:
safe_title = "".join([c if c.isalnum() else "_" for c in node_title])
txt_filename = f"{safe_title}_{node_type}_{node_id}.txt"
txt_filepath = os.path.join(text_dir, txt_filename)
if os.path.exists(txt_filepath):
os.remove(txt_filepath)
elif node_type == 'node2':
# Lógica para eliminar archivos de audio
audio_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}', 'audio_files')
node_title = next((n['title'] for n in tab_data['nodes'] if n['id'] == node_id), None)
if node_title:
safe_title = "".join([c if c.isalnum() else "_" for c in node_title])
wav_filename = f"{safe_title}_{node_type}_{node_id}.wav"
wav_filepath = os.path.join(audio_dir, wav_filename)
if os.path.exists(wav_filepath):
os.remove(wav_filepath)
elif node_type == 'node4':
# Lógica para eliminar archivos o datos específicos de node4 si es necesario
pass # Por ahora, no hay archivos asociados a node4
# Guardar los datos actualizados
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(tab_data, f, ensure_ascii=False, indent=4)
return JsonResponse({'status': 'success', 'message': 'Nodo y sus conexiones eliminados exitosamente'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para eliminar una pestaña
@csrf_exempt
def delete_tab(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
tab_name = data.get('tabName')
if not tab_id or not tab_name:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Eliminar el archivo de datos de la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
file_path = os.path.join(data_dir, f'tab_{tab_id}.json')
if os.path.exists(file_path):
os.remove(file_path)
else:
# Buscar el archivo JSON correspondiente a la pestaña
matching_files = [f for f in os.listdir(data_dir) if f.startswith(f'tab_{tab_id}_') and f.endswith('.json')]
if not matching_files:
return JsonResponse({'status': 'error', 'message': 'Archivo de pestaña no encontrado'}, status=404)
# Eliminar los archivos de medios asociados (audio_files y text_files)
media_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}')
if os.path.exists(media_dir):
shutil.rmtree(media_dir)
# Eliminar el archivo de condiciones JSON
ivr_dir = '/home/Flow_IA/IVR/'
conditions_filename = f'tab_{tab_id}_conditions.json'
conditions_file_path = os.path.join(ivr_dir, conditions_filename)
if os.path.exists(conditions_file_path):
os.remove(conditions_file_path)
return JsonResponse({'status': 'success', 'message': 'Pestaña eliminada exitosamente'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para actualizar Node4
@csrf_exempt
def update_node4(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
node_id = data.get('nodeId')
node_type = data.get('nodeType')
extension_number = data.get('extensionNumber') # Renombrado de receivedString a extensionNumber
if not tab_id or not node_id or not node_type or extension_number is None:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
if node_type != "node4":
return JsonResponse({'status': 'error', 'message': 'Este endpoint solo es para node4'}, status=400)
# Guardar el número de extensión en el archivo de la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
file_path = os.path.join(data_dir, f'tab_{tab_id}.json')
if not os.path.exists(file_path):
return JsonResponse({'status': 'error', 'message': 'Archivo de pestaña no encontrado'}, status=404)
file_path = os.path.join(data_dir, matching_files[0])
with open(file_path, 'r', encoding='utf-8') as f:
tab_data = json.load(f)
# Buscar el node4 correspondiente y actualizar su extensionNumber
node_found = False
for node in tab_data.get('nodes', []):
if node['id'] == node_id and node['nodeType'] == 'node4':
node['extensionNumber'] = extension_number
node_found = True
break
if not node_found:
return JsonResponse({'status': 'error', 'message': 'node4 no encontrado'}, status=404)
# Guardar el archivo .json actualizado
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(tab_data, f, ensure_ascii=False, indent=4)
return JsonResponse({'status': 'success', 'message': 'node4 actualizado exitosamente'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para eliminar audio de Node2
@csrf_exempt
def delete_audio(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
node_id = data.get('nodeId') # Agregamos nodeId
node_type = data.get('nodeType')
old_audio_file = data.get('oldAudioFile')
if not tab_id or not node_id or not node_type or not old_audio_file:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Procesar solo si el nodo es node2
if node_type != "node2":
return JsonResponse({'status': 'error', 'message': 'Este endpoint solo es para node2'}, status=400)
# Obtener la ruta completa del archivo de audio
audio_file_path = os.path.join(settings.BASE_DIR, old_audio_file.replace(settings.MEDIA_URL, ''))
if os.path.exists(audio_file_path):
os.remove(audio_file_path)
return JsonResponse({'status': 'success', 'message': 'Archivo de audio eliminado correctamente'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para renombrar audio de Node2
@csrf_exempt
def rename_audio(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
node_id = data.get('nodeId') # Agregamos nodeId
node_type = data.get('nodeType')
old_title = data.get('oldTitle')
new_title = data.get('newTitle')
current_audio_file = data.get('currentAudioFile')
if not tab_id or not node_id or not node_type or not old_title or not new_title or not current_audio_file:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Procesar solo si el nodo es node2
if node_type != "node2":
return JsonResponse({'status': 'error', 'message': 'Este endpoint solo es para node2'}, status=400)
# Directorios específicos para la pestaña
tab_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}')
audio_dir = os.path.join(tab_dir, 'audio_files')
os.makedirs(audio_dir, exist_ok=True)
# Generar nombres de archivo basados en los títulos antiguos y nuevos y node_id
safe_old_title = "".join([c if c.isalnum() else "_" for c in old_title])
safe_new_title = "".join([c if c.isalnum() else "_" for c in new_title])
old_filename_base = f"{safe_old_title}_{node_type}_{node_id}"
new_filename_base = f"{safe_new_title}_{node_type}_{node_id}"
old_wav_filename = f"{old_filename_base}.wav"
new_wav_filename = f"{new_filename_base}.wav"
old_wav_filepath = os.path.join(audio_dir, old_wav_filename)
new_wav_filepath = os.path.join(audio_dir, new_wav_filename)
# Renombrar el archivo de audio
if os.path.exists(old_wav_filepath):
os.rename(old_wav_filepath, new_wav_filepath)
# Generar URL del nuevo archivo
new_audio_file_url = os.path.join(settings.MEDIA_URL, 'tabs', f'tab_{tab_id}', 'audio_files', new_wav_filename)
return JsonResponse({
'status': 'success',
'newAudioFile': new_audio_file_url
})
else:
return JsonResponse({'status': 'error', 'message': 'Archivo de audio original no encontrado'}, status=404)
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para actualizar Node1 (título y extensión)
@csrf_exempt
def update_node1(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
tab_name = data.get('tabName')
node_id = data.get('nodeId') # Agregamos nodeId
node_type = data.get('nodeType')
new_title = data.get('newTitle')
new_extension = data.get('newExtension')
old_title = data.get('oldTitle')
directory = '/etc/asterisk/extensions_custom.conf'
file_content = f"""
exten => {new_extension},1,Answer()
exten => {new_extension},n,AGI(/var/lib/asterisk/agi-bin/{tab_name}.py)
exten => {new_extension},n,Hangup()
"""
try:
subprocess.run(['sudo', 'test', '-f', directory], check=True)
# Escribir en el archivo con permisos de root
subprocess.run(['sudo', 'bash', '-c', f'echo "{file_content}" >> {directory}'], check=True)
except subprocess.CalledProcessError as e:
print('No fue posible crear o escribir en el archivo:', e)
if not tab_id or not node_id or not node_type or not new_title or not new_extension or not old_title:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Procesar solo si el nodo es node1
if node_type != "node1":
return JsonResponse({'status': 'error', 'message': 'Este endpoint solo es para node1'}, status=400)
# Directorios específicos para la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
file_path = os.path.join(data_dir, f'tab_{tab_id}.json')
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
tab_data = json.load(f)
# Buscar el nodo correspondiente y actualizar sus datos
for node in tab_data.get('nodes', []):
if node['id'] == node_id and node['nodeType'] == 'node1':
node['title'] = new_title
node['extensionNumber'] = new_extension
break
# Guardar el archivo .json actualizado
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(tab_data, f, ensure_ascii=False, indent=4)
return JsonResponse({
'status': 'success',
'message': 'Nodo1 actualizado exitosamente'
})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para eliminar un nodo
@csrf_exempt
def delete_node(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
node_id = data.get('nodeId')
node_type = data.get('nodeType')
if not tab_id or not node_id or not node_type:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Cargar el archivo de datos de la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
file_path = os.path.join(data_dir, f'tab_{tab_id}.json')
if not os.path.exists(file_path):
return JsonResponse({'status': 'error', 'message': 'Archivo de pestaña no encontrado'}, status=404)
with open(file_path, 'r', encoding='utf-8') as f:
tab_data = json.load(f)
# Obtener los datos del nodo antes de eliminarlo
node_data = next((n for n in tab_data.get('nodes', []) if n['id'] == node_id), None)
if not node_data:
return JsonResponse({'status': 'error', 'message': 'Nodo no encontrado en la pestaña'}, status=404)
node_title = node_data.get('title', '')
# Eliminar el nodo y sus conexiones
tab_data['nodes'] = [node for node in tab_data.get('nodes', []) if node['id'] != node_id]
@ -1074,29 +776,21 @@ def delete_node(request):
if node_type == 'node3':
# Lógica para eliminar archivos de texto
text_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}', 'text_files')
node_title = next((n['title'] for n in tab_data['nodes'] if n['id'] == node_id), None)
if node_title:
safe_title = "".join([c if c.isalnum() else "_" for c in node_title])
txt_filename = f"{safe_title}_{node_type}_{node_id}.txt"
txt_filepath = os.path.join(text_dir, txt_filename)
if os.path.exists(txt_filepath):
os.remove(txt_filepath)
safe_title = "".join([c if c.isalnum() else "_" for c in node_title])
txt_filename = f"{safe_title}_{node_type}_{node_id}.txt"
txt_filepath = os.path.join(text_dir, txt_filename)
if os.path.exists(txt_filepath):
os.remove(txt_filepath)
elif node_type == 'node2':
# Lógica para eliminar archivos de audio
audio_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}', 'audio_files')
node_title = next((n['title'] for n in tab_data['nodes'] if n['id'] == node_id), None)
if node_title:
safe_title = "".join([c if c.isalnum() else "_" for c in node_title])
wav_filename = f"{safe_title}_{node_type}_{node_id}.wav"
wav_filepath = os.path.join(audio_dir, wav_filename)
if os.path.exists(wav_filepath):
os.remove(wav_filepath)
elif node_type == 'node4':
# Lógica para eliminar archivos o datos específicos de node4 si es necesario
pass # Por ahora, no hay archivos asociados a node4
safe_title = "".join([c if c.isalnum() else "_" for c in node_title])
wav_filename = f"{safe_title}_{node_type}_{node_id}.wav"
wav_filepath = os.path.join(audio_dir, wav_filename)
if os.path.exists(wav_filepath):
os.remove(wav_filepath)
# Guardar los datos actualizados
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(tab_data, f, ensure_ascii=False, indent=4)
@ -1122,13 +816,14 @@ def delete_tab(request):
# Eliminar el archivo de datos de la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
file_path = os.path.join(data_dir, f'tab_{tab_id}.json')
if os.path.exists(file_path):
os.remove(file_path)
else:
# Buscar el archivo JSON correspondiente a la pestaña
matching_files = [f for f in os.listdir(data_dir) if f.startswith(f'tab_{tab_id}_') and f.endswith('.json')]
if not matching_files:
return JsonResponse({'status': 'error', 'message': 'Archivo de pestaña no encontrado'}, status=404)
file_path = os.path.join(data_dir, matching_files[0])
os.remove(file_path)
# Eliminar los archivos de medios asociados (audio_files y text_files)
media_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}')
if os.path.exists(media_dir):
@ -1155,6 +850,7 @@ def update_node4(request):
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
tab_name = data.get('tabName')
node_id = data.get('nodeId')
node_type = data.get('nodeType')
extension_number = data.get('extensionNumber') # Renombrado de receivedString a extensionNumber
@ -1167,7 +863,7 @@ def update_node4(request):
# Guardar el número de extensión en el archivo de la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
file_path = os.path.join(data_dir, f'tab_{tab_id}.json')
file_path = os.path.join(data_dir, f'tab_{tab_id}_{tab_name}.json')
if not os.path.exists(file_path):
return JsonResponse({'status': 'error', 'message': 'Archivo de pestaña no encontrado'}, status=404)

Loading…
Cancel
Save