import numpy as np
import pandas as pd
class CalculadoraMetrado:
def __init__(self, in_elevado, in_soporte, in_cisterna, in_materiales):
# 1. Asignación de Inputs Clasificados
self.Elev = in_elevado # Datos Tanque Elevado
self.Sop = in_soporte # Datos Soporte (Vigas/Col)
self.Cis = in_cisterna # Datos Cisterna
self.Mat = in_materiales # Materiales Generales
# 2. Geometría Derivada (Calculada una sola vez)
# Ejes y dimensiones exteriores basados en el Tanque Elevado
self.Leje = self.Elev['L_luz'] + 0.50
self.L_Cis_X = self.Leje + 0.50
self.L_Cis_Y = self.Leje + 0.50
def Ejecutar(self):
"""
Realiza el metrado separando Cargas Vivas (CV) y Muertas (CM).
"""
# ==========================================
# 1. CARGAS VIVAS (CV) - PESO DEL AGUA
# ==========================================
# A. Agua Tanque Elevado
# Volumen = Area_Luz * Altura_Agua
P_AguaE = (self.Elev['L_luz']**2 * self.Elev['HL_agua']) * self.Mat['GammaW']
# B. Agua Cisterna
# Volumen = (Largo_Ext - 2*Muros) * Altura_Ent * Ancho...
L_int_cis = self.L_Cis_X - 2*self.Cis['Tw_muro']
P_AguaC = (L_int_cis**2 * self.Cis['H_ent']) * self.Mat['GammaW']
Total_CV = P_AguaE + P_AguaC
# ==========================================
# 2. CARGAS MUERTAS (CM) - CONCRETO
# ==========================================
# A. Tanque Elevado (Recipiente)
# Paredes (4 lados) + Losa Fondo + Losa Techo
Vol_Paredes_E = (self.Elev['L_luz'] * 4) * self.Elev['H_cajon'] * self.Elev['Tw_par']
Vol_Losas_E = (self.Elev['L_luz']**2) * (self.Elev['Tf_fondo'] + self.Elev['Tr_techo'])
P_ConcE = (Vol_Paredes_E + Vol_Losas_E) * self.Mat['GammaC']
# B. Soporte (Columnas y Vigas)
# Altura total de columnas = Altura_piso * N_pisos
H_Total_Cols = self.Sop['H_entrepiso'] * self.Sop['N_pisos']
# Volumen Columnas = Area_Seccion * H_Total * Num_Cols
Vol_Cols = self.Sop['Area_Col_Unit'] * H_Total_Cols * self.Sop['Num_Cols']
# Volumen Vigas = (Seccion_Viga * Longitud_Total) * N_pisos
Longitud_Vigas_Piso = self.Leje * 4 # Perímetro a ejes
Vol_Vigas = (self.Sop['Viga_b'] * self.Sop['Viga_h']) * Longitud_Vigas_Piso * self.Sop['N_pisos']
P_Soporte = (Vol_Cols + Vol_Vigas) * self.Mat['GammaC']
# C. Cisterna (Casco Estructural)
# Muros Perimetrales + Losa Techo (La zapata se calcula en el paso de Geotecnia)
Vol_Muros_C = (self.L_Cis_X * 4) * self.Cis['H_ent'] * self.Cis['Tw_muro']
Vol_Techo_C = (self.L_Cis_X**2) * self.Cis['Tr_techo']
P_ConcC = (Vol_Muros_C + Vol_Techo_C) * self.Mat['GammaC']
# Total Carga Muerta
Total_CM = P_ConcE + P_Soporte + P_ConcC
# Carga Total de Servicio
P_Servicio = Total_CM + Total_CV
# ==========================================
# 3. EMPAQUETADO DE DATOS (OUTPUT)
# ==========================================
# DataFrame para visualización
data = {
"ID": ["AGUA_ELEV", "AGUA_CIS", "CONC_ELEV", "SOPORTE", "CONC_CIS", "TOTAL_CM", "TOTAL_CV", "TOTAL_SERV"],
"Descripcion": [
"Agua Tanque Elevado",
"Agua Cisterna",
"Concreto Tanque Elevado",
"Soporte (Columnas + Vigas)",
"Casco Cisterna (Muros+Techo)",
"TOTAL CARGA MUERTA",
"TOTAL CARGA VIVA",
"TOTAL SERVICIO"
],
"Valor": [P_AguaE, P_AguaC, P_ConcE, P_Soporte, P_ConcC, Total_CM, Total_CV, P_Servicio],
"Tipo": ["CV", "CV", "CM", "CM", "CM", "RESUMEN", "RESUMEN", "RESUMEN"]
}
df = pd.DataFrame(data)
# Retorno estructurado para R
return {
"tabla": df,
"parametros": {
"Total_CM": Total_CM,
"Total_CV": Total_CV,
"P_Servicio": P_Servicio,
"L_Cis_X": self.L_Cis_X,
"H_Zapata": self.Cis['H_zap']
}
}
# --- DEFINICIÓN CLARA DE INPUTS ---
# 1. Tanque Elevado
Input_Elevado = {
'L_luz': 2.35, # Luz interior
'HL_agua': 2.50, # Altura de agua
'H_cajon': 2.50, # Altura muros (HL + Borde Libre implícito o explícito)
'Tw_par': 0.20, # Espesor paredes
'Tf_fondo': 0.20, # Espesor losa fondo
'Tr_techo': 0.15 # Espesor losa techo
}
# 2. Soporte (Vigas y Columnas)
Input_Soporte = {
'H_entrepiso': 2.60,
'N_pisos': 4,
'Num_Cols': 4,
# Área de Columna L (0.50x0.50x0.25) -> (0.50*0.25) + (0.25*0.25) = 0.1875 m2
'Area_Col_Unit': 0.1875,
'Viga_b': 0.25,
'Viga_h': 0.40
}
# 3. Cisterna
Input_Cisterna = {
'H_ent': 3.00, # Altura enterramiento
'H_zap': 0.50, # Altura zapata (para geotecnia posterior)
'Tw_muro': 0.25, # Espesor muros
'Tr_techo': 0.15 # Espesor techo
}
# 4. Materiales
Input_Materiales = {
'GammaC': 2.4, # Concreto Armado
'GammaW': 1.0 # Agua
}
# --- EJECUCIÓN ---
metrado = CalculadoraMetrado(Input_Elevado, Input_Soporte, Input_Cisterna, Input_Materiales)
resultados_py = metrado.Ejecutar()Zapata De Tanque Elevado
OBRA IE 344 INICIAL - AYACUCHO
library(reticulate)
library(flextable)
library(dplyr)
# 1. Extracción Segura de Datos (Clean Source)
# Convertimos el DF de Python a una lista pura antes de traerla a R
py_run_string("export_metrado = resultados_py['tabla'].to_dict(orient='list')")
raw_data <- py$export_metrado
# 2. Reconstrucción del DataFrame en R
df_reporte <- data.frame(
Descripcion = unlist(raw_data$Descripcion),
Valor = as.numeric(unlist(raw_data$Valor)),
Tipo = unlist(raw_data$Tipo),
stringsAsFactors = FALSE
)
# 3. Renderizado de Tabla Profesional
df_reporte %>%
select(Descripcion, Valor, Tipo) %>%
flextable() %>%
# Encabezados
set_caption("Tabla 1: Metrado de Cargas por Componente") %>%
set_header_labels(
Descripcion = "Elemento Estructural",
Valor = "Carga (Ton)",
Tipo = "Clasif."
) %>%
# Formatos
colformat_double(j = "Valor", digits = 2) %>%
theme_box() %>%
autofit() %>%
# Estilos:
# - Filas de Resumen (Totales) en amarillo
bg(i = ~ Tipo == "RESUMEN", bg = "#FFF2CC") %>%
bold(i = ~ Tipo == "RESUMEN") %>%
# - Cargas Vivas en azul muy claro (opcional, para diferenciar)
bg(i = ~ Tipo == "CV", bg = "#F0F8FF") %>%
align(j = c("Valor", "Tipo"), align = "center", part = "all")Elemento Estructural | Carga (Ton) | Clasif. |
|---|---|---|
Agua Tanque Elevado | 13.81 | CV |
Agua Cisterna | 24.37 | CV |
Concreto Tanque Elevado | 15.92 | CM |
Soporte (Columnas + Vigas) | 29.66 | CM |
Casco Cisterna (Muros+Techo) | 28.16 | CM |
TOTAL CARGA MUERTA | 73.74 | RESUMEN |
TOTAL CARGA VIVA | 38.17 | RESUMEN |
TOTAL SERVICIO | 111.92 | RESUMEN |
class CalculadoraGeotecnia:
def __init__(self, obj_input, suelo, dim_user=None):
# 1. Recuperación de Datos (Desde el diccionario 'parametros')
params = obj_input['parametros']
self.P_Servicio = params['P_Servicio']
self.Total_CM = params['Total_CM']
self.Total_CV = params['Total_CV']
# Geometría de la Cisterna (Base para el mínimo)
# Por ahora asumimos cisterna cuadrada en planta base, pero preparada para X/Y
self.L_Cis_X = params['L_Cis_X']
self.L_Cis_Y = params['L_Cis_X'] # Asumimos cuadrada si no se definió Y
self.H_Zap = params['H_Zapata']
self.Suelo = suelo
self.DimUser = dim_user # Tupla opcional (Lx, Ly) si el usuario quiere forzar medidas
def Ejecutar(self):
# 1. Capacidad Portante Neta
# Sigma_Neto = Sigma_t - (Sobrecarga Relleno 3m) - (Peso Propio Zapata)
# Asumimos relleno promedio gamma=1.8
Sigma_Neto = self.Suelo['Sigma_t'] - (3.00 * 1.8) - (self.H_Zap * 2.4)
if Sigma_Neto <= 0:
raise ValueError(f"ERROR: Capacidad Neta Negativa ({Sigma_Neto}). Suelo muy pobre o zapata muy profunda.")
# 2. Área Requerida
Area_Req = self.P_Servicio / Sigma_Neto
# 3. Dimensionamiento (Lógica de Selección)
if self.DimUser:
# A. Usuario define dimensiones
L_Final_X, L_Final_Y = self.DimUser
Nota = "Definido por Usuario"
if (L_Final_X * L_Final_Y) < Area_Req:
print(f"⚠️ ADVERTENCIA: El área ingresada ({L_Final_X*L_Final_Y:.2f} m2) es menor a la requerida ({Area_Req:.2f} m2).")
else:
# B. Cálculo Automático (Zapata Cuadrada Óptima)
L_Teorico = np.sqrt(Area_Req)
# El ancho no puede ser menor que la cisterna
L_Min_Arq = max(self.L_Cis_X, self.L_Cis_Y)
# Seleccionamos el mayor y redondeamos a 0.05m
L_Calc = max(L_Teorico, L_Min_Arq)
L_Final = np.ceil(L_Calc * 20) / 20
L_Final_X = L_Final
L_Final_Y = L_Final
Nota = "Cálculo Automático"
# 4. Presión de Contacto Real (Servicio)
Area_Final = L_Final_X * L_Final_Y
Presion_Real = self.P_Servicio / Area_Final
# 5. Estructuración de Salida
data = {
"Descripcion": [
"Capacidad Neta Disponible",
"Área Requerida (Suelo)",
"Lado Mínimo (Cisterna)",
"LADO ZAPATA X",
"LADO ZAPATA Y",
"Presión Real Contacto"
],
"Valor": [
Sigma_Neto,
Area_Req,
max(self.L_Cis_X, self.L_Cis_Y),
L_Final_X,
L_Final_Y,
Presion_Real
],
"Unidad": ["Ton/m2", "m2", "m", "m", "m", "Ton/m2"]
}
df = pd.DataFrame(data)
# Retorno compatible con Fase 3 (Diseño Acero)
return {
"tabla": df,
"parametros": {
"Lx_Zap": L_Final_X,
"Ly_Zap": L_Final_Y,
"Lx_Cis": self.L_Cis_X,
"Ly_Cis": self.L_Cis_Y,
"H_Zap": self.H_Zap,
"Total_CM": self.Total_CM, # <--- CLAVE: Pasamos cargas separadas
"Total_CV": self.Total_CV,
"Presion_Serv": Presion_Real
}
}
# --- INPUTS ---
SueloDatos = {'Sigma_t': 11.0} # Ton/m2
# Opción A: Automático
# dim_user = None
# Opción B: Forzar dimensiones (ej. 4.50 x 4.50)
dim_user = (7.60, 4.45)
geotecnia = CalculadoraGeotecnia(resultados_py, SueloDatos, dim_user)
resultados_geo = geotecnia.Ejecutar()library(reticulate)
library(flextable)
library(dplyr)
# 1. Extracción Segura
py_run_string("export_geo = resultados_geo['tabla'].to_dict(orient='list')")
raw_geo <- py$export_geo
# 2. Reconstrucción
df_geo <- data.frame(
Parametro = unlist(raw_geo$Descripcion),
Valor = as.numeric(unlist(raw_geo$Valor)),
Unidad = unlist(raw_geo$Unidad),
stringsAsFactors = FALSE
)
# 3. Tabla
flextable(df_geo) %>%
set_caption("Tabla 2: Dimensionamiento Geotécnico") %>%
set_header_labels(Parametro = "Parámetro", Valor = "Resultado") %>%
colformat_double(j = "Valor", digits = 2) %>%
theme_box() %>%
autofit() %>%
# Resaltar Lados Finales
bg(i = ~ Parametro %in% c("LADO ZAPATA X", "LADO ZAPATA Y"), bg = "#E2EFDA") %>%
bold(i = ~ Parametro %in% c("LADO ZAPATA X", "LADO ZAPATA Y")) %>%
align(j = c("Valor", "Unidad"), align = "center", part = "all")Parámetro | Resultado | Unidad |
|---|---|---|
Capacidad Neta Disponible | 4.40 | Ton/m2 |
Área Requerida (Suelo) | 25.44 | m2 |
Lado Mínimo (Cisterna) | 3.35 | m |
LADO ZAPATA X | 7.60 | m |
LADO ZAPATA Y | 4.45 | m |
Presión Real Contacto | 3.31 | Ton/m2 |
class CalculadoraConcretoDetallada:
def __init__(self, geo_output, mat, acero_config, vuelos_reales):
params = geo_output['parametros']
self.Lx_Zap = params['Lx_Zap']
self.Ly_Zap = params['Ly_Zap']
self.Lx_Cis = params['Lx_Cis']
self.Ly_Cis = params['Ly_Cis']
self.H_Zap = params['H_Zap']
self.Total_CM = params['Total_CM']
self.Total_CV = params['Total_CV']
self.Mat = mat
self.Barra = acero_config
self.Vuelos = vuelos_reales
def _disenar_eje(self, Vuelo_1, Vuelo_2, L_Zap_Transversal, qu):
# 1. Momento
Vuelo_Critico = max(Vuelo_1, Vuelo_2)
Lado = "Izquierda/Arriba" if Vuelo_1 > Vuelo_2 else "Derecha/Abajo"
Mu = (qu * Vuelo_Critico**2) / 2
# 2. Acero por Cálculo (Flexión)
Rec = 7.5
d = (self.H_Zap * 100) - Rec - (self.Barra['diam_cm'] / 2)
b = 100
a = 2.0
for _ in range(10):
As_Calc = (Mu * 100000) / (0.90 * self.Mat['Fy'] * (d - a/2))
a = (As_Calc * self.Mat['Fy']) / (0.85 * self.Mat['Fc'] * b)
# 3. Acero Mínimo (Temperatura - Norma E.060/ACI)
# As_min = 0.0018 * b * h
As_Min = 0.0018 * b * (self.H_Zap * 100)
# 4. Decisión
if As_Min >= As_Calc:
As_Final = As_Min
Control = "MÍNIMO (Temp)"
else:
As_Final = As_Calc
Control = "CÁLCULO (Flexión)"
# 5. Distribución
s_calc = (self.Barra['area_cm2'] / As_Final) * 100
s_final = np.floor(s_calc) / 100
N_barras = int(L_Zap_Transversal / s_final) + 1
Detalle = f"{N_barras} \u03C6 {self.Barra['nombre']} @ {s_final:.2f} m"
return {
"Vuelo": Vuelo_Critico,
"Lado": Lado,
"Mu": Mu,
"As_Calc": As_Calc,
"As_Min": As_Min,
"As_Final": As_Final,
"Control": Control,
"Detalle": Detalle
}
def Ejecutar(self):
# Cargas
Pu = (1.4 * self.Total_CM) + (1.7 * self.Total_CV)
Area_Zap = self.Lx_Zap * self.Ly_Zap
qu = Pu / Area_Zap
# Diseños
# Eje X (Transversal Ly)
Res_X = self._disenar_eje(self.Vuelos['Ix'], self.Vuelos['Dx'], self.Ly_Zap, qu)
# Eje Y (Transversal Lx)
Res_Y = self._disenar_eje(self.Vuelos['Iy'], self.Vuelos['Dy'], self.Lx_Zap, qu)
# Tabla Detallada
data = {
"Concepto": [
"Carga Factorada (Pu)", "Presión Diseño (qu)",
"--- EJE X (Horizontal) ---",
f"Vuelo Crítico ({Res_X['Lado']})",
"Momento Último (Mu)",
"As Requerido (Por Cálculo)", # <--- NUEVO
"As Mínimo (Norma)", # <--- NUEVO
f"AS DISEÑO ({Res_X['Control']})", # <--- Dice cual ganó
"DISTRIBUCIÓN EJE X",
"--- EJE Y (Vertical) ---",
f"Vuelo Crítico ({Res_Y['Lado']})",
"Momento Último (Mu)",
"As Requerido (Por Cálculo)", # <--- NUEVO
"As Mínimo (Norma)", # <--- NUEVO
f"AS DISEÑO ({Res_Y['Control']})", # <--- Dice cual ganó
"DISTRIBUCIÓN EJE Y"
],
"Resultado": [
f"{Pu:.2f}", f"{qu:.2f}",
"",
f"{Res_X['Vuelo']:.2f}",
f"{Res_X['Mu']:.2f}",
f"{Res_X['As_Calc']:.2f}", # Valor Calc
f"{Res_X['As_Min']:.2f}", # Valor Min
f"{Res_X['As_Final']:.2f}", # Valor Final
Res_X['Detalle'],
"",
f"{Res_Y['Vuelo']:.2f}",
f"{Res_Y['Mu']:.2f}",
f"{Res_Y['As_Calc']:.2f}", # Valor Calc
f"{Res_Y['As_Min']:.2f}", # Valor Min
f"{Res_Y['As_Final']:.2f}", # Valor Final
Res_Y['Detalle']
],
"Unidad": [
"Ton", "Ton/m2",
"", "m", "Ton.m/m", "cm2/m", "cm2/m", "cm2/m", "-",
"", "m", "Ton.m/m", "cm2/m", "cm2/m", "cm2/m", "-"
]
}
df = pd.DataFrame(data)
return {"tabla": df, "parametros": {}}
# --- INPUTS (Asegúrate que coinciden con tu imagen/geotecnia) ---
Inputs_Imagen = {'Ix': 1.00, 'Dx': 0.70, 'Iy': 2.45, 'Dy': 2.45}
ConfigBarra = {'nombre': '3/4"', 'diam_cm': 1.905, 'area_cm2': 2.85}
MatDiseno = {'Fc': 210, 'Fy': 4200}
concreto = CalculadoraConcretoDetallada(resultados_geo, MatDiseno, ConfigBarra, Inputs_Imagen)
resultados_acero = concreto.Ejecutar()library(reticulate)
library(flextable)
library(dplyr)
py_run_string("export_acero = resultados_acero['tabla'].to_dict(orient='list')")
raw_acero <- py$export_acero
df_acero <- data.frame(
Concepto = unlist(raw_acero$Concepto),
Resultado = unlist(raw_acero$Resultado),
Unidad = unlist(raw_acero$Unidad),
stringsAsFactors = FALSE
)
flextable(df_acero) %>%
set_caption("Tabla 3: Diseño de Acero (Comparativo Cálculo vs Mínimo)") %>%
theme_box() %>%
autofit() %>%
# 1. Separadores de Sección
bg(i = ~ grepl("---", Concepto), bg = "#404040") %>%
color(i = ~ grepl("---", Concepto), color = "white") %>%
bold(i = ~ grepl("---", Concepto)) %>%
# 2. Resaltar COMPARATIVO
# Gris suave para los inputs de la decisión
bg(i = ~ grepl("As Requerido|As Mínimo", Concepto), bg = "#F9F9F9") %>%
# VERDE para el Ganador (As Diseño)
bg(i = ~ grepl("AS DISEÑO", Concepto), bg = "#E2EFDA") %>%
bold(i = ~ grepl("AS DISEÑO", Concepto)) %>%
# 3. Resaltar RECETA FINAL (Amarillo)
bg(i = ~ grepl("DISTRIBUCIÓN", Concepto), bg = "#FFFF00") %>%
bold(i = ~ grepl("DISTRIBUCIÓN", Concepto)) %>%
align(j = "Resultado", align = "center", part = "all")Concepto | Resultado | Unidad |
|---|---|---|
Carga Factorada (Pu) | 168.14 | Ton |
Presión Diseño (qu) | 4.97 | Ton/m2 |
--- EJE X (Horizontal) --- | ||
Vuelo Crítico (Izquierda/Arriba) | 1.00 | m |
Momento Último (Mu) | 2.49 | Ton.m/m |
As Requerido (Por Cálculo) | 1.59 | cm2/m |
As Mínimo (Norma) | 9.00 | cm2/m |
AS DISEÑO (MÍNIMO (Temp)) | 9.00 | cm2/m |
DISTRIBUCIÓN EJE X | 15 φ 3/4" @ 0.31 m | - |
--- EJE Y (Vertical) --- | ||
Vuelo Crítico (Derecha/Abajo) | 2.45 | m |
Momento Último (Mu) | 14.92 | Ton.m/m |
As Requerido (Por Cálculo) | 9.77 | cm2/m |
As Mínimo (Norma) | 9.00 | cm2/m |
AS DISEÑO (CÁLCULO (Flexión)) | 9.77 | cm2/m |
DISTRIBUCIÓN EJE Y | 27 φ 3/4" @ 0.29 m | - |
class VerificacionCortante:
def __init__(self, geo_output, mat, acero_config):
params = geo_output['parametros']
# Geometría
self.Lx_Zap = params['Lx_Zap']
self.Ly_Zap = params['Ly_Zap']
self.Lx_Cis = params['Lx_Cis']
self.Ly_Cis = params['Ly_Cis']
self.H_Zap = params['H_Zap']
# Cargas
self.Total_CM = params['Total_CM']
self.Total_CV = params['Total_CV']
self.Mat = mat
# d promedio (restamos 1 diametro aprox)
self.d_cm = (self.H_Zap * 100) - 7.5 - acero_config['diam_cm']
self.d_m = self.d_cm / 100
def _capacidad_concreto(self, b_cm, d_cm, tipo="Viga"):
phi = 0.85
fc = self.Mat['Fc']
if tipo == "Viga":
Vc = 0.53 * np.sqrt(fc) * b_cm * d_cm
elif tipo == "Punzonamiento":
Vc = 1.06 * np.sqrt(fc) * b_cm * d_cm
return (phi * Vc) / 1000 # Ton
def Ejecutar(self):
# 1. Cargas
Pu = (1.4 * self.Total_CM) + (1.7 * self.Total_CV)
Area_Zap = self.Lx_Zap * self.Ly_Zap
qu = Pu / Area_Zap
Resultados = []
# ==========================================
# 1. CORTANTE 1-DIRECCIÓN (VIGA)
# ==========================================
# Calculamos vuelos teóricos promedio
Vuelo_X = (self.Lx_Zap - self.Lx_Cis) / 2
Vuelo_Y = (self.Ly_Zap - self.Ly_Cis) / 2
# Determinamos cuál es el crítico para reportarlo
if Vuelo_X > Vuelo_Y:
Eje_Critico = "EJE X (Transversal)"
Vuelo_Max = Vuelo_X
L_Corte = self.Ly_Zap # El ancho que resiste es el opuesto
else:
Eje_Critico = "EJE Y (Longitudinal)"
Vuelo_Max = Vuelo_Y
L_Corte = self.Lx_Zap
# Análisis
L_Tributaria = Vuelo_Max - self.d_m
if L_Tributaria > 0:
# Vu = qu * Area_Trib (Ancho total de la zapata)
# OJO: Se verifica en todo el ancho L_Corte
Vu_Viga = qu * L_Tributaria * L_Corte
# Resistencia en todo el ancho
Phi_Vc_Viga = self._capacidad_concreto(L_Corte*100, self.d_cm, "Viga")
Ratio_Viga = Vu_Viga / Phi_Vc_Viga
Status_Viga = "OK" if Ratio_Viga <= 1.0 else "FALLA"
else:
Vu_Viga = 0
Phi_Vc_Viga = 0
Ratio_Viga = 0
Status_Viga = "N/A (d > Vuelo)"
Resultados.append({
"Control": f"Cortante por Flexión",
"Detalle": f"Crítico: {Eje_Critico}", # <--- NUEVO CAMPO
"Demanda_Vu": Vu_Viga,
"Capacidad_PhiVc": Phi_Vc_Viga,
"Ratio": Ratio_Viga,
"Estado": Status_Viga
})
# ==========================================
# 2. PUNZONAMIENTO (2-DIRECCIONES)
# ==========================================
b1 = self.Lx_Cis + self.d_m
b2 = self.Ly_Cis + self.d_m
Perimetro_Bo = 2 * (b1 + b2)
Area_Critica = b1 * b2
if Area_Critica < Area_Zap:
Fuerza_Alivio = qu * Area_Critica
Vu_Punch = Pu - Fuerza_Alivio
Phi_Vc_Punch = self._capacidad_concreto(Perimetro_Bo*100, self.d_cm, "Punzonamiento")
Ratio_Punch = Vu_Punch / Phi_Vc_Punch
Status_Punch = "OK" if Ratio_Punch <= 1.0 else "FALLA"
else:
Vu_Punch = 0
Phi_Vc_Punch = 0
Ratio_Punch = 0
Status_Punch = "N/A"
Resultados.append({
"Control": "Punzonamiento",
"Detalle": f"Perímetro Bo = {Perimetro_Bo:.2f} m", # <--- NUEVO CAMPO
"Demanda_Vu": Vu_Punch,
"Capacidad_PhiVc": Phi_Vc_Punch,
"Ratio": Ratio_Punch,
"Estado": Status_Punch
})
df = pd.DataFrame(Resultados)
return {"tabla": df}
# Ejecución
cortante = VerificacionCortante(resultados_geo, MatDiseno, ConfigBarra)
resultados_cortante = cortante.Ejecutar()library(reticulate)
library(flextable)
library(dplyr)
# 1. Extracción Segura
py_run_string("export_cort = resultados_cortante['tabla'].to_dict(orient='list')")
raw_cort <- py$export_cort
# 2. Reconstrucción
df_cort <- data.frame(
Control = unlist(raw_cort$Control),
Detalle = unlist(raw_cort$Detalle), # <--- Columna Nueva
Vu = as.numeric(unlist(raw_cort$Demanda_Vu)),
PhiVc = as.numeric(unlist(raw_cort$Capacidad_PhiVc)),
Ratio = as.numeric(unlist(raw_cort$Ratio)),
Estado = unlist(raw_cort$Estado),
stringsAsFactors = FALSE
)
# 3. Renderizado
ft <- flextable(df_cort) %>%
set_caption("Tabla 4: Verificación de Cortante (ACI 318)") %>%
set_header_labels(
Control = "Tipo de Verificación",
Detalle = "Ubicación / Detalle",
Vu = "Vu (Ton)",
PhiVc = "φVc (Ton)",
Ratio = "Ratio"
) %>%
theme_box() %>%
autofit() %>%
colformat_double(j = c("Vu", "PhiVc", "Ratio"), digits = 2) %>%
# Resaltar la ubicación crítica en itálica
italic(j = "Detalle") %>%
# Semáforo
bg(i = ~ Ratio <= 1.0, j = "Estado", bg = "#C6EFCE") %>%
color(i = ~ Ratio <= 1.0, j = "Estado", color = "#006100") %>%
bg(i = ~ Ratio > 1.0, j = "Estado", bg = "#FFC7CE") %>%
color(i = ~ Ratio > 1.0, j = "Estado", color = "#9C0006") %>%
bold(j = "Estado") %>%
align(j = c("Vu", "PhiVc", "Ratio", "Estado"), align = "center", part = "all")
ftTipo de Verificación | Ubicación / Detalle | Vu (Ton) | φVc (Ton) | Ratio | Estado |
|---|---|---|---|---|---|
Cortante por Flexión | Crítico: EJE X (Transversal) | 38.03 | 117.93 | 0.32 | OK |
Punzonamiento | Perímetro Bo = 15.02 m | 98.00 | 796.32 | 0.12 | OK |