angelsg213 commited on
Commit
e45829e
·
verified ·
1 Parent(s): 9085ed2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -41
app.py CHANGED
@@ -22,9 +22,9 @@ def extraer_texto_pdf(pdf_file):
22
  def analizar_y_convertir_json(texto):
23
  """El LLM lee la factura, decide cómo estructurarla y devuelve JSON"""
24
 
25
- token = os.getenv("HF_TOKEN")
26
  if not token:
27
- return None, "Error: Falta configurar HF_TOKEN en Settings → Secrets"
28
 
29
  # Limitar texto
30
  texto_limpio = texto[:8000]
@@ -112,7 +112,11 @@ Responde SOLO con el JSON válido (sin explicaciones, sin markdown):"""
112
  try:
113
  datos_json = json.loads(json_str)
114
  print(f"✅ JSON válido extraído con {modelo}")
115
- return datos_json, f"✅ Procesado con {modelo}"
 
 
 
 
116
  except json.JSONDecodeError as e:
117
  print(f"⚠️ JSON inválido: {str(e)[:50]}")
118
  continue
@@ -124,7 +128,35 @@ Responde SOLO con el JSON válido (sin explicaciones, sin markdown):"""
124
  print(f"❌ {modelo} falló: {str(e)[:100]}")
125
  continue
126
 
127
- return None, "Ningún modelo LLM pudo extraer el JSON. Verifica tu HF_TOKEN."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
  # ============= CONVERTIR JSON A CSV =============
130
  def json_a_csv(datos_json):
@@ -199,44 +231,44 @@ def json_a_csv(datos_json):
199
  # ============= FUNCIÓN PRINCIPAL =============
200
  def procesar_factura(pdf_file):
201
  if pdf_file is None:
202
- return "", None, None, "⚠️ Sube un PDF primero"
203
 
204
  # PASO 1: Extraer texto del PDF
205
- print("\n📄 Extrayendo texto del PDF...")
206
  texto = extraer_texto_pdf(pdf_file)
207
 
208
  if texto.startswith("Error"):
209
- return "", None, None, f" {texto}"
210
 
211
  # Mostrar preview del texto
212
  texto_preview = f"{texto[:1500]}..." if len(texto) > 1500 else texto
213
 
214
  # PASO 2: LLM analiza y convierte a JSON
215
- print("🤖 El LLM está analizando la factura y creando el JSON...")
216
- datos_json, mensaje = analizar_y_convertir_json(texto)
217
 
218
  if not datos_json:
219
- return texto_preview, None, None, mensaje
220
 
221
  # PASO 3: Convertir JSON a DataFrame
222
- print("📊 Convirtiendo JSON a CSV...")
223
  df = json_a_csv(datos_json)
224
 
225
  # PASO 4: Guardar CSV
226
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
227
  numero = datos_json.get('numero_factura', 'factura')
228
- numero = re.sub(r'[^\w\-]', '_', str(numero)) # Limpiar caracteres especiales
229
  csv_filename = f"{numero}_{timestamp}.csv"
230
  df.to_csv(csv_filename, index=False, encoding='utf-8-sig')
231
 
232
- # PASO 5: Crear resumen
233
- resumen = f"""## Factura Procesada Exitosamente
234
 
235
- {mensaje}
236
 
237
  ---
238
 
239
- ### 📊 JSON Generado por el LLM:
240
 
241
  ```json
242
  {json.dumps(datos_json, indent=2, ensure_ascii=False)}
@@ -244,66 +276,118 @@ def procesar_factura(pdf_file):
244
 
245
  ---
246
 
247
- ### 💾 Archivo CSV:
248
- - **Nombre:** `{csv_filename}`
249
- - **Filas:** {len(df)}
 
 
 
 
250
 
251
- ### 📋 Datos Extraídos:
252
- - **Número:** {datos_json.get('numero_factura', 'N/A')}
253
- - **Fecha:** {datos_json.get('fecha', 'N/A')}
254
- - **Productos:** {len(datos_json.get('productos', datos_json.get('conceptos', [])))}
255
- - **Total:** {datos_json.get('totales', {}).get('total', datos_json.get('total', 'N/A'))}
 
256
  """
257
 
258
- print(f" CSV guardado: {csv_filename}")
259
- return texto_preview, df, csv_filename, resumen
260
 
261
  # ============= INTERFAZ GRADIO =============
262
- with gr.Blocks(title="Extractor IA de Facturas") as demo:
 
 
 
 
 
 
 
263
  gr.Markdown("""
264
  # Extractor Inteligente de Facturas
265
  ### Análisis automático de facturas PDF con Inteligencia Artificial
266
  """)
267
 
 
 
268
  with gr.Row():
269
- # Columna izquierda - Input
270
  with gr.Column(scale=1):
271
- gr.Markdown("### Archivo de entrada")
272
  pdf_input = gr.File(
273
  label="Seleccionar factura PDF",
274
  file_types=[".pdf"],
275
  type="filepath"
276
  )
277
- btn = gr.Button("Procesar Factura", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
278
 
279
- # Columna derecha - Resultados
280
  with gr.Column(scale=2):
281
- gr.Markdown("### Resultados del análisis")
 
 
 
 
 
 
 
 
 
 
282
 
 
 
 
283
  with gr.Tabs():
284
  with gr.Tab("Vista Previa CSV"):
 
285
  tabla_preview = gr.DataFrame(
286
- label="Datos extraídos",
287
  wrap=True,
288
- interactive=False
 
289
  )
290
 
291
  with gr.Tab("Texto Original"):
 
292
  texto_extraido = gr.Textbox(
293
  label="Texto extraído del PDF",
294
- lines=15,
295
- max_lines=20
 
296
  )
297
 
298
- with gr.Tab("Análisis JSON"):
299
- resumen = gr.Markdown(label="Estructura de datos")
300
-
301
- csv_output = gr.File(label="Descargar archivo CSV")
 
 
 
 
 
 
 
 
 
302
 
 
303
  btn.click(
304
  fn=procesar_factura,
305
  inputs=[pdf_input],
306
- outputs=[texto_extraido, tabla_preview, csv_output, resumen]
307
  )
308
 
309
  if __name__ == "__main__":
 
22
  def analizar_y_convertir_json(texto):
23
  """El LLM lee la factura, decide cómo estructurarla y devuelve JSON"""
24
 
25
+ token = os.getenv("aa")
26
  if not token:
27
+ return None, None, "Error: Falta configurar HF_TOKEN en Settings → Secrets"
28
 
29
  # Limitar texto
30
  texto_limpio = texto[:8000]
 
112
  try:
113
  datos_json = json.loads(json_str)
114
  print(f"✅ JSON válido extraído con {modelo}")
115
+
116
+ # Generar resumen de información útil
117
+ resumen_util = generar_resumen_util(texto_limpio, modelo, client)
118
+
119
+ return datos_json, resumen_util, f"Procesado con {modelo}"
120
  except json.JSONDecodeError as e:
121
  print(f"⚠️ JSON inválido: {str(e)[:50]}")
122
  continue
 
128
  print(f"❌ {modelo} falló: {str(e)[:100]}")
129
  continue
130
 
131
+ return None, None, "Ningún modelo LLM pudo extraer el JSON. Verifica tu HF_TOKEN."
132
+
133
+ # ============= GENERAR RESUMEN ÚTIL =============
134
+ def generar_resumen_util(texto, modelo, client):
135
+ """Genera un resumen con información útil para administrativos"""
136
+
137
+ prompt_resumen = f"""Analiza esta factura y proporciona información útil para un administrativo o usuario medio.
138
+
139
+ TEXTO DE LA FACTURA:
140
+ {texto[:6000]}
141
+
142
+ Genera un resumen estructurado con:
143
+ 1. ESTADO DE PAGO: ¿Está pagada? ¿Fecha de vencimiento?
144
+ 2. INFORMACIÓN CLAVE: Datos importantes que destacar
145
+ 3. ALERTAS: Cualquier aspecto que requiera atención (vencimientos, importes altos, etc.)
146
+ 4. RESUMEN EJECUTIVO: Descripción breve y clara de la factura
147
+
148
+ Responde en español de forma clara y profesional:"""
149
+
150
+ try:
151
+ response = client.chat.completions.create(
152
+ model=modelo,
153
+ messages=[{"role": "user", "content": prompt_resumen}],
154
+ max_tokens=800,
155
+ temperature=0.4
156
+ )
157
+ return response.choices[0].message.content
158
+ except:
159
+ return "No se pudo generar el resumen de información útil."
160
 
161
  # ============= CONVERTIR JSON A CSV =============
162
  def json_a_csv(datos_json):
 
231
  # ============= FUNCIÓN PRINCIPAL =============
232
  def procesar_factura(pdf_file):
233
  if pdf_file is None:
234
+ return "", None, None, "", "Sube un PDF primero"
235
 
236
  # PASO 1: Extraer texto del PDF
237
+ print("\n--- Extrayendo texto del PDF...")
238
  texto = extraer_texto_pdf(pdf_file)
239
 
240
  if texto.startswith("Error"):
241
+ return "", None, None, "", f"Error: {texto}"
242
 
243
  # Mostrar preview del texto
244
  texto_preview = f"{texto[:1500]}..." if len(texto) > 1500 else texto
245
 
246
  # PASO 2: LLM analiza y convierte a JSON
247
+ print("--- El LLM está analizando la factura y creando el JSON...")
248
+ datos_json, resumen_util, mensaje = analizar_y_convertir_json(texto)
249
 
250
  if not datos_json:
251
+ return texto_preview, None, None, "", mensaje
252
 
253
  # PASO 3: Convertir JSON a DataFrame
254
+ print("--- Convirtiendo JSON a CSV...")
255
  df = json_a_csv(datos_json)
256
 
257
  # PASO 4: Guardar CSV
258
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
259
  numero = datos_json.get('numero_factura', 'factura')
260
+ numero = re.sub(r'[^\w\-]', '_', str(numero))
261
  csv_filename = f"{numero}_{timestamp}.csv"
262
  df.to_csv(csv_filename, index=False, encoding='utf-8-sig')
263
 
264
+ # PASO 5: Crear resumen técnico
265
+ resumen_tecnico = f"""## Factura Procesada Exitosamente
266
 
267
+ **Modelo utilizado:** {mensaje}
268
 
269
  ---
270
 
271
+ ### Estructura JSON Generada
272
 
273
  ```json
274
  {json.dumps(datos_json, indent=2, ensure_ascii=False)}
 
276
 
277
  ---
278
 
279
+ ### Información del Archivo CSV
280
+
281
+ **Nombre del archivo:** `{csv_filename}`
282
+ **Total de filas:** {len(df)}
283
+ **Formato:** UTF-8 con BOM
284
+
285
+ ---
286
 
287
+ ### Datos Principales Extraídos
288
+
289
+ **Número de factura:** {datos_json.get('numero_factura', 'N/A')}
290
+ **Fecha de emisión:** {datos_json.get('fecha', 'N/A')}
291
+ **Productos/Servicios:** {len(datos_json.get('productos', datos_json.get('conceptos', [])))} items
292
+ **Importe total:** {datos_json.get('totales', {}).get('total', datos_json.get('total', 'N/A'))} EUR
293
  """
294
 
295
+ print(f"--- CSV guardado: {csv_filename}")
296
+ return texto_preview, df, csv_filename, resumen_tecnico, resumen_util
297
 
298
  # ============= INTERFAZ GRADIO =============
299
+ with gr.Blocks(title="Extractor IA de Facturas", css="""
300
+ .gradio-container {padding: 30px !important;}
301
+ .gr-box {border-radius: 8px !important; border: 1px solid #e0e0e0 !important;}
302
+ .gr-form {gap: 20px !important;}
303
+ .separator {height: 2px; background: linear-gradient(90deg, #e0e0e0 0%, #f5f5f5 100%); margin: 30px 0;}
304
+ """) as demo:
305
+
306
+ # Título principal
307
  gr.Markdown("""
308
  # Extractor Inteligente de Facturas
309
  ### Análisis automático de facturas PDF con Inteligencia Artificial
310
  """)
311
 
312
+ gr.HTML('<div class="separator"></div>')
313
+
314
  with gr.Row():
315
+ # COLUMNA IZQUIERDA - INPUT
316
  with gr.Column(scale=1):
317
+ gr.Markdown("### Cargar Documento")
318
  pdf_input = gr.File(
319
  label="Seleccionar factura PDF",
320
  file_types=[".pdf"],
321
  type="filepath"
322
  )
323
+
324
+ gr.Markdown("<br>")
325
+
326
+ btn = gr.Button(
327
+ "Procesar Factura",
328
+ variant="primary",
329
+ size="lg"
330
+ )
331
+
332
+ gr.Markdown("<br>")
333
+
334
+ csv_output = gr.File(label="Descargar archivo CSV generado")
335
 
336
+ # COLUMNA DERECHA - RESULTADOS
337
  with gr.Column(scale=2):
338
+ gr.Markdown("### Resultados del Análisis")
339
+
340
+ gr.Markdown("<br>")
341
+
342
+ # Información útil destacada
343
+ with gr.Group():
344
+ gr.Markdown("#### Información Útil para Administrativos")
345
+ info_util = gr.Markdown(
346
+ label="Resumen ejecutivo",
347
+ value="Aquí aparecerá información relevante una vez procesada la factura"
348
+ )
349
 
350
+ gr.HTML('<div class="separator"></div>')
351
+
352
+ # Tabs para información detallada
353
  with gr.Tabs():
354
  with gr.Tab("Vista Previa CSV"):
355
+ gr.Markdown("<br>")
356
  tabla_preview = gr.DataFrame(
357
+ label="Datos extraídos estructurados",
358
  wrap=True,
359
+ interactive=False,
360
+ height=400
361
  )
362
 
363
  with gr.Tab("Texto Original"):
364
+ gr.Markdown("<br>")
365
  texto_extraido = gr.Textbox(
366
  label="Texto extraído del PDF",
367
+ lines=18,
368
+ max_lines=25,
369
+ show_copy_button=True
370
  )
371
 
372
+ with gr.Tab("Análisis Técnico"):
373
+ gr.Markdown("<br>")
374
+ resumen_tecnico = gr.Markdown(label="Estructura de datos y metadatos")
375
+
376
+ gr.HTML('<div class="separator"></div>')
377
+
378
+ # Footer con información
379
+ gr.Markdown("""
380
+ <div style='text-align: center; color: #666; font-size: 0.9em; padding: 20px;'>
381
+ <p>Sistema de extracción automática de datos mediante modelos de lenguaje</p>
382
+ <p style='font-size: 0.85em; margin-top: 10px;'>Configuración requerida: HF_TOKEN en Settings → Secrets</p>
383
+ </div>
384
+ """)
385
 
386
+ # Conectar botón con función
387
  btn.click(
388
  fn=procesar_factura,
389
  inputs=[pdf_input],
390
+ outputs=[texto_extraido, tabla_preview, csv_output, resumen_tecnico, info_util]
391
  )
392
 
393
  if __name__ == "__main__":