import os
import gradio as gr
import pandas as pd
import database as db
import ai_processor as ai
import exportar as exp
db.init_db()
CSS = """
.emergency-header { background:#b91c1c !important; color:white !important; padding:16px; border-radius:8px; text-align:center; }
.info-card { background:#fffbeb !important; color:#111827 !important; border-left:4px solid #f59e0b !important; padding:12px; border-radius:4px; margin:8px 0; }
.aviso-ia { background:#f0f9ff !important; color:#111827 !important; border-left:4px solid #0ea5e9 !important; padding:10px; border-radius:4px; margin:8px 0; font-size:0.9em; }
.paso { background:#f8fafc !important; color:#111827 !important; border:1px solid #e2e8f0 !important; border-left:4px solid #2563eb !important; padding:10px 14px; border-radius:6px; margin:6px 0; }
.dup-exacto { background:#fef2f2 !important; color:#111827 !important; border:2px solid #dc2626 !important; border-radius:8px; padding:14px; }
.dup-prob { background:#fff7ed !important; color:#111827 !important; border:2px solid #ea580c !important; border-radius:8px; padding:14px; }
footer { display:none !important; }
"""
HOSPITALES = [
"", "Hospital Universitario de Caracas", "Hospital Vargas",
"Hospital Domingo Luciani (El Llanito)", "Hospital Pérez Carreño",
"Hospital Miguel Pérez Carreño", "Periférico de Catia", "Cruz Roja",
"Centro Médico de Caracas", "Hospital José Gregorio Hernández",
"Hospital Dr. Jesús Yerena (Lidice)", "Clínica El Ávila",
"Hospital Militar Dr. Carlos Arvelo",
"Hospital de Niños J.M. de los Ríos", "Otro",
]
CONDICIONES = ["Sin información", "Estable", "Grave", "Crítico",
"En observación", "En cirugía", "Fallecido"]
COLS_CARGA = ["Nombre", "Cédula", "Edad", "Condición", "Notas"]
# ─── Helpers ──────────────────────────────────────────────────────────────────
def _contador_md() -> str:
return f"📊 **{db.total_registros()} personas registradas**"
def _rows_a_df(rows: list[dict]) -> pd.DataFrame:
if not rows:
return pd.DataFrame(columns=["Nombre", "Cédula", "Edad", "Hospital",
"Condición", "Última actualización", "Verificado"])
df = pd.DataFrame(rows)
df["Verificado"] = df["verificado"].apply(lambda x: "✅" if x else "👤")
return df[["nombre", "cedula", "edad", "hospital", "condicion",
"fecha_update", "Verificado"]].rename(columns={
"nombre": "Nombre", "cedula": "Cédula", "edad": "Edad",
"hospital": "Hospital", "condicion": "Condición",
"fecha_update": "Última actualización",
})
def _generar_cards(rows: list[dict]) -> str:
if not rows:
return ""
html = '
'
for r in rows:
nombre = r.get("nombre") or "Desconocido"
ced = r.get("cedula_norm") or r.get("cedula") or "No indicada"
if r.get("cedula_norm"):
ced = db.formatear_cedula_display(r.get("cedula_norm"))
edad = f"{r.get('edad')} años" if r.get('edad') else "--"
hospital = r.get("hospital") or "No indicado"
cond = r.get("condicion") or "Sin información"
notas = r.get("notas")
html_notas = f'
📝 Notas: {notas}
' if notas and str(notas).strip() else ""
color_cond = "#6b7280"
if "Crítico" in cond: color_cond = "#dc2626"
elif "Grave" in cond: color_cond = "#ea580c"
elif "Estable" in cond: color_cond = "#16a34a"
elif "Fallecido" in cond: color_cond = "#000000"
verif = "✅ Verif." if r.get("verificado") else "👤 Reporte civil"
fecha = r.get('fecha_update', '')[:10]
html += f'''
{nombre}
{cond}
🪪 C.I: {ced}
🎂 Edad: {edad}
🏥 Hospital: {hospital}
{html_notas}
{verif}
Act: {fecha}
'''
html += "
"
return html
def _df_carga_vacio() -> pd.DataFrame:
return pd.DataFrame(columns=COLS_CARGA)
def _personas_a_df_carga(personas: list[dict]) -> pd.DataFrame:
if not personas:
return _df_carga_vacio()
filas = [{
"Nombre": p.get("nombre") or "",
"Cédula": p.get("cedula") or "",
"Edad": p.get("edad") or "",
"Condición": p.get("condicion") or "Sin información",
"Notas": p.get("notas") or "",
} for p in personas]
return pd.DataFrame(filas, columns=COLS_CARGA)
def _df_carga_a_personas(tabla) -> list[dict]:
"""Convierte la tabla editable (DataFrame o dict de Gradio) a lista de personas."""
if tabla is None:
return []
if isinstance(tabla, pd.DataFrame):
registros = tabla.to_dict(orient="records")
elif isinstance(tabla, dict) and "data" in tabla:
headers = tabla.get("headers", COLS_CARGA)
registros = [dict(zip(headers, fila)) for fila in tabla["data"]]
elif isinstance(tabla, list):
registros = [dict(zip(COLS_CARGA, fila)) for fila in tabla]
else:
return []
personas = []
for r in registros:
personas.append({
"nombre": str(r.get("Nombre", "") or "").strip(),
"cedula": str(r.get("Cédula", "") or "").strip(),
"edad": r.get("Edad"),
"condicion": str(r.get("Condición", "") or "Sin información").strip(),
"notas": str(r.get("Notas", "") or "").strip() or None,
})
return personas
def _formato_analisis(res: dict) -> str:
if not res:
return "Sin resultado."
if not res.get("procesable", True):
return (
f"⚠️ **La IA no pudo procesar esta imagen.**\n\n"
f"**Razón:** {res.get('error', res.get('observaciones', 'No especificada'))}\n\n"
f"*Registra la persona manualmente en «Agregar Persona».*"
)
lines = [f"### Tipo de imagen\n{res.get('tipo_imagen', 'No determinado')}\n"]
def campo(label, valor):
v = valor if valor not in (None, "", "null", "None") else "No detectado / No legible"
lines.append(f"**{label}:** {v}")
campo("Nombre", res.get("nombre_detectado"))
ced_raw = res.get("cedula_detectada")
if ced_raw:
ced_fmt = db.formatear_cedula_display(db.normalizar_cedula(str(ced_raw)))
campo("Cédula", ced_fmt or ced_raw)
else:
campo("Cédula", None)
campo("Edad estimada", res.get("edad_estimada"))
campo("Descripción física", res.get("descripcion_fisica"))
campo("Texto visible", res.get("texto_visible") or "Ninguno legible")
campo("Condición aparente", res.get("condicion_aparente"))
if res.get("personas_multiples"):
lines.append(f"\n**👥 Se detectaron {len(res['personas_multiples'])} personas en una lista.** "
"Usa la pestaña «📋 Cargar Lista» para guardarlas todas de una vez.")
if res.get("observaciones"):
lines.append(f"\n**Observaciones:** {res['observaciones']}")
lines.append(f"\n---\n*{res.get('resumen', '')}*")
lines.append(
"\n> ℹ️ Solo se reporta lo que la IA observa con certeza. "
"Campos «No detectado» deben completarse manualmente."
)
return "\n".join(lines)
def _html_duplicados(dups: list[dict]) -> str:
if not dups:
return ""
filas = ""
for d in dups:
nivel = d.get("nivel_alerta", "POSIBLE")
cls = "dup-exacto" if nivel == "EXACTO" else "dup-prob"
ico = "🚨" if nivel == "EXACTO" else "⚠️"
filas += f"""
{ico} Duplicado {nivel} encontrado — ID #{d['id']}
Nombre: {d.get('nombre') or '—'} |
Cédula: {d.get('cedula') or '—'} |
Edad: {d.get('edad') or '—'}
Hospital: {d.get('hospital') or '—'} |
Condición: {d.get('condicion') or '—'}
Registrado: {d.get('fecha_ingreso') or '—'} |
Fuente: {d.get('fuente') or '—'}
"""
return f"""
⛔ ¡POSIBLE DUPLICADO DETECTADO!
Antes de registrar, revisa si esta persona ya está en la base de datos:
{filas}
Si es la misma persona, NO registres de nuevo.
Solo confirma si estás seguro de que es un registro distinto.
"""
# ─── Buscar / exportar ─────────────────────────────────────────────────────────
def buscar_familiar(nombre, cedula, edad_min, edad_max, hospital):
rows = db.buscar(
nombre=nombre or "", cedula=cedula or "",
edad_min=int(edad_min) if edad_min else None,
edad_max=int(edad_max) if edad_max else None,
hospital=hospital or "",
)
df = _rows_a_df(rows)
html_cards = _generar_cards(rows)
n = len(rows)
msg = ("⚠️ No se encontraron registros. Intenta con menos filtros." if n == 0
else f"✅ **{n} persona(s) encontrada(s).** Confirma siempre con el hospital.")
return html_cards, msg, rows
def exportar_excel_fn(rows):
return exp.exportar_excel(rows) if rows else None
def exportar_pdf_fn(rows):
return exp.exportar_pdf(rows) if rows else None
def normalizar_cedula_ui(cedula_raw: str) -> str:
n = db.normalizar_cedula(cedula_raw or "")
return db.formatear_cedula_display(n) if n else ""
# ─── Agregar una persona (con verificación de duplicados) ──────────────────────
def verificar_antes_de_agregar(nombre, cedula, edad, hospital, hospital_manual,
condicion, notas, contacto, imagen, fuente):
hospital_final = (hospital_manual.strip()
if hospital == "Otro" and hospital_manual.strip() else hospital)
if not (nombre or "").strip() and not (cedula or "").strip():
return ("❌ Debes ingresar al menos **nombre** o **cédula**.",
gr.update(visible=False), gr.update(visible=False), "", {}, _contador_md())
cedula_norm = db.normalizar_cedula(cedula or "")
cedula_fmt = db.formatear_cedula_display(cedula_norm) if cedula_norm else (cedula or "")
descripcion_ia = ""
imagen_path = imagen
if imagen is not None:
res = ai.analizar_imagen_hospital(imagen)
if res.get("procesable", True) and "error" not in res:
descripcion_ia = res.get("resumen", "")
if not (nombre or "").strip() and res.get("nombre_detectado"):
nombre = res["nombre_detectado"]
if not cedula_norm and res.get("cedula_detectada"):
cedula_fmt = db.formatear_cedula_display(
db.normalizar_cedula(str(res["cedula_detectada"]))
) or str(res["cedula_detectada"])
cedula_norm = db.normalizar_cedula(str(res["cedula_detectada"]))
if not edad and res.get("edad_estimada"):
try:
edad = int(res["edad_estimada"])
except (TypeError, ValueError):
pass
else:
descripcion_ia = f"[IA no procesó la imagen: {res.get('error', '')}]"
pendiente = dict(
nombre=nombre, cedula=cedula_fmt, cedula_norm=cedula_norm,
edad=edad, hospital=hospital_final, condicion=condicion,
descripcion=descripcion_ia, imagen_path=imagen_path,
contacto=contacto, notas=notas, fuente=fuente
)
dups = db.buscar_duplicados(nombre or "", cedula_norm, edad)
if dups:
html_aviso = _html_duplicados(dups)
return (
f"⚠️ Se detectaron **{len(dups)} posible(s) duplicado(s)**. "
"Revisa abajo y confirma solo si es un registro nuevo.",
gr.update(visible=True), gr.update(visible=True), html_aviso,
pendiente, _contador_md()
)
new_id = _registrar(pendiente)
return (
f"✅ Persona registrada con ID **#{new_id}** sin duplicados detectados. ¡Gracias!",
gr.update(visible=False), gr.update(visible=False), "", {}, _contador_md()
)
def confirmar_registro(pendiente):
if not pendiente:
return ("❌ No hay datos pendientes.", gr.update(visible=False),
gr.update(visible=False), "", {}, _contador_md())
new_id = _registrar(pendiente)
return (
f"✅ Persona registrada con ID **#{new_id}** (confirmado manualmente).",
gr.update(visible=False), gr.update(visible=False), "", {}, _contador_md()
)
def cancelar_registro():
return ("❌ Registro cancelado. Verifica los datos e intenta con más información.",
gr.update(visible=False), gr.update(visible=False), "", {}, _contador_md())
def _registrar(datos: dict) -> int:
new_id = db.agregar_persona(
nombre=datos.get("nombre"), cedula=datos.get("cedula"),
edad=datos.get("edad"), hospital=datos.get("hospital"),
condicion=datos.get("condicion"), descripcion=datos.get("descripcion"),
imagen_path=datos.get("imagen_path"), contacto=datos.get("contacto"),
notas=datos.get("notas"), fuente=datos.get("fuente", "ciudadano"),
)
if datos.get("imagen_path"):
try:
db.guardar_imagen(datos["imagen_path"], new_id)
except Exception:
pass
return new_id
# ─── Cargar LISTA (varias personas de una imagen / PDF / Excel) ────────────────
def extraer_lista_fn(archivo):
if not archivo:
return _df_carga_vacio(), "⚠️ Sube primero una imagen, PDF o Excel con la lista."
res = ai.extraer_lista(archivo)
if not res.get("procesable"):
return _df_carga_vacio(), (
f"⚠️ **No se pudo extraer la lista.**\n\n{res.get('error', '')}\n\n"
"*Puedes escribir las personas manualmente en la tabla de abajo y guardarlas.*"
)
personas = res.get("personas", [])
if not personas:
return _df_carga_vacio(), ("⚠️ No se detectaron personas en el archivo. "
"Revisa que la imagen sea legible.")
df = _personas_a_df_carga(personas)
msg = (f"✅ Se extrajeron **{len(personas)} personas**. "
"**Revisa y corrige** la tabla antes de guardar — la IA nunca inventa, "
"pero conviene verificar nombres y cédulas. Luego pulsa «Guardar todos».")
return df, msg
def guardar_lote_fn(tabla, hospital, hospital_manual, fuente):
personas = _df_carga_a_personas(tabla)
if not personas:
return ("⚠️ La tabla está vacía. Extrae una lista o escribe al menos una persona.",
_contador_md())
hospital_final = (hospital_manual.strip()
if hospital == "Otro" and (hospital_manual or "").strip() else hospital)
resumen = db.agregar_personas_bulk(
personas, hospital_default=hospital_final or None,
fuente=fuente or "registro hospital", saltar_duplicados=True
)
lineas = [f"✅ **{resumen['agregados']} personas agregadas** a la base de datos."]
if resumen["omitidos"]:
ejemplos = ", ".join(resumen["nombres_omitidos"][:8])
extra = "…" if len(resumen["nombres_omitidos"]) > 8 else ""
lineas.append(f"↩️ **{resumen['omitidos']} omitidas** por estar ya registradas "
f"(misma cédula): {ejemplos}{extra}")
if hospital_final:
lineas.append(f"🏥 Asignadas al hospital: **{hospital_final}**")
lineas.append("\n> Ve a «🔍 Buscar Familiar» para verificarlas.")
return "\n\n".join(lineas), _contador_md()
# ─── Buscar por foto ───────────────────────────────────────────────────────────
def buscar_por_imagen(imagen):
if imagen is None:
return pd.DataFrame(), "⚠️ Por favor sube una imagen primero."
res = ai.describir_imagen_busqueda(imagen)
if not res.get("procesable", True):
return pd.DataFrame(), (
f"⚠️ **La IA no pudo procesar esta imagen.**\n\n"
f"{res.get('error', 'Razón desconocida')}\n\n"
"*Prueba buscar manualmente con nombre o cédula.*"
)
descripcion = res.get("descripcion_busqueda") or "No determinada"
edad_est = res.get("edad_estimada")
nombre_vis = res.get("nombre_visible")
cedula_vis = res.get("cedula_visible")
advertencia = res.get("advertencia") or ""
resultados = []
if cedula_vis:
resultados = db.buscar(cedula=str(cedula_vis))
if not resultados and nombre_vis:
resultados = db.buscar(nombre=nombre_vis)
if not resultados and edad_est:
try:
e = int(edad_est)
resultados = db.buscar(edad_min=max(0, e - 10), edad_max=e + 10)
except (TypeError, ValueError):
pass
df = _rows_a_df(resultados)
msg = [f"**La IA describe a la persona:**\n\n_{descripcion}_",
f"**Características:** {res.get('caracteristicas_clave') or 'No determinadas'}"]
if advertencia:
msg.append(f"⚠️ **Aviso:** {advertencia}")
msg.append(f"**{len(resultados)} coincidencia(s)** encontrada(s)." if resultados
else "Sin coincidencias automáticas — busca manualmente con nombre o cédula.")
msg.append("\n> ℹ️ La búsqueda por imagen es orientativa. Siempre verifica en el hospital.")
return df, "\n\n".join(msg)
def analizar_imagen(imagen):
if imagen is None:
return "⚠️ Sube una imagen para analizar.", {}
res = ai.analizar_imagen_hospital(imagen)
return _formato_analisis(res), res
def mostrar_hospital_manual(h):
return gr.Textbox(visible=(h == "Otro"))
# ─── Interfaz ─────────────────────────────────────────────────────────────────
with gr.Blocks(title="Buscador Familiar – Terremoto Venezuela", css=CSS,
theme=gr.themes.Soft()) as demo:
estado_busqueda = gr.State([])
estado_pendiente = gr.State({})
gr.HTML("""
""")
gr.HTML("""
📞 Emergencias:
Bomberos: 171 |
Cruz Roja: 0212-781-3557 |
Protección Civil: 0800-776-8352 |
CICPC: 0800-248-1248
""")
contador = gr.Markdown(_contador_md())
gr.Markdown("---")
with gr.Tabs():
# ── BUSCAR ────────────────────────────────────────────────────────────
with gr.Tab("🔍 Buscar Familiar"):
gr.Markdown("### Ingresa uno o varios criterios")
with gr.Row():
inp_nombre = gr.Textbox(label="Nombre (o parte)", placeholder="Ej: María González")
inp_cedula = gr.Textbox(label="Cédula", placeholder="Ej: V-12345678 ó 12345678")
with gr.Row():
inp_emin = gr.Number(label="Edad mínima", minimum=0, maximum=120, value=None, scale=1)
inp_emax = gr.Number(label="Edad máxima", minimum=0, maximum=120, value=None, scale=1)
inp_hosp = gr.Textbox(label="Hospital", placeholder="Ej: Hospital Vargas", scale=2)
btn_buscar = gr.Button("🔍 Buscar", variant="primary", size="lg")
msg_buscar = gr.Markdown()
tabla = gr.HTML()
with gr.Row():
btn_excel = gr.DownloadButton("📥 Excel", variant="secondary", size="sm")
btn_pdf = gr.DownloadButton("📄 PDF", variant="secondary", size="sm")
btn_buscar.click(buscar_familiar,
[inp_nombre, inp_cedula, inp_emin, inp_emax, inp_hosp],
[tabla, msg_buscar, estado_busqueda])
btn_excel.click(exportar_excel_fn, inputs=[estado_busqueda], outputs=[btn_excel])
btn_pdf.click(exportar_pdf_fn, inputs=[estado_busqueda], outputs=[btn_pdf])
gr.Markdown("> ✅ Verificado por personal médico | 👤 Reportado por ciudadano")
# ── CARGAR LISTA (varias personas) ────────────────────────────────────
with gr.Tab("📋 Cargar Lista (varias personas)"):
gr.Markdown("### Sube una imagen, PDF o Excel con una LISTA de personas")
gr.HTML("""
Paso 1. Sube la imagen/lista (ej: captura de WhatsApp de un hospital con nombres, edades y cédulas).
Paso 2. Pulsa «Extraer con IA». La IA ordena los datos en la tabla — nunca inventa.
Paso 3. Revisa y corrige la tabla, elige el hospital, y pulsa «Guardar todos».
""")
with gr.Row():
with gr.Column(scale=1):
carga_file = gr.File(
label="📎 Imagen / PDF / Excel con la lista",
file_types=[".jpg", ".jpeg", ".png", ".webp", ".gif",
".pdf", ".xlsx", ".xls", ".csv"],
type="filepath")
carga_hosp = gr.Dropdown(choices=HOSPITALES, value="",
label="🏥 Hospital para TODA la lista")
carga_hosp_m = gr.Textbox(label="Nombre del hospital (si elegiste «Otro»)",
visible=False, placeholder="Nombre exacto")
carga_fuente = gr.Radio(
choices=["registro hospital", "personal médico", "voluntario",
"lista hospital", "ciudadano"],
value="registro hospital", label="¿De dónde viene la lista?")
btn_extraer = gr.Button("🤖 Extraer con IA", variant="primary", size="lg")
with gr.Column(scale=2):
msg_carga = gr.Markdown("Sube un archivo y pulsa «Extraer con IA».")
tabla_carga = gr.Dataframe(
headers=COLS_CARGA,
datatype=["str", "str", "str", "str", "str"],
col_count=(5, "fixed"),
row_count=(1, "dynamic"),
interactive=True,
wrap=True,
label="Revisa y corrige antes de guardar")
btn_guardar_lote = gr.Button("💾 Guardar todos en la base de datos",
variant="primary", size="lg")
msg_lote = gr.Markdown()
carga_hosp.change(mostrar_hospital_manual, carga_hosp, carga_hosp_m)
btn_extraer.click(extraer_lista_fn, [carga_file], [tabla_carga, msg_carga])
btn_guardar_lote.click(
guardar_lote_fn,
[tabla_carga, carga_hosp, carga_hosp_m, carga_fuente],
[msg_lote, contador])
gr.HTML("""
🔒 Datos sensibles: esta lista puede contener menores de edad. Úsala solo para
reunificación familiar. Los duplicados (misma cédula) se omiten automáticamente.
""")
# ── AGREGAR UNA ───────────────────────────────────────────────────────
with gr.Tab("➕ Agregar Persona"):
gr.Markdown("""
### Registra a UNA persona ubicada en un hospital
Para listas de varias personas, usa la pestaña «📋 Cargar Lista».
""")
gr.HTML("""
🔍 Detección de duplicados activa: Antes de guardar se verifica si la persona
ya existe. Si hay coincidencia, se mostrará un aviso para que confirmes.
La cédula se normaliza automáticamente al formato venezolano.
""")
with gr.Row():
with gr.Column(scale=1):
img_agregar = gr.Image(label="📷 Imagen (cualquier tipo)", type="filepath",
sources=["upload", "clipboard"])
fuente = gr.Radio(
choices=["ciudadano", "personal médico", "voluntario", "familiar"],
value="ciudadano", label="¿Quién reporta?")
with gr.Column(scale=2):
with gr.Row():
a_nombre = gr.Textbox(label="Nombre completo *", placeholder="Nombre y apellido")
a_cedula = gr.Textbox(label="Cédula *", placeholder="V-12345678 / E-12345678 / solo números")
with gr.Row():
a_edad = gr.Number(label="Edad", minimum=0, maximum=120, value=None)
a_cond = gr.Dropdown(choices=CONDICIONES, value="Sin información", label="Condición")
a_hosp = gr.Dropdown(choices=HOSPITALES, value="", label="Hospital / Centro")
a_hosp_m = gr.Textbox(label="Nombre del hospital (si elegiste «Otro»)",
visible=False, placeholder="Nombre exacto")
a_contacto = gr.Textbox(label="Teléfono familiar (opcional)", placeholder="04XX-XXXXXXX")
a_notas = gr.Textbox(label="Notas adicionales", lines=2,
placeholder="Sala, cama, señas particulares...")
a_cedula.blur(normalizar_cedula_ui, inputs=[a_cedula], outputs=[a_cedula])
btn_agregar = gr.Button("✅ Verificar y Registrar", variant="primary", size="lg")
msg_agregar = gr.Markdown()
html_dups = gr.HTML("")
with gr.Row():
btn_confirmar = gr.Button("⚠️ Confirmar registro (soy consciente del duplicado)",
variant="stop", visible=False)
btn_cancelar = gr.Button("❌ Cancelar", variant="secondary", visible=False)
a_hosp.change(mostrar_hospital_manual, a_hosp, a_hosp_m)
btn_agregar.click(
verificar_antes_de_agregar,
inputs=[a_nombre, a_cedula, a_edad, a_hosp, a_hosp_m,
a_cond, a_notas, a_contacto, img_agregar, fuente],
outputs=[msg_agregar, btn_confirmar, btn_cancelar, html_dups,
estado_pendiente, contador]
)
btn_confirmar.click(confirmar_registro,
inputs=[estado_pendiente],
outputs=[msg_agregar, btn_confirmar, btn_cancelar, html_dups,
estado_pendiente, contador])
btn_cancelar.click(cancelar_registro,
outputs=[msg_agregar, btn_confirmar, btn_cancelar, html_dups,
estado_pendiente, contador])
# ── BUSCAR POR FOTO ───────────────────────────────────────────────────
with gr.Tab("📸 Buscar por Foto"):
gr.Markdown("""
### Sube una foto de tu familiar para buscar coincidencias
Acepta foto, imagen de cédula, captura de pantalla, etc.
**Si la IA no puede procesar la imagen, te lo dirá — nunca inventará datos.**
""")
img_busq = gr.Image(label="Imagen de tu familiar", type="filepath",
sources=["upload", "clipboard"])
btn_bimg = gr.Button("🔍 Buscar", variant="primary")
msg_bimg = gr.Markdown()
tabla_bimg = gr.Dataframe(interactive=False, wrap=True)
btn_bimg.click(buscar_por_imagen, [img_busq], [tabla_bimg, msg_bimg])
# ── ANALIZAR IMAGEN ───────────────────────────────────────────────────
with gr.Tab("🤖 Analizar Imagen con IA"):
gr.Markdown("### Extrae información de una imagen para revisarla antes de registrar")
gr.HTML("""
⚠️ Sin sesgo: Si la IA no puede leer algo con certeza, lo dirá explícitamente.
No supondrá ni inventará nombres, cédulas ni datos.
""")
img_anal = gr.Image(label="Imagen a analizar", type="filepath",
sources=["upload", "clipboard"])
btn_anal = gr.Button("🔍 Analizar", variant="secondary")
txt_anal = gr.Markdown()
json_anal = gr.JSON(label="Datos JSON", visible=False)
btn_anal.click(analizar_imagen, [img_anal], [txt_anal, json_anal])
# ── INFORMACIÓN ───────────────────────────────────────────────────────
with gr.Tab("ℹ️ Información"):
gr.Markdown("## 📥 Descarga Completa de Datos\nPuedes descargar toda la base de datos (sin límites) en formato Excel para realizar respaldos o compartirla.")
btn_descargar_todo = gr.DownloadButton("📥 Descargar Base de Datos Completa (Excel)", variant="primary")
def exportar_todo_fn():
return exp.exportar_excel(db.obtener_todos_para_exportar())
btn_descargar_todo.click(exportar_todo_fn, None, btn_descargar_todo)
gr.Markdown("""
---
## ¿Cómo usar esta plataforma?
### 🔍 Buscar familiar
- Ingresa nombre, cédula (con o sin V-) o rango de edad
- La búsqueda acepta cédulas con puntos, guiones o sin ellos
- Descarga los resultados en **Excel** o **PDF** para imprimir o compartir
### 📋 Cargar Lista (varias personas)
- Sube una **imagen, PDF o Excel** con una lista de pacientes
- La IA **ordena** los datos (nombre, cédula, edad) en una tabla editable
- Revisa, corrige y **guarda todas de una vez** — los duplicados se omiten solos
### ➕ Agregar persona
- Para registrar a **una** persona con detalle (foto, condición, contacto)
- Antes de guardar, el sistema verifica duplicados
### 📸 Buscar por foto
- Sube la imagen de tu familiar; la IA lo describe y busca coincidencias
---
## 📞 Líneas de emergencia
| Servicio | Número |
|---|---|
| Bomberos | 171 |
| Cruz Roja Venezolana | 0212-781-3557 |
| Protección Civil | 0800-PROTEJA (776-8352) |
| CICPC | 0800-248-1248 |
| Defensa Civil | 0500-DEFENSA |
---
## ⚠️ Avisos
- La IA **solo reporta lo que puede observar con certeza** — nunca inventa datos
- Esta es una plataforma ciudadana, **no oficial**
- Confirma siempre la información directamente con el hospital
- Los datos pueden incluir **menores de edad**: úsalos solo para reunificación familiar
---
*Hecha con ❤️ para Venezuela en tiempos de emergencia.*
""")
gr.HTML("""
Plataforma ciudadana de emergencia · Datos para facilitar la búsqueda de familiares
""")
# ── API OCULTA PARA DESARROLLADORES ───────────────────────────────────
btn_api = gr.Button(visible=False)
json_api = gr.JSON(visible=False)
def api_get_personas():
rows = db.obtener_todos_para_exportar()
return {"status": "success", "total": len(rows), "data": rows}
btn_api.click(api_get_personas, inputs=[], outputs=[json_api], api_name="api_personas")
demo.load(_contador_md, outputs=[contador])
if __name__ == "__main__":
demo.queue(max_size=50).launch(server_name="0.0.0.0", server_port=7860)