{
 "name": "Estudio Paz · facturas de proveedores",
 "nodes": [
  {
   "id": "9ceeb038-8c72-4888-8f9d-a38c51139c09",
   "name": "Nota 8c3b",
   "type": "n8n-nodes-base.stickyNote",
   "typeVersion": 1,
   "position": [
    -40,
    -340
   ],
   "parameters": {
    "content": "## Facturas de proveedores\nSe sube la factura en PDF: n8n la lee, la IA extrae proveedor, CUIT, número, fechas e importe, el código valida el CUIT con su dígito verificador y la guarda. Si vence en 5 días o menos, avisa en el momento. Aparte, todos los días a las 9 manda el resumen de lo que vence en la semana.",
    "width": 560,
    "height": 200,
    "color": 6
   }
  },
  {
   "id": "2f156483-df3d-4d69-a12e-d1a38015ac54",
   "name": "Subir factura",
   "type": "n8n-nodes-base.formTrigger",
   "typeVersion": 2.6,
   "position": [
    0,
    0
   ],
   "parameters": {
    "formTitle": "Estudio Paz · Carga de facturas",
    "formDescription": "Sube la factura del proveedor en PDF. La leemos, la cargamos y te avisamos antes del vencimiento.",
    "formFields": {
     "values": [
      {
       "fieldLabel": "Factura (PDF)",
       "fieldName": "factura",
       "fieldType": "file",
       "requiredField": true,
       "multipleFiles": false,
       "acceptFileTypes": ".pdf"
      },
      {
       "fieldLabel": "Comentario",
       "fieldName": "comentario",
       "fieldType": "text",
       "requiredField": false,
       "placeholder": "Opcional"
      }
     ]
    },
    "responseMode": "onReceived",
    "options": {
     "appendAttribution": false,
     "buttonLabel": "Cargar factura",
     "path": "facturas-estudio",
     "respondWithOptions": {
      "values": {
       "respondWith": "text",
       "formSubmittedText": "Factura recibida: ya está en la planilla."
      }
     }
    }
   },
   "webhookId": "25d06f35-c8d3-407a-8db4-284ef094b23d"
  },
  {
   "id": "1db7e86a-105f-4da2-9b8d-8d70d216c2c8",
   "name": "Leer el PDF",
   "type": "n8n-nodes-base.extractFromFile",
   "typeVersion": 1.1,
   "position": [
    240,
    0
   ],
   "parameters": {
    "operation": "pdf",
    "binaryPropertyName": "factura",
    "options": {}
   }
  },
  {
   "id": "d6e266a7-67ab-4e82-9783-c0f07cbbf18e",
   "name": "La IA extrae los datos",
   "type": "@n8n/n8n-nodes-langchain.informationExtractor",
   "typeVersion": 1.2,
   "position": [
    480,
    0
   ],
   "parameters": {
    "text": "={{ $json.text }}",
    "schemaType": "fromAttributes",
    "attributes": {
     "attributes": [
      {
       "name": "proveedor",
       "type": "string",
       "description": "Razón social del EMISOR de la factura",
       "required": true
      },
      {
       "name": "cuit",
       "type": "string",
       "description": "CUIT del emisor, tal como figura",
       "required": true
      },
      {
       "name": "tipo",
       "type": "string",
       "description": "Letra de la factura: A, B o C"
      },
      {
       "name": "numero",
       "type": "string",
       "description": "Punto de venta y número, ej. 0003-00001234"
      },
      {
       "name": "emision",
       "type": "string",
       "description": "Fecha de emisión AAAA-MM-DD"
      },
      {
       "name": "vencimiento",
       "type": "string",
       "description": "Fecha de vencimiento del pago AAAA-MM-DD; vacío si no figura"
      },
      {
       "name": "importe",
       "type": "number",
       "description": "Importe TOTAL a pagar, sólo el número",
       "required": true
      },
      {
       "name": "moneda",
       "type": "string",
       "description": "ARS o USD"
      },
      {
       "name": "categoria",
       "type": "string",
       "description": "Una de: servicios, alquiler, insumos, honorarios, impuestos, otros"
      }
     ]
    },
    "options": {
     "systemPromptTemplate": "Lees facturas argentinas. El emisor es quien factura (arriba), no el cliente. Fechas siempre AAAA-MM-DD."
    }
   }
  },
  {
   "id": "b04880ca-30e1-48d9-a016-90869309a036",
   "name": "Claude",
   "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
   "typeVersion": 1.6,
   "position": [
    500,
    220
   ],
   "parameters": {
    "model": {
     "__rl": true,
     "mode": "id",
     "value": "claude-sonnet-5-5"
    },
    "options": {
     "maxTokensToSample": 1200,
     "thinkingMode": "adaptive",
     "effort": "low"
    }
   },
   "credentials": {
    "anthropicApi": {
     "id": "",
     "name": "Claude (demos)"
    }
   }
  },
  {
   "id": "a15fd4bb-ddcd-4bde-acc8-627cace24204",
   "name": "Validar CUIT y vencimiento",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    740,
    0
   ],
   "parameters": {
    "mode": "runOnceForEachItem",
    "jsCode": "\n// Valida el CUIT con su dígito verificador (módulo 11) y calcula los días que faltan para el vencimiento.\nconst d = $json.output || {};\nconst cuit = String(d.cuit || '').replace(/\\D/g, '');\nconst pesos = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];\nlet ok = cuit.length === 11;\nif (ok) { const s = pesos.reduce((a, p, i) => a + p * Number(cuit[i]), 0); let v = 11 - (s % 11); v = v === 11 ? 0 : v === 10 ? 9 : v; ok = v === Number(cuit[10]); }\nconst hoy = new Date(); hoy.setHours(0, 0, 0, 0);\nconst vence = d.vencimiento ? new Date(d.vencimiento + 'T00:00:00') : null;\nconst dias = vence ? Math.round((vence - hoy) / 864e5) : null;\nconst importe = Number(String(d.importe ?? '').replace(/[^\\d.,-]/g, '').replace(/\\.(?=\\d{3}(\\D|$))/g, '').replace(',', '.')) || 0;\nreturn { ...d, cuit: cuit.length === 11 ? `${cuit.slice(0, 2)}-${cuit.slice(2, 10)}-${cuit[10]}` : d.cuit, cuit_valido: ok, dias_al_vencimiento: dias, importe,\n  importe_texto: (d.moneda === 'USD' ? 'USD ' : '$ ') + importe.toLocaleString('es-AR', { minimumFractionDigits: 2 }) };\n"
   }
  },
  {
   "id": "cdd8e624-14f3-474c-8b23-fb7674b0c7fa",
   "name": "¿CUIT válido?",
   "type": "n8n-nodes-base.if",
   "typeVersion": 2.3,
   "position": [
    980,
    0
   ],
   "parameters": {
    "conditions": {
     "options": {
      "caseSensitive": true,
      "leftValue": "",
      "typeValidation": "loose",
      "version": 2
     },
     "conditions": [
      {
       "id": "33613eee-738d-498d-90f4-0d536dfa02a2",
       "leftValue": "={{ $json.cuit_valido }}",
       "rightValue": "",
       "operator": {
        "type": "boolean",
        "operation": "true"
       }
      }
     ],
     "combinator": "and"
    },
    "options": {}
   }
  },
  {
   "id": "8d5b01c1-a905-4e5d-97ff-e61bbcc879fd",
   "name": "Planilla de facturas",
   "type": "n8n-nodes-base.dataTable",
   "typeVersion": 1.1,
   "position": [
    1220,
    -80
   ],
   "parameters": {
    "resource": "row",
    "operation": "insert",
    "dataTableId": {
     "__rl": true,
     "mode": "id",
     "value": ""
    },
    "columns": {
     "mappingMode": "defineBelow",
     "value": {
      "cargada": "={{ $now.toISODate() }}",
      "proveedor": "={{ $json.proveedor }}",
      "cuit": "={{ $json.cuit }}",
      "numero": "={{ ($json.tipo ? $json.tipo + ' ' : '') + ($json.numero || '') }}",
      "emision": "={{ $json.emision }}",
      "vencimiento": "={{ $json.vencimiento }}",
      "importe": "={{ $json.importe }}",
      "moneda": "={{ $json.moneda || 'ARS' }}",
      "categoria": "={{ $json.categoria }}",
      "estado": "pendiente"
     },
     "matchingColumns": [],
     "schema": [
      {
       "id": "cargada",
       "displayName": "cargada",
       "required": false,
       "defaultMatch": false,
       "display": true,
       "type": "string",
       "canBeUsedToMatch": true
      },
      {
       "id": "proveedor",
       "displayName": "proveedor",
       "required": false,
       "defaultMatch": false,
       "display": true,
       "type": "string",
       "canBeUsedToMatch": true
      },
      {
       "id": "cuit",
       "displayName": "cuit",
       "required": false,
       "defaultMatch": false,
       "display": true,
       "type": "string",
       "canBeUsedToMatch": true
      },
      {
       "id": "numero",
       "displayName": "numero",
       "required": false,
       "defaultMatch": false,
       "display": true,
       "type": "string",
       "canBeUsedToMatch": true
      },
      {
       "id": "emision",
       "displayName": "emision",
       "required": false,
       "defaultMatch": false,
       "display": true,
       "type": "string",
       "canBeUsedToMatch": true
      },
      {
       "id": "vencimiento",
       "displayName": "vencimiento",
       "required": false,
       "defaultMatch": false,
       "display": true,
       "type": "string",
       "canBeUsedToMatch": true
      },
      {
       "id": "importe",
       "displayName": "importe",
       "required": false,
       "defaultMatch": false,
       "display": true,
       "type": "string",
       "canBeUsedToMatch": true
      },
      {
       "id": "moneda",
       "displayName": "moneda",
       "required": false,
       "defaultMatch": false,
       "display": true,
       "type": "string",
       "canBeUsedToMatch": true
      },
      {
       "id": "categoria",
       "displayName": "categoria",
       "required": false,
       "defaultMatch": false,
       "display": true,
       "type": "string",
       "canBeUsedToMatch": true
      },
      {
       "id": "estado",
       "displayName": "estado",
       "required": false,
       "defaultMatch": false,
       "display": true,
       "type": "string",
       "canBeUsedToMatch": true
      }
     ]
    },
    "options": {}
   }
  },
  {
   "id": "7842a8c5-0126-4ea9-a46b-adbbf65462b7",
   "name": "¿Vence en 5 días o menos?",
   "type": "n8n-nodes-base.if",
   "typeVersion": 2.3,
   "position": [
    1460,
    -80
   ],
   "parameters": {
    "conditions": {
     "options": {
      "caseSensitive": true,
      "leftValue": "",
      "typeValidation": "loose",
      "version": 2
     },
     "conditions": [
      {
       "id": "5fef119f-2cbb-4348-b4f5-16e6dea42d7c",
       "leftValue": "={{ $('Validar CUIT y vencimiento').item.json.dias_al_vencimiento ?? 999 }}",
       "rightValue": 5,
       "operator": {
        "type": "number",
        "operation": "lte"
       }
      }
     ],
     "combinator": "and"
    },
    "options": {}
   }
  },
  {
   "id": "d670f7e8-e414-4180-b26a-3ded66c65ab0",
   "name": "Alerta de vencimiento",
   "type": "n8n-nodes-base.emailSend",
   "typeVersion": 2.1,
   "position": [
    1700,
    -160
   ],
   "parameters": {
    "fromEmail": "Facturas · Estudio Paz <facturas@estudiopaz.demo>",
    "toEmail": "pagos@estudiopaz.demo",
    "subject": "={{ '⏰ ' + $('Validar CUIT y vencimiento').item.json.proveedor + ' vence ' + ($('Validar CUIT y vencimiento').item.json.dias_al_vencimiento <= 0 ? 'HOY' : 'en ' + $('Validar CUIT y vencimiento').item.json.dias_al_vencimiento + ' días') }}",
    "emailFormat": "html",
    "html": "={{ `<p>${$('Validar CUIT y vencimiento').item.json.proveedor} (${$('Validar CUIT y vencimiento').item.json.cuit}) · ${$('Validar CUIT y vencimiento').item.json.importe_texto} · vence ${$('Validar CUIT y vencimiento').item.json.vencimiento}.</p>` }}",
    "options": {
     "appendAttribution": false
    }
   },
   "credentials": {
    "smtp": {
     "id": "",
     "name": "Buzón de prueba (Mailpit)"
    }
   }
  },
  {
   "id": "5020c30c-cdd3-488e-8edb-f2fd68996515",
   "name": "CUIT inválido: revisar",
   "type": "n8n-nodes-base.emailSend",
   "typeVersion": 2.1,
   "position": [
    1220,
    140
   ],
   "parameters": {
    "fromEmail": "Facturas · Estudio Paz <facturas@estudiopaz.demo>",
    "toEmail": "pagos@estudiopaz.demo",
    "subject": "={{ 'Revisar factura de ' + $json.proveedor + ': el CUIT no valida' }}",
    "emailFormat": "html",
    "html": "={{ `<p>El CUIT <b>${$json.cuit}</b> no pasa el dígito verificador. La factura no se cargó: revisala a mano.</p>` }}",
    "options": {
     "appendAttribution": false
    }
   },
   "credentials": {
    "smtp": {
     "id": "",
     "name": "Buzón de prueba (Mailpit)"
    }
   }
  },
  {
   "id": "b5612fec-6b43-4f25-b282-ec7a5c09a7df",
   "name": "Todos los días a las 9",
   "type": "n8n-nodes-base.scheduleTrigger",
   "typeVersion": 1.2,
   "position": [
    0,
    420
   ],
   "parameters": {
    "rule": {
     "interval": [
      {
       "field": "cronExpression",
       "expression": "0 9 * * 1-5"
      }
     ]
    }
   }
  },
  {
   "id": "c8c1398e-dc84-42de-92a3-b17edb9590e6",
   "name": "Facturas pendientes",
   "type": "n8n-nodes-base.dataTable",
   "typeVersion": 1.1,
   "position": [
    240,
    420
   ],
   "parameters": {
    "resource": "row",
    "operation": "get",
    "dataTableId": {
     "__rl": true,
     "mode": "id",
     "value": ""
    },
    "matchType": "allConditions",
    "filters": {
     "conditions": [
      {
       "keyName": "estado",
       "condition": "eq",
       "keyValue": "pendiente"
      }
     ]
    },
    "returnAll": true
   },
   "alwaysOutputData": false
  },
  {
   "id": "0976b3ee-3fe2-4797-a7df-a75887cd5b58",
   "name": "Vencen esta semana",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    480,
    420
   ],
   "parameters": {
    "mode": "runOnceForAllItems",
    "jsCode": "\n// Resumen diario: facturas PENDIENTES que vencen en los próximos 7 días (o ya vencidas). Sin nada que avisar, no manda mail.\nconst hoy = new Date(); hoy.setHours(0, 0, 0, 0);\nconst filas = $input.all().map(i => i.json).filter(f => f.estado === 'pendiente' && f.vencimiento);\nconst proximas = filas.map(f => ({ ...f, dias: Math.round((new Date(String(f.vencimiento).slice(0, 10) + 'T00:00:00') - hoy) / 864e5) }))\n  .filter(f => f.dias <= 7).sort((a, b) => a.dias - b.dias);\nif (!proximas.length) return [];\nconst total = proximas.reduce((s, f) => s + Number(f.importe || 0), 0);\nconst fila = f => `<tr><td>${f.proveedor}</td><td>${String(f.vencimiento).slice(0, 10)}</td><td style=\"text-align:right\">$ ${Number(f.importe).toLocaleString('es-AR')}</td><td>${f.dias < 0 ? '<b style=\"color:#B3261E\">vencida</b>' : f.dias === 0 ? '<b>hoy</b>' : 'en ' + f.dias + ' días'}</td></tr>`;\nconst uno = proximas.length === 1;\nreturn [{ json: { cantidad: proximas.length, total: '$ ' + total.toLocaleString('es-AR'),\n  asunto: `📋 ${uno ? '1 factura vence' : proximas.length + ' facturas vencen'} esta semana · $ ${total.toLocaleString('es-AR')}`,\n  html: `<p>${uno ? 'Vence esta semana <b>1</b> factura' : `Vencen esta semana <b>${proximas.length}</b> facturas`} por <b>$ ${total.toLocaleString('es-AR')}</b>:</p><table cellpadding=\"6\" style=\"border-collapse:collapse\">${proximas.map(fila).join('')}</table>` } }];\n"
   }
  },
  {
   "id": "82e7c2ee-c88c-4c63-92d6-2ee50e78d248",
   "name": "Resumen semanal",
   "type": "n8n-nodes-base.emailSend",
   "typeVersion": 2.1,
   "position": [
    720,
    420
   ],
   "parameters": {
    "fromEmail": "Facturas · Estudio Paz <facturas@estudiopaz.demo>",
    "toEmail": "pagos@estudiopaz.demo",
    "subject": "={{ $json.asunto }}",
    "emailFormat": "html",
    "html": "={{ $json.html }}",
    "options": {
     "appendAttribution": false
    }
   },
   "credentials": {
    "smtp": {
     "id": "",
     "name": "Buzón de prueba (Mailpit)"
    }
   }
  }
 ],
 "connections": {
  "Subir factura": {
   "main": [
    [
     {
      "node": "Leer el PDF",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Leer el PDF": {
   "main": [
    [
     {
      "node": "La IA extrae los datos",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Claude": {
   "ai_languageModel": [
    [
     {
      "node": "La IA extrae los datos",
      "type": "ai_languageModel",
      "index": 0
     }
    ]
   ]
  },
  "La IA extrae los datos": {
   "main": [
    [
     {
      "node": "Validar CUIT y vencimiento",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Validar CUIT y vencimiento": {
   "main": [
    [
     {
      "node": "¿CUIT válido?",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "¿CUIT válido?": {
   "main": [
    [
     {
      "node": "Planilla de facturas",
      "type": "main",
      "index": 0
     }
    ],
    [
     {
      "node": "CUIT inválido: revisar",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Planilla de facturas": {
   "main": [
    [
     {
      "node": "¿Vence en 5 días o menos?",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "¿Vence en 5 días o menos?": {
   "main": [
    [
     {
      "node": "Alerta de vencimiento",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Todos los días a las 9": {
   "main": [
    [
     {
      "node": "Facturas pendientes",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Facturas pendientes": {
   "main": [
    [
     {
      "node": "Vencen esta semana",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Vencen esta semana": {
   "main": [
    [
     {
      "node": "Resumen semanal",
      "type": "main",
      "index": 0
     }
    ]
   ]
  }
 },
 "settings": {
  "executionOrder": "v1",
  "timezone": "America/Argentina/Buenos_Aires",
  "saveManualExecutions": true
 }
}