# -*- coding: utf-8 -*-
"""
Monomotapa - A Micro CMS
Copyright (C) 2014, Paul Munday.
PO Box 28228, Portland, OR, USA 97228
paul at paulmunday.net
Modificado por: Rodrigo Garcia 2017 https://rmgs.com.bo/contacto
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Monomotapa:
a city whose inhabitants are bounded by deep feelings of friendship,
so that they intuit one another's most secret needs and desire.
For instance, if one dreams that his friend is sad, the friend will
perceive the distress and rush to the sleepers rescue.
(Jean de La Fontaine, *Fables choisies, mises en vers*, VIII:11 Paris,
2nd ed., 1678-9)
cited in :
Alberto Manguel and Gianni Guadalupi, *The Dictionary of Imaginary Places*,
Bloomsbury, London, 1999.
A micro cms written using the Flask microframework, orignally to manage my
personal site. It is designed so that publishing a page requires no more than
dropping a markdown page in the appropriate directory (though you need to edit
a json file if you want it to appear in the top navigation).
It can also display its own source code and run its own unit tests.
The name 'monomotapa' was chosen more or less at random (it shares an initial
with me) as I didn't want to name it after the site and be typing import
paulmunday, or something similar, as that would be strange.
"""
from flask import render_template, abort, request #, make_response
from flask import redirect
from markupsafe import Markup, escape
from werkzeug.utils import secure_filename
from pygments import highlight
from pygments.lexers import PythonLexer, HtmlDjangoLexer, TextLexer
from pygments.formatters import HtmlFormatter
import markdown
from time import gmtime, strptime, strftime, ctime, mktime
import datetime
import os.path
import os
import subprocess
import json
import traceback
from collections import OrderedDict
from simplemotds import SimpleMotd
from monomotapa import app
from monomotapa.config import ConfigError
from monomotapa.utils import captcha_comprobar_respuesta, captcha_pregunta_opciones_random
from monomotapa.utils import categorias_de_post, categoriasDePost, categoriasList, cabezaPost
from monomotapa.utils import titulo_legible, metaTagsAutomaticos
from markdown.extensions.toc import TocExtension
json_pattrs = {}
with open(os.path.join('monomotapa','pages.json'), 'r') as pagefile:
json_pattrs = json.load(pagefile)
simplemotd = SimpleMotd("config_simplemotds.json")
class MonomotapaError(Exception):
"""create classs for own errors"""
pass
def get_page_attributes(jsonfile):
"""Returns dictionary of page_attributes.
Defines additional static page attributes loaded from json file.
N.B. static pages do not need to have attributes defined there,
it is sufficient to have a page.md in src for each /page
possible values are src (name of markdown file to be rendered)
heading, title, and trusted (i.e. allow embeded html in markdown)"""
try:
with open(src_file(jsonfile), 'r') as pagesfile:
page_attributes = json.load(pagesfile)
except IOError:
page_attributes = []
return page_attributes
def get_page_attribute(attr_src, page, attribute):
"""returns attribute of page if it exists, else None.
attr_src = dictionary(from get_page_attributes)"""
if page in attr_src and attribute in attr_src[page]:
return attr_src[page][attribute]
else:
return None
# Navigation
def top_navigation(page):
"""Generates navigation as an OrderedDict from navigation.json.
Navigation.json consists of a json array(list) "nav_order"
containing the names of the top navigation elements and
a json object(dict) called "nav_elements"
if a page is to show up in the top navigation
there must be an entry present in nav_order but there need not
be one in nav_elements. However if there is the key must be the same.
Possible values for nav_elements are link_text, url and urlfor
The name from nav_order will be used to set the link text,
unless link_text is present in nav_elements.
url and urlfor are optional, however if ommited the url wil be
generated in the navigation by url_for('staticpage', page=[key])
equivalent to @app.route"/page"; def page())
which may not be correct. If a url is supplied it will be used
otherwise if urlfor is supplied it the url will be
generated with url_for(urlfor). url takes precendence so it makes
no sense to supply both.
Web Sign-in is supported by adding a "rel": "me" attribute.
"""
with open(src_file('navigation.json'), 'r') as navfile:
navigation = json.load(navfile)
base_nav = OrderedDict({})
for key in navigation["nav_order"]:
nav = {}
nav['base'] = key
nav['link_text'] = key
if key in navigation["nav_elements"]:
elements = navigation["nav_elements"][key]
nav.update(elements)
base_nav[key] = nav
return {'navigation' : base_nav, 'page' : page}
# For pages
class Page:
"""Generates pages as objects"""
def __init__(self, page, **kwargs):
"""Define attributes for pages (if present).
Sets self.name, self.title, self.heading, self.trusted etc
This is done through indirection so we can update the defaults
(defined in the 'attributes' dictionary) with values from config.json
or pages.json easily without lots of if else statements.
If css is supplied it will overide any default css. To add additional
style sheets on a per page basis specifiy them in pages.json.
The same also applies with hlinks.
css is used to set locally hosted stylesheets only. To specify
external stylesheets use hlinks: in config.json for
default values that will apply on all pages unless overidden, set here
to override the default. Set in pages.json to add after default.
"""
# set default attributes
self.page = page.rstrip('/')
self.defaults = get_page_attributes('defaults.json')
self.pages = get_page_attributes('pages.json')
self.url_base = self.defaults['url_base']
title = titulo_legible(page.lower())
heading = titulo_legible(page.capitalize())
self.categorias = categoriasDePost(self.page)
self.exclude_toc = True
try:
self.default_template = self.defaults['template']
except KeyError:
raise ConfigError('template not found in default.json')
# will become self.name, self.title, self.heading,
# self.footer, self.internal_css, self.trusted
attributes = {'name' : self.page, 'title' : title,
'navigation' : top_navigation(self.page),
'heading' : heading, 'footer' : None,
'css' : None , 'hlinks' :None, 'internal_css' : None,
'trusted': False,
'preview-chars': 250,
}
# contexto extra TODO: revisar otra forma de incluir un contexto
self.contexto = {}
self.contexto['consejo'] = simplemotd.getMotdContent()
# set from defaults
attributes.update(self.defaults)
# override with kwargs
attributes.update(kwargs)
# override attributes if set in pages.json
if page in self.pages:
attributes.update(self.pages[page])
# set attributes (as self.name etc) using indirection
for attribute, value in attributes.items():
# print('attribute', attribute, '=-==>', value)
setattr(self, attribute, value)
# meta tags
try:
self.pages[self.page]['title'] = attributes['title']
self.pages[self.page]['url_base'] = self.url_base
metaTags = metaTagsAutomaticos(self.page, self.pages.get(self.page, {}))
self.meta = metaTags
# for key, value in self.pages[self.page].items():
# print(' ', key, ' = ', value)
except Exception as e:
tb = traceback.format_exc()
print('Error assigning meta:', str(e), '\n', str(tb))
# reset these as we want to append rather than overwrite if supplied
if 'css' in kwargs:
self.css = kwargs['css']
elif 'css' in self.defaults:
self.css = self.defaults['css']
if 'hlinks' in kwargs:
self.hlinks = kwargs['hlinks']
elif 'hlinks' in self.defaults:
self.hlinks = self.defaults['hlinks']
# append hlinks and css from pages.json rather than overwriting
# if css or hlinks are not supplied they are set to default
if page in self.pages:
if 'css' in self.pages[page]:
self.css = self.css + self.pages[page]['css']
if 'hlinks' in self.pages[page]:
self.hlinks = self.hlinks + self.pages[page]['hlinks']
# append heading to default if set in config
self.title = self.title + app.config.get('default_title', '')
def _get_markdown(self):
"""returns rendered markdown or 404 if source does not exist"""
src = self.get_page_src(self.page, 'src', 'md')
if src is None:
abort(404)
else:
return render_markdown(src, self.trusted)
def get_page_src(self, page, directory=None, ext=None):
""""return path of file (used to generate page) if it exists,
or return none.
Also returns the template used to render that page, defaults
to static.html.
It will optionally add an extension, to allow
specifiying pages by route."""
# is it stored in a config
pagename = get_page_attribute(self.pages, page, 'src')
if not pagename:
pagename = page + get_extension(ext)
if os.path.exists(src_file(pagename , directory)):
return src_file(pagename, directory)
else:
return None
def get_template(self, page):
"""returns the template for the page"""
pagetemplate = get_page_attribute(self.pages, page, 'template')
if not pagetemplate:
pagetemplate = self.default_template
if os.path.exists(src_file(pagetemplate , 'templates')):
return pagetemplate
else:
raise MonomotapaError("Template: %s not found" % pagetemplate)
def generate_page(self, contents=None):
"""return a page generator function.
For static pages written in Markdown under src/.
contents are automatically rendered.
N.B. See note above in about headers"""
toc = '' # table of contents
if not contents:
contents, toc = self._get_markdown()
# print('////', toc)
template = self.get_template(self.page)
# print('......................')
# def mos(**kwargs):
# for k in kwargs:
# print(k, end=',')
# mos(**vars(self))
return render_template(template,
contents = Markup(contents),
toc=toc,
**vars(self))
# helper functions
def src_file(name, directory=None):
"""return potential path to file in this app"""
if not directory:
return os.path.join( 'monomotapa', name)
else:
return os.path.join('monomotapa', directory, name)
def get_extension(ext):
'''constructs extension, adding or stripping leading . as needed.
Return null string for None'''
if ext is None:
return ''
elif ext[0] == '.':
return ext
else:
return '.%s' % ext
def render_markdown(srcfile, trusted=False):
""" Returns markdown file rendered as html and the table of contents as html.
Defaults to untrusted:
html characters (and character entities) are escaped
so will not be rendered. This departs from markdown spec
which allows embedded html."""
try:
with open(srcfile, 'r') as f:
src = f.read()
md = markdown.Markdown(extensions=['toc', 'codehilite'])
md.convert(src)
toc = md.toc
if trusted == True:
content = markdown.markdown(src,
extensions=['codehilite',
TocExtension(permalink=True)])
else:
content = markdown.markdown(escape(src),
extensions=['codehilite',
TocExtension(permalink=True)])
return content, toc
except IOError:
return None
def render_pygments(srcfile, lexer_type):
"""returns src(file) marked up with pygments"""
if lexer_type == 'python':
with open(srcfile, 'r') as f:
src = f.read()
contents = highlight(src, PythonLexer(), HtmlFormatter())
elif lexer_type == 'html':
with open(srcfile, 'r') as f:
src = f.read()
contents = highlight(src, HtmlDjangoLexer(), HtmlFormatter())
# default to TextLexer for everything else
else:
with open(srcfile, 'r') as f:
src = f.read()
contents = highlight(src, TextLexer(), HtmlFormatter())
return contents
def get_pygments_css(style=None):
"""returns css for pygments, use as internal_css"""
if style is None:
style = 'friendly'
return HtmlFormatter(style=style).get_style_defs('.highlight')
def heading(text, level):
"""return as html heading at h[level]"""
heading_level = 'h%s' % str(level)
return '\n<%s>%s</%s>\n' % (heading_level, text, heading_level)
def posts_list(ordenar_por_fecha=True, ordenar_por_nombre=False):
'''Retorna una lista con los nombres de archivos con extension .md
dentro de la cappeta src/posts, por defecto retorna una lista con
la tupla (nombre_archivo, fecha_subida)'''
lista_posts = []
lp = []
if ordenar_por_nombre:
try:
ow = os.walk("monomotapa/src/posts")
p , directorios , archs = ow.__next__()
except OSError:
print ("[posts] - Error: Cant' os.walk() on monomotapa/src/posts except OSError")
else:
for arch in archs:
if arch.endswith(".md") and not arch.startswith("#") \
and not arch.startswith("~") and not arch.startswith("."):
lista_posts.append(arch)
lista_posts.sort()
return lista_posts
if ordenar_por_fecha:
try:
ow = os.walk("monomotapa/src/posts")
p,d,files=ow.__next__()
except OSError:
print ("[posts] - Error: Can't os.walk() on monomotapa/src/posts except OSError.")
else:
for f in files:
nombre_con_ruta = os.path.join("monomotapa/src/posts", f)
if not f.endswith("~") and not f.startswith("#") and not f.startswith("."):
secs_modificacion = SecsModificacionPostDesdeJson(f, json_pattrs)
ultima_modificacion = os.path.getmtime(nombre_con_ruta)
lp.append((secs_modificacion, ultima_modificacion, f))
lp.sort()
lp.reverse()
# colocando fecha en formato
for tupla in lp:
#fecha = strftime("a, %d %b %Y %H:%M:%S", ctime(tupla[0]))
cfecha = ctime(tupla[1])
#fecha = strptime("%a %b %d %H:%M:%S %Y", cfecha)
lista_posts.append((cfecha, tupla[2]))
return lista_posts
def categorias_list(categoria=None):
""" Rotorna una lista con los nombres de posts y el numero de posts que
pertenecen a la categoria dada o a cada categoria.
Las categorias se obtienen analizando la primera linea de cada archivo .md
an la carpeta donde se almacenan los posts.
Si no se especifica `categoria' cada elemento de la lista devuelta es:
(nombre_categoria, numero_posts, [nombres_posts])
si se especifica `categoria' cada elemento de la lista devuelta es:
(numero_posts, [nombres_posts]
"""
lista_posts = posts_list(ordenar_por_nombre=True)
lista_categorias = []
if categoria is not None:
c = 0
posts = []
for post in lista_posts:
nombre_arch = "monomotapa/src/posts/"+post
with open(nombre_arch, 'r') as file:
linea = file.readline().decode("utf-8")
lc = linea.split("[#")[1:]
for cad in lc:
cat = cad.split("]")[0]
if cat == categoria:
c += 1
posts.append(post)
lista_categorias = (c, posts)
return lista_categorias
dic_categorias = {}
for post in lista_posts:
nombre_arch = "monomotapa/src/posts/"+post
with open(nombre_arch, 'r') as fil:
linea = fil.readline().decode('utf-8') # primera linea
# extrayendo las categorias y registrando sus ocurrencias
# ejemplo: catgorías: [#reflexión](categoria/reflexion) [#navidad](categoria/navidad)
# extrae: [reflexion,navidad]
lc = linea.split("[#")[1:]
for cad in lc:
cat = cad.split("]")[0]
if cat not in dic_categorias:
dic_categorias[cat] = (1,[post]) # nuevo registro por categoria
else:
tupla = dic_categorias[cat]
c = tupla[0] + 1
lis = tupla[1]
if post not in lis:
lis.append(post)
dic_categorias[cat] = (c, lis)
# convirtiendo en lista
for k, v in dic_categorias.iteritems():
lista_categorias.append((k,v[0],v[1]))
lista_categorias.sort()
lista_categorias.reverse()
return lista_categorias
def cabeza_post(archivo , max_caracteres=250, categorias=True):
""" Devuelve las primeras lineas de una archivo de post (en formato markdown)
con un maximo numero de caracteres excluyendo titulos en la cabeza devuelta.
Si se especifica `categorias' en True
Se devuelve una lista de la forma:
(cabeza_post, categorias)
donde categorias son cadenas con los nombres de las categorias a la que
pertenece el post
"""
cabeza_post = ""
cats = []
with open(os.path.join("monomotapa/src/posts",archivo)) as file:
# analizando si hay titulos al principio
# Se su pone que la primera linea es de categorias
for linea in file.readlines():
linea = linea.decode("utf-8")
if linea.startswith(u"categorías:") or linea.startswith("categorias"):
if categorias:
cats = categoriasDePost(archivo)
#cats = categorias_de_post(archivo)
else:
# evitando h1, h2
if linea.startswith("##") or linea.startswith("#"):
cabeza_post += " "
else:
cabeza_post += linea
if len(cabeza_post) >= max_caracteres:
break
cabeza_post = cabeza_post[0:max_caracteres-1]
if categorias:
return (cabeza_post, cats)
return cabeza_post
def ultima_modificacion_archivo(archivo):
""" Retorna una cadena indicando la fecha de ultima modificacion del
`archivo' dado, se asume que `archivo' esta dentro la carpeta "monomotapa/src"
Retorna una cadena vacia en caso de no poder abrir `archivo'
"""
try:
ts = strptime(ctime(os.path.getmtime("monomotapa/src/"+archivo+".md")))
return strftime("%d %B %Y", ts)
except OSError:
return ""
def SecsModificacionPostDesdeJson(archivo, dict_json):
''' dado el post con nombre 'archivo' busca en 'dict_json' el
attribute 'date' y luego obtiene los segundos totales desde
esa fecha.
Si no encuentra 'date' para 'archivo' en 'dict.json'
retorna los segundos totales desde la ultima modificacion
del archivo de post directamente (usa os.path.getmtime)
'''
nombre = archivo.split('.md')[0] # no contar extension .md
nombre_con_ruta = os.path.join("monomotapa/src/posts", archivo)
date_str = dict_json.get('posts/'+nombre, {}).\
get('attributes',{}).\
get('date','')
if date_str == '':
# el post no tiene "date" en pages.json
return os.path.getmtime(nombre_con_ruta)
else:
time_struct = strptime(date_str, '%Y-%m-%d')
dt = datetime.datetime.fromtimestamp(mktime(time_struct))
return (dt - datetime.datetime(1970,1,1)).total_seconds()
def noticias_recientes(cantidad=11, max_caracteres=250,
categoria=None, pagina=0):
'''Devuelve una lista con hasta `cantidad' de posts mas recientes,
un maximo de `max_caracteres' de caracteres del principio del post y
el numero total de posts encontrados
Si se proporciona `categoria' devuelve la lista de posts solamente
pertenecientes esa categoria.
Si `pagina' > 0 se devulve hasta `cantidad' numero de posts en el
rango de [ cantidad*pagina : cantidad*(pagina+1)]
Cada elemento de la lista devuelta contiene:
(nombre_post, ultima_modificacion, cabeza_archivo, categorias)
Al final se retorna: (lista_posts, numero_de_posts)
'''
lista_posts = []
lp = []
num_posts = 0
posts_en_categoria = []
if categoria is not None:
#posts_en_categoria = categorias_list(categoria)[1]
posts_en_categoria = categoriasList(categoria)[1]
# categoria especial fotos
if categoria == "fotos":
l = []
for p in posts_en_categoria:
l.append(p + '.md')
posts_en_categoria = l
try:
ow = os.walk("monomotapa/src/posts")
p,d,files = ow.__next__()
#p,d,files=ow.next()
except OSError:
print ("[posts] - Error: Can't os.walk() on monomotapa/src/posts except OSError.")
else:
for f in files:
nombre_con_ruta = os.path.join("monomotapa/src/posts", f)
if not f.endswith("~") and not f.startswith("#") and not f.startswith("."):
secs_modificacion = SecsModificacionPostDesdeJson(f, json_pattrs)
ultima_modificacion = os.path.getmtime(nombre_con_ruta)
previewChars = json_pattrs.get('posts/'+f[:-3], {}).\
get('attributes', {}).\
get('preview-chars', max_caracteres)
if categoria is not None:
if f in posts_en_categoria:
lp.append((secs_modificacion,
ultima_modificacion,
previewChars,
f))
num_posts += 1
else:
lp.append((secs_modificacion,
ultima_modificacion,
previewChars,
f))
num_posts += 1
lp.sort()
lp.reverse()
# seleccionado por paginas
lp = lp[cantidad*pagina : cantidad*(pagina+1)]
# colocando fecha en formato
for tupla in lp:
cfecha = ctime(tupla[1])
nombre_post = tupla[3].split(os.sep)[-1]
previewChars = tupla[2]
#contenido = cabeza_post(tupla[3], max_caracteres=previewChars)[0]
#categorias = cabeza_post(tupla[3], max_caracteres=previewChars)[1]
contenido = cabezaPost(tupla[3], max_caracteres=previewChars)[0]
categorias = cabezaPost(tupla[3], max_caracteres=previewChars)[1]
cabeza_archivo = markdown.markdown(escape(contenido + ' ...'))
lista_posts.append((nombre_post[:-3], cfecha, \
cabeza_archivo, categorias))
return (lista_posts, num_posts)
def noticias_relacionadas(cantidad=5, nombre=None):
"""Retorna una lista con posts relacionadas, es decir que tienen son de las
mismas categorias que el post con nombre `nombre'.
Cada elemento de la lista de posts contiene el nombre del post
"""
#categorias = categorias_de_post(nombre) ## TODO: corregir categorias de post
categorias = categoriasDePost(nombre)
numero = 0
if categorias is None:
return None
posts = []
for categoria in categorias:
#lista = categorias_list(categoria)[1] # nombres de posts
lista = categoriasList(categoria)[1]
numero += len(lista)
for nombre_post in lista:
if nombre_post + '.md' != nombre:
posts.append(nombre_post)
if numero >= cantidad:
return posts
return posts
def rss_ultimos_posts_jinja(cantidad=15):
"""Retorna una lista de los ultimos posts preparados para
ser renderizados (usando jinja) como un feed rss
Examina cada post del mas reciente al menos reciente, en
total `cantidad' posts. Por cada post devuelve:
id: id which identifies the entry using a
universally unique and permanent URI
author: Get or set autor data. An author element is a dict containing a
name, an email adress and a uri.
category: A categories has the following fields:
- *term* identifies the category
- *scheme* identifies the categorization scheme via a URI.
- *label* provides a human-readable label for display
comments: Get or set the the value of comments which is the url of the
comments page for the item.
content: Get or set the cntent of the entry which contains or links to the
complete content of the entry.
description(no contiene): Get or set the description value which is the item synopsis.
Description is an RSS only element.
link: Get or set link data. An link element is a dict with the fields
href, rel, type, hreflang, title, and length. Href is mandatory for
ATOM.
pubdate(no contiene): Get or set the pubDate of the entry which indicates when the entry
was published.
title: the title value of the entry. It should contain a human
readable title for the entry.
updated: the updated value which indicates the last time the entry
was modified in a significant way.
"""
lista_posts = []
lp = []
num_posts = 0
try:
ow = os.walk("monomotapa/src/posts")
p,d,files=ow.__next__()
except OSError:
print ("[posts] - Error: Can't os.walk() on monomotapa/src/posts except OSError.")
else:
for f in files:
nombre_con_ruta = os.path.join("monomotapa/src/posts", f)
if not f.endswith("~") and not f.startswith("#") and not f.startswith("."):
lp.append((os.path.getmtime(nombre_con_ruta), f))
num_posts += 1
if num_posts > cantidad:
break
lp.sort()
lp.reverse()
# colocando fecha en formato
for tupla in lp:
nombre_post = tupla[1].split(os.sep)[-1]
#contenido = cabeza_post(tupla[1], max_caracteres=149999)
contenido = cabezaPost(tupla[1], max_caracteres=149999)
id_post = "https://rmgss.net/posts/"+nombre_post[:-3]
#categorias = categorias_de_post(nombre_post)
categorias = categoriasDePost(nombre_post)
dict_categorias = {}
c = ""
for cat in categorias:
c += cat + " "
dict_categorias['label'] = c
#dict_categorias['term'] = c
html = markdown.markdown(escape(contenido))
link = id_post
pubdate = ctime(tupla[0])
title = titulo_legible(nombre_post[:-3]) # no incluir '.md'
updated = pubdate
dict_feed_post = {
"id":id_post,
"author": "Rodrigo Garcia",
"category" : categorias,
"content": html,
"link" : id_post,
"updated" : updated,
"title": title
}
lista_posts.append(dict_feed_post)
return lista_posts
###### Define routes
@app.errorhandler(404)
def page_not_found(e):
""" provides basic 404 page"""
defaults = get_page_attributes('defaults.json')
try:
css = defaults['css']
except KeyError:
css = None
pages = get_page_attributes('pages.json')
if '404' in pages:
if'css' in pages['404']:
css = pages['404']['css']
return render_template('static.html',
title = "404::page not found", heading = "Page Not Found",
navigation = top_navigation('404'),
css = css,
contents = Markup(
"This page is not there, try somewhere else.")), 404
@app.route('/users/', defaults={'page': 1})
@app.route('/users/page/<int:page>')
@app.route("/", defaults={'pagina':0})
@app.route('/<int:pagina>')
def index(pagina):
"""provides index page"""
index_page = Page('index')
lista_posts_recientes, total_posts = noticias_recientes(pagina=pagina)
index_page.contexto['lista_posts_recientes'] = lista_posts_recientes
index_page.contexto['total_posts'] = total_posts
index_page.contexto['pagina_actual'] = int(pagina)
return index_page.generate_page()
# default route is it doe not exist elsewhere
@app.route("/<path:page>")
def staticpage(page):
""" display a static page rendered from markdown in src
i.e. displays /page or /page/ as long as src/page.md exists.
srcfile, title and heading may be set in the pages global
(ordered) dictionary but are not required"""
static_page = Page(page)
return static_page.generate_page()
@app.route("/posts/<page>")
def rposts(page):
""" Mustra las paginas dentro la carpeta posts, no es coincidencia
que en este ultimo directorio se guarden los posts.
Ademas incrusta en el diccionario de contexto de la pagina la
fecha de ultima modificacion del post
"""
static_page = Page("posts/"+page)
ultima_modificacion = ultima_modificacion_archivo("posts/"+page)
static_page.contexto['relacionadas'] = noticias_relacionadas(nombre=page+".md")
static_page.contexto['ultima_modificacion'] = ultima_modificacion
static_page.exclude_toc = False # no excluir Índice de contenidos
return static_page.generate_page()
@app.route("/posts")
def indice_posts():
""" Muestra una lista de todos los posts
"""
lista_posts_fecha = posts_list()
#lista_posts_categoria = categorias_list()
lista_posts_categoria = categoriasList()
static_page = Page("posts")
static_page.contexto['lista_posts_fecha'] = lista_posts_fecha
static_page.contexto['lista_posts_categoria'] = lista_posts_categoria
return static_page.generate_page()
@app.route("/posts/categorias")
def lista_categorias():
""" Muestra una lista de las categorias , los posts pertenecen
a cada una y un conteo"""
#lista_categorias = categorias_list()
lista_categorias = categoriasList()
static_page = Page("categorias")
static_page.contexto['lista_posts_categoria'] = lista_categorias
#return (str(lista_categorias))
return static_page.generate_page()
@app.route("/posts/categoria/<categoria>")
def posts_de_categoria(categoria):
""" Muestra los posts que perteneces a la categoria dada
"""
lista_posts = []
if categoria == "fotos": # caegoria especial fotos
lista_posts, total_posts = noticias_recientes(max_caracteres=1250,categoria=categoria)
static_page = Page("fotos")
static_page.contexto['categoria_actual'] = categoria
static_page.contexto['lista_posts_recientes'] = lista_posts
return static_page.generate_page()
#lista_posts = categorias_list(categoria=categoria)
lista_posts = categoriasList(categoria=categoria)
static_page = Page("categorias")
static_page.contexto['categoria_actual'] = categoria
static_page.contexto['lista_posts_categoria'] = lista_posts
return static_page.generate_page()
@app.route("/posts/recientes", defaults={'pagina':0})
@app.route("/posts/recientes/<int:pagina>")
def posts_recientes(pagina):
""" muestra una lista de los posts mas recientes
TODO: terminar
"""
lista_posts, total_posts = noticias_recientes(max_caracteres=368,
pagina=pagina)
static_page = Page("recientes")
static_page.contexto['lista_posts_recientes'] = lista_posts
static_page.contexto['total_posts'] = total_posts
static_page.contexto['pagina_actual'] = pagina
#return (str(lista_posts))
return static_page.generate_page()
@app.route("/contacto", methods=['GET'])
def contacto():
tupla_captcha = captcha_pregunta_opciones_random()
if tupla_captcha is None:
return ("<br>Parece un error interno!</br>")
pregunta = tupla_captcha[0]
opciones = tupla_captcha[1]
static_page = Page("contacto")
static_page.contexto['pregunta'] = pregunta
static_page.contexto['opciones'] = opciones
return static_page.generate_page()
@app.route("/contactoe", methods=['POST'])
def comprobar_mensaje():
""" Comprueba que el mensaje enviado por la caja de texto sea valido
y si lo es, guarda un archivo de texto con los detalles"""
errors = []
if request.method == "POST":
# comprobando validez
nombre = request.form["nombre"]
dir_respuesta = request.form['dir_respuesta']
mensaje = request.form['mensaje']
pregunta = request.form['pregunta']
respuesta = request.form['respuesta']
if len(mensaje) < 2 or mensaje.startswith(" "):
errors.append("Mensaje invalido")
if not captcha_comprobar_respuesta(pregunta, respuesta):
errors.append("Captcha invalido")
if len(errors) > 0:
return str(errors)
# guardando texto
texto = "Remitente: "+nombre
texto += "\nResponder_a: "+dir_respuesta
texto += "\n--- mensaje ---\n"
texto += mensaje
# TODO: cambiar a direccion especificada en archivo de configuracion
dt = datetime.datetime.now()
nombre = "m_"+str(dt.day)+"_"+str(dt.month)+\
"_"+str(dt.year)+"-"+str(dt.hour)+\
"-"+str(dt.minute)+"-"+str(dt.second)
with open(os.path.join("fbs",nombre), "wb") as f:
f.write(texto.encode("utf-8"))
return redirect("/mensaje_enviado", code=302)
@app.route("/mensaje_enviado")
def mensaje_enviado():
static_page = Page("mensaje_enviado")
return static_page.generate_page()
@app.route("/rss")
def rss_feed():
"""Genera la cadena rss con las 15 ultimas noticias del sitio
TODO: Agregar mecenismo para no generar los rss feeds y solo
devolver el archivo rss.xml generado anteriormente. Esto
quiere decir solamente generar el rss_feed cuando se haya hecho
un actualizacion en los posts mas reciente que la ultima vez
que se genero el rss_feed
"""
#return str(rss_ultimos_posts_jinja())
return render_template("rss.html",
contents = rss_ultimos_posts_jinja())
#**vars(self)
#)
##### specialized pages
@app.route("/source")
def source():
"""Display source files used to render a page"""
source_page = Page('source', title = "view the source code",
#heading = "Ver el código fuente",
heading = "Ver el codigo fuente",
internal_css = get_pygments_css())
page = request.args.get('page')
# get source for markdown if any. 404's for non-existant markdown
# unless special page eg source
pagesrc = source_page.get_page_src(page, 'src', 'md')
special_pages = ['source', 'unit-tests', '404']
if not page in special_pages and pagesrc is None:
abort(404)
# set enable_unit_tests to true in config.json to allow
# unit tests to be run through the source page
if app.config['enable_unit_tests']:
contents = '''<p><a href="/unit-tests" class="button">Run unit tests
</a></p>'''
# render tests.py if needed
if page == 'unit-tests':
contents += heading('tests.py', 2)
contents += render_pygments('tests.py', 'python')
else:
contents = ''
# render views.py
contents += heading('views.py', 2)
contents += render_pygments(source_page.get_page_src('views.py'),
'python')
# render markdown if present
if pagesrc:
contents += heading(os.path.basename(pagesrc), 2)
contents += render_pygments(pagesrc, 'markdown')
# render jinja templates
contents += heading('base.html', 2)
contents += render_pygments(
source_page.get_page_src('base.html', 'templates'), 'html')
template = source_page.get_template(page)
contents += heading(template, 2)
contents += render_pygments(
source_page.get_page_src(template, 'templates'), 'html')
return source_page.generate_page(contents)
# @app.route("/unit-tests")
# def unit_tests():
# """display results of unit tests"""
# unittests = Page('unit-tests', heading = "Test Results",
# internal_css = get_pygments_css())
# # exec unit tests in subprocess, capturing stderr
# capture = subprocess.Popen(["python", "tests.py"],
# stdout = subprocess.PIPE, stderr = subprocess.PIPE)
# output = capture.communicate()
# results = output[1]
# contents = '''<p>
# <a href="/unit-tests" class="button">Run unit tests</a>
# </p><br>\n
# <div class="output" style="background-color:'''
# if 'OK' in results:
# color = "#ddffdd"
# result = "TESTS PASSED"
# else:
# color = "#ffaaaa"
# result = "TESTS FAILING"
# contents += ('''%s">\n<strong>%s</strong>\n<pre>%s</pre>\n</div>\n'''
# % (color, result, results))
# # render test.py
# contents += heading('tests.py', 2)
# contents += render_pygments('tests.py', 'python')
# return unittests.generate_page(contents)
En esta reflexión trato de abordar la importancia de las clases trabajadoras rurales Bolivianas o "el campesinado" y su importancia como sustento de la sociedad, analizando incoherencias en pro de clases pudientes y en desmedro del campesinado.

<small><small>Foto <a href="https://live.staticflickr.com/1211/4732320797_d9d7ec48aa_h.jpg">original</a> de <a href="https://www.flickr.com/photos/prosalus/">Enraíza Derechos antes Prosalus</a> bajo licencia <a href="https://creativecommons.org/licenses/by-nc-nd/2.0/deed.en"> CC BY-NC-ND 2.0</a>.</small></small>
## "Buen día de Dios"
En un pueblo en medio de impresionantes paisajes, "accidentes" geográficos y entre otras cosas huellas de dinosaurios petrificadas, cerca de las cinco de la mañana canta un gallo unas tres veces. Al poco rato desde otras casas, otros dos gallos le siguen y más se van uniendo. Es el pueblo de Toro Toro en el norte de Potosí, y un par de minutos después se escucha la voz de un hombre adulto «Buen día de Dios doña Filomena», y una mujer le responde en voz alta «Buen día». Poco después se escuchan conversaciones cortas en Quechua junto con varios *pio pio* de pollitos siguiendo a sus mamás gallinas.
En la casa de adobe de enfrente, detrás de las murallas de baja altura hay un sembradío de papas y un poco más abajo una parcela pequeña de choclos con plantas jóvenes de hojas caídas. En la fachada de la casa de al lado hay una tienda de frutas que ya está abriendo. Se escuchan con más frecuencia conversaciones en quechua de casa a casa o de gente que camina por las calles, a media calle está el mercado central donde se puede tomar desayuno a las cinco de la mañana.
Desde mi limitada y cómoda visión de turista curioso, me imagino así los días en Toro Toro y en tantos pueblos de Bolivia. Gente que se levanta antes que salga el sol, directo a trabajar en sus parcelas de tierra donde hay un sembradío que cuidan a diario para cosechar y subsistir, si sobra sirve para vender o cambiar. Su trabajo les permite recoger lo que la tierra provee. Así sin descanso aparente trabajan, después de todo se ven sus niñas y niños flaquitos caminando, algunos atendiendo la tienda, otros jugando y guiando a algunas cabras, ovejitas o vacas. A menudo hay al menos un perro de apariencia desnutrida siguiéndoles a trote constante y olfateando el suelo por si encuentra algo más para comer.
## Algunos datos sobre la importancia de la agricultura familiar
La [agricultura familiar](https://es.wikipedia.org/wiki/Agricultura_familiar) se define como una práctica agrícola donde el trabajo es realizado por familias que buscan principalmente su propio auto-abastecimiento. En Bolivia se realiza principalmente en áreas rurales o «el campo». A nivel mundial se estima que la agricultura familiar emplea al 30% de la población mundial y produce más del 80% de los alimentos del mundo en términos de valor [[1]](https://www.fao.org/family-farming-engagement/es), en Bolivia la situación es muy similar vamos a citar algunos datos.
Primero las Unidades Productivas Agropecuarias (UPA) se clasifican en **familiar** y **no familiar**. A su vez las UPA familiares se clasifican en [[2]](https://cipca.org.bo/publicaciones-e-investigaciones/cuadernos-de-investigacion/contribucion-de-la-agricultura-familiar-campesina-indigena-a-la-produccion-y-consumo-de-alimentos-en-bolivia):
- [De subsistencia](https://es.wikipedia.org/wiki/Agricultura_de_subsistencia): 47% de las UPA.
- **De transición**: 26% de las UPA. En esta los productores emplean mano de obra familiar con menor intensidad que la de subsistencia y aunque su acceso a la tierra es limitado, el rendimiento promedio es superior.
- **Consolidadas**: 27% de las UPA. Se usa mano de obra familiar en menor proporción, se caracteriza porque gran parte de su producción se destina a la venta. La tecnificación y vínculo con el mercado se acrecenta.
- En Bolivia el 96% de las UPA son familiares y el 4% no familiares.
Un estudio del CIPCA actualizado en 2021 sobre la contribución de la agricultura familiar campesina a la producción y consumo de alimentos en Bolivia [[2]](https://cipca.org.bo/publicaciones-e-investigaciones/cuadernos-de-investigacion/contribucion-de-la-agricultura-familiar-campesina-indigena-a-la-produccion-y-consumo-de-alimentos-en-bolivia) informa:
- El 61% del **volumen total de producción de alimentos** a nivel nacional corresponde a la agricultura familiar, mientras que el 39% restante proviene de la agricultura no familiar.
- El 86% del volumen total de producción lo aporta la agricultura consolidada y la de transición.
- La agricultura de subsistencia aporta el 14% del volumen total de producción. Esto tiene sentido ya que las pequeñas familias campesinas de escasos recursos tienen dificultades para producir en grandes volúmenes.
- La agricultura familiar cubre el **95%** del total de mano de obra agrícola. Para 2015 esto se traducía en 3.064 millones de trabajadores(as). Este es un dato fundamental pues la generación de empleos es una de las mejores formas de distribución de riqueza. Esto muestra que la agricultura familiar y por tanto gran parte del campesinado —reparte sus riquezas— como un río que fluye y riega campos abiertos dejando vida a su paso.
- La agricultura **no familiar** solamente emplea al 5% del total de mano de obra agrícola. Para 2015 un aproximado de 163.8 mil trabajadores(as) —una diferencia abismal con la agricultura familiar— y una muestra de que este tipo de agricultura casi no reparte riquezas a través de fuentes laborales, y quizá se enfoca en concentrar los excedentes en manos de sus propietarios(as).
- La participación, prácticas solidarias y de reciprocidad son del 70% en la agricultura de subsistencia y 80% en la consolidada. Estos altos porcentajes quieren decir que la gran mayoría de las familias colaboran entre sí, y forman una comunidad valiosa que les permite afrontar retos y dificultades.
- A 2024 el **27.8%** de la población boliviana trabaja en el sector *Agricultores, pecuarios, forestales, acuicultores y pesqueros*, lo que lo convierte en el sector ocupacional más grande seguido de *Trabajadores de los servicios y vendedores* con 23% [[3]](https://cpv2024.ine.gob.bo/index.php/resultados/sociales-y-economicas/resultados-caracteristicas-soc-eco-condicion-de-actividad/).
**Superficie cultivada** y mano de obra por tipo de agricultura:
<table>
<tr>
<td>Tipología</td>
<td>Superficie total cultivada</td>
<td>Total de la mano de Obra agrícola familiar que emplea</td>
<td>Total de la mano de Obra agrícola contratada que emplea</td>
</tr>
<tr>
<td>Agricultura no familiar</td>
<td>33%</td>
<td>2%</td>
<td>9%</td>
</tr>
<tr>
<td>Agricultura familiar consolidada</td>
<td>26%</td>
<td>24%</td>
<td>63%</td>
</tr>
<tr>
<td>Agricultura familiar de transición</td>
<td>24%</td>
<td>24%</td>
<td>22%</td>
</tr>
<tr>
<td>Agricultura familiar de subsistencia</td>
<td>47%</td>
<td>50%</td>
<td>6%</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
La anterior tabla muestra que la mayor parte de la superficie cultivada la utiliza la agricultura familiar. Sin embargo si comparamos la proporción de generación de empleos versus el tamaño de área usada, la agricultura no familiar usa mucho espacio y genera muy poco trabajo. Hablando de generación de empleos esto significa que más del 30% de la tierra agrícola usada por la agricultura no familiar como la agroindustria en Bolivia, se usa para beneficiar a un pequeño porcentaje de la población.
Veamos cómo son los volúmenes de producción por grandes regiones:
<table>
<tr>
<th>Región</th>
<th>Agricultura Familiar |</th>
<th>Agricultura no familiar</th>
</tr>
<tr>
<td>Los Valles</td>
<td>98.6 %</td>
<td>1.14 %</td>
</tr>
<tr>
<td>Altiplano</td>
<td>99.8 %</td>
<td>0.8 %</td>
</tr>
<tr>
<td>Gran Chaco</td>
<td>88.5 %</td>
<td>11.5 %</td>
</tr>
<tr>
<td>Llanos tropicales</td>
<td>52.4 %</td>
<td>48.6 %</td>
</tr>
<tr>
<td>Amazonía</td>
<td>77.8 %</td>
<td>22.2 %</td>
</tr>
</table>
La tabla anterior muestra que los volúmenes de producción de alimentos provienen en gran mayoría de la agricultura familiar en Los Valles, Altiplano, Gran Chaco y la Amazonía. En los llanos tropicales aproximadamente la mitad de los alimentos vienen de la agricultura no familiar y esto hace sentido al hecho de que la agroindustria y ganadería se concentran mayormente en departamentos como Santa Cruz y Beni.
### Incidencia en la canasta familiar
Entonces, ¿Qué sector de la agricultura en Bolivia tiene mayor aporte directo en la alimentación de la población?
He encontrado varias fuentes y opiniones contrarias al respecto, hay quienes afirman que la mayor parte del consumo de alimentos viene de la agricultura familiar y otros de la agroindustria. Mi tendencia es creer que ambos hacen aportes importantes aunque los estudios en favor de la agricultura familiar me parecen mejor elaborados. No encontré estudios rigurosos que analicen el aporte a la canasta básica, pero voy a citar algunos datos que he encontrado.
#### A favor de la agroindustria (agricultura no familiar)
- El 82 % de los alimentos que se consumen en Bolivia provienen de la Agroindustria, mientras que la agricultura campesina familiar provee el restante 18 % [[4]](https://boliviaemprende.com/noticias/agroindustria-provee-82-de-los-alimentos-bolivia).
- Más del 50 % del gasto en alimentos de los hogares proviene de productos producidos por la agroindustria. [[8]](https://idl-bnc-idrc.dspacedirect.org/server/api/core/bitstreams/dab3efb4-0215-4981-b3f2-30271c0485db/content)
#### A favor de la agricultura familiar (producción campesina)
- Este sector genera el 60% de la producción nacional; en contraste, la agricultura empresarial, concentrada principalmente en Santa Cruz y enfocada en commodities para exportación como soya, trigo y caña de azúcar, representa solo el 40%. Mientras que la primera prioriza la diversificación para el consumo interno, la segunda se orienta al mercado global [[5]](https://cipca.org.bo/analisis-y-opinion/cipca-notas/alimentando-bolivia-el-poder-nutricional-de-la-agricultura-familiar)
- El 98 % de los productos de la canasta básica alimentaria es proveída por la agricultura familiar [[6]](https://cipca.org.bo/docs/publications/es/258_desmitificando-la-agricultura-familiar-en-la-economia-rural-boliviana-caracterizacion-contribucion-e-implicaciones.pdf)
#### Contrastando
Parece que nuestro régimen alimentario es más complejo y que la agricultura familiar se sigue sosteniendo en condiciones precarias además su aporte a la seguridad alimentaria va en descenso. Sin embargo, el avance de la agricultura agroindustrial boliviana no necesariamente responde a la seguridad alimentaria del país sino a un mercado global de materias primas agrícolas.
La canasta básica alimentaria varía de acuerdo a regiones, por ejemplo en regiones del Altiplano y los Valles se consume papa en grandes cantidades, en cambio en los Llanos tropicales y la Amazonía el consumo es menor en favor de productos locales como yuca o plátanos. Generalizar de forma precisa podría ser un gran reto, pero hay alimentos comunes en casi todas las mesas como el arroz, papa, fideo, aceites, carne, verduras, cebolla. La mitad de estos proviene de la agroindustria y la otra de la agricultura campesina [[7]](https://www.ftierra.org/index.php/opinion-y-analisis/1011-la-agroindustria-o-la-pequena-produccion-familiar-quien-nos-alimenta).
La siguiente tabla estima el **porcentaje de gastos** en la canasta familiar según productos clave en gestiones 2015-2016 [[2]](https://cipca.org.bo/publicaciones-e-investigaciones/cuadernos-de-investigacion/contribucion-de-la-agricultura-familiar-campesina-indigena-a-la-produccion-y-consumo-de-alimentos-en-bolivia).
<table>
<tr>
<th>Clase de producto</th>
<th>Urbano</th>
<th>Rural</th>
<th>Bolivia</th>
</tr>
<tr>
<td>Carnes</td>
<td>32%</td>
<td>19%</td>
<td>29%</td>
</tr>
<tr>
<td>Pan y cereales</td>
<td>23%</td>
<td>24%</td>
<td>23%</td>
</tr>
<tr>
<td>Legumbres, hortalizas y otros</td>
<td>16%</td>
<td>22%</td>
<td>17%</td>
</tr>
<tr>
<td>Leche, queso y huevo</td>
<td>14%</td>
<td>11%</td>
<td>13%</td>
</tr>
<tr>
<td>Azúcar, mermelada, miel dulces</td>
<td>3%</td>
<td>4%</td>
<td>4%</td>
</tr>
<tr>
<td>Productos alimenticios sin especificar</td>
<td>3%</td>
<td>9%</td>
<td>4%</td>
</tr>
<tr>
<td>Frutas</td>
<td>6%</td>
<td>6%</td>
<td>6%</td>
</tr>
<tr>
<td>Aceites y grasas</td>
<td>2%</td>
<td>3%</td>
<td>2%</td>
</tr>
<tr>
<td>Pescado</td>
<td>2%</td>
<td>2%</td>
<td>2%</td>
</tr>
</table>
- Los mayores gastos familiares radican en carnes y pan/cereales, con 29% y 23%, respectivamente. La producción de carne y arroz viene principalmente de la agroindustria.
- Los gastos en frutas y verduras ocupan el 23%, estos productos vienen mayormente de la agricultura familiar.
- El restante 25% de los gastos recae en aceites, azúcares, leche, huevos, etc. Los aceites también vienen de la agroindustria así como el azúcar de los grandes ingenios azucareros. Aunque hay muchos productos como la leche que son producidos en cantidades importantes por el sector campesino.
Los gastos por tipo de productos muestran en qué invierte la población para alimentarse, pero esto depende mucho de los precios en el mercado —las carnes son mucho más costosas que las frutas, verduras o carbohidratos. Por tanto, para responder a la pregunta *¿qué sector alimenta más a la población?* se debería también considerar el volumen de consumo y también cuánto de estos porcentajes corresponde a alimentos importados o de contrabando.
No se puede negar la importancia de la agricultura familiar brindando alimentos frescos y al alcance de la población —debido a su precio accesible—, y de la no familiar con los productos mencionados anteriormente.
He intentado reunir datos que muestran la importancia de la agricultura familiar campesina, pero sé que me he quedado corto y que dependemos de la realización del censo agropecuario (esperado en 2026) para tener datos más actualizados.
## Falta de políticas que atiendan las necesidades del campesinado
Históricamente la mayor parte de Bolivia ha sido campesina. Incluso antes de que se llame "Bolivia", los pueblos vivían de la agricultura. Durante la colonización española estas comunidades indígenas fueron saqueadas y sus habitantes a quienes empezaron a llamar «los indios» fueron utilizados como esclavos para trabajar en las minas o como sirvientes de sus amos. Tras la fundación de la república, en pongueaje los utilizó como fuerza laboral para el patronaje a base de trabajo no remunerado.
Desde la revolución agraria de 1952, los campesinos tienen tierras, pero la sociedad racializada se resistía a aceptarlos, las duras condiciones y su falta de educación «occidental» les obligó a migrar masivamente a las ciudades donde se vieron forzados a sobrevivir dentro de las costumbres y adoctrinamientos de esa misma sociedad que los discriminaba a pesar de ser la importante fuerza laboral y social que entre otras cosas, trabajando la tierra ha seguido produciendo los alimentos para todo el país.
Este mismo racismo y discriminación sigue latente y también se refleja en la carencia de políticas públicas que atiendan las necesidades del indígena en Bolivia.
### Reconocimiento de incoherencias
Un dato importante viene del censo de población y vivienda en 2024 donde el 61.8 % de la población del área rural —está en condición de pobreza—, y esto contrasta con el área urbana con sólo el 15.8 %. En Bolivia el 29.8 % de la población aún está en condición de pobreza [[9]](https://cpv2024.ine.gob.bo/index.php/resultados/pobreza/resultados-pobreza-necesidades-basicas/). Sin embargo el 38.7 % de la población **se autoidentifica con alguna nación pueblo indígena originario campesino y afroboliviano**, esto es más de 4.3 millones de habitantes [[10]](https://cpv2024.ine.gob.bo/index.php/resultados/sociales-y-economicas/resultados-caracteristicas-soc-eco-autoidentificacion/).
La población rural ha tenido siempre menor acceso a [servicios básicos](https://www.ine.gob.bo/index.php/estadisticas-sociales/servicios-basicos/).
Aunque existen avances, el **acceso a la educación** en el área rural todavía es un problema importante, la tasa de asistencia escolar para niveles de secundaria alta es del 66.6 % vs. 79 % en el área urbana [[11]](https://siteal.iiep.unesco.org/pais/bolivia).
En el área rural la presencia estatal de la justicia, **acceso a servicios legales y seguridad ciudadana** es muy precaria. La policía, los fiscales y el poder judicial están limitados a las ciudades y pueblos, y su presencia en las zonas rurales es ocasional e infrecuente [[12]](https://www.oas.org/es/cidh/actividades/discursos/2023/03-31-visita-in-loco-bolivia.pdf). Gran parte de las áreas rurales no disponen de juzgados, en 2023 el presupuesto asignado a justicia a nivel nacional no pasó del 0.5 % del total del Estado y a 2022 el Servicio Plurinacional de Defensa Pública solo tiene capacidad para cubrir el 29 % de municipios y cada departamento sólo cuenta con una fiscalía [[13]](https://www.oas.org/es/cidh/actividades/discursos/2023/03-31-visita-in-loco-bolivia.pdf).
Con los juzgados casi inaccesibles ¿cuánto tiempo tomará resolver un litigio en el área rural?. Dada esta realidad no sorprende que los sindicatos campesinos sean la principal instancia de administración de justicia, y las autoridades tradicionales de las comunidades indígenas también tienen un papel destacado.
Se podrían seguir citando muchos otros datos desfavorables, pero veamos brevemente qué se estuvo haciendo respecto al desarrollo productivo para la clase campesina.
#### "El fondo indígena"
El Fondo Indígena o *Fondioc* se creó en 2005 y luego fue asumido por el mandato de Evo Morales desde 2006 a 2019. Su finalidad era la de gestionar y recibir recursos para proyectos de desarrollo en comunidades indígenas y campesinas. Este fondo recibía el 5 % del Impuesto directo a los hidrocarburos *IDH*.
A cargo de los ministerios, el fondo indígena tenía un directorio constituido por representantes de organizaciones sociales campesinas como CSUTCB, CONAMAQ, CIDOB y otras. Sin embargo en 2015 la contraloría general del estado descubrió una serie de irregularidades [[14]](https://repositorio.umsa.bo/bitstream/handle/123456789/21014/DAF-V-II%20029-2019%20%E2%80%9CIRREGULARIDADES%20EN%20LA%20ADMINISTRACI%C3%93N%20DEL%20FONDO%20DE%20DESARROLLO%20DE%20LOS%20PUEBLOS%20IND%C3%8DGENAS%20ORIGINARIAS%20Y%20CAMPESINAS%20(FDPPIOYCC)%E2%80%9D.pdf) entre ellas:
- No hubo proyectos estratégicos, la inversión pública se fragmentó en miles de proyectos.
- No se respetó los recursos indígenas que se iban acumulando en las cuentas del Fondo.
- No se respetó la autonomía en la gestión indígena. Se produjeron recortes para uso del Gobierno
Central.
- No se propició un uso justo de los recursos, de acuerdo a los más necesitados. Fueron entregados aproximadamente el 30% fue destinado a la CSUTCB, 29% a los Interculturales, 17% a las Bartolinas y el resto a las otras organizaciones indígenas: 14% a CONAMAQ, 6% a la CIDOB, 1% a la APG, 1% a la CPEMB, 2% a la CPESC.
- No se distribuyó a los que más necesitan, se distribuyó a quienes más presionan.
- Una enorme cantidad de recursos fue destinada a la realización de talleres y seminarios.
- Se hacían transferencias de recursos públicos a cuentas privadas (Unos 685 millones de Bs. a 978 cuentas particulares)
Todo estalló en un escandaloso y millonario desfalco con Nemesia Achacollo la ex ministra de Desarrollo Rural y Tierras como la principal implicada, siendo ella una de las personas cercanas al ex presidente Evo Morales.
Es muy lamentable que un proyecto destinado a impulsar el desarrollo de la clase social que era la promesa electoral del gobierno del Movimiento al Socialismo (MAS), haya sido víctima de la corrupción de este mismo partido. Lo más doloroso es quizá que la participación en los actos de corrupción vengan también de los mismos sectores dirigenciales indígenas y campesinos.
#### Agroindustria en Bolivia y el apoyo estatal
El modelo agroindustrial surgió en Bolivia como alternativa empresarial a la agricultura familiar campesina. En la década de los setenta el Movimiento Nacionalista Revolucionario (MNR) concluye la carretera Cochabamba-Santa Cruz y comienza el proceso de integración de los llanos Bolivianos con el resto del país y para ello se escogió un «modelo» de desarrollo basado en unidades empresariales agroindustriales: primero fue la caña de azúcar, luego el algodón y, finalmente, en los noventa se amplió a las oleaginosas y al ganado vacuno en torno a proyectos inicialmente promovidos por instituciones estatales y luego por iniciativa privada nacional e inversión extranjera. Sobre todo, en sus orígenes, este modelo creó un espacio a los migrantes colonizadores andinos (campesinos indígenas collas sin tierras), a quienes el Estado otorgó parcelas marginales de 50 hectáreas en el Oriente y precaria asistencia técnica y financiera. [[15]](https://www.ftierra.org/index.php/opinion-y-analisis/808-medio-siglo-de-la-agricultura-boliviana).
Bajo regímenes militares como el de Hugo Banzer, se intensificó el apoyo a la agroindustria con subsidios, créditos preferenciales y políticas de inversión en tecnología. Esto consolidó el "ciclo de apoyo estatal" para la expansión en Santa Cruz, impulsando oleaginosas como soya y girasol. La agroindustria se orientó hacia el mercado externo, aunque aún dependía de incentivos para viabilidad. En la década de los ochenta durante los gobiernos neoliberales, este sector fue el único que recibió subsidios directos e indirectos. En esta misma década la gran crisis inflacionaria y el D.S. 21060 del MNR fue devastador para los pequeños productores agrícolas que no pudieron competir con la libre importación y contrabando. Sin embargo, los medianos y grandes productores agrícolas pudieron consolidar su propiedades y adquirir nuevas con los créditos destinados al fomento de productos agroindustriales. [[16]](https://cipca.org.bo/docs/publications/es/228_mundos-rurales-15-web.pdf).
En los años 90s, la nueva Ley de Tierras 1715 garantiza el derecho propietario sobre la tierra, desde entonces y durante los años 2000, la agroindustria y ganadería extensiva se ha expandido masivamente a pesar de ser responsables del 50 % de la deforestación del país. Durante el gobierno del MAS hubo mayor intervención estatal en tierras para el apoyo a la producción familiar, pero no se dejó de apoyar a la agroindustria.
Al día de hoy, la agroindustria en Bolivia tiene pleno apoyo del gobierno de Rodrigo Paz y últimos datos brindados por este dicen que este sector representa el 17 % del Producto Interno Bruto (PIB) [[17]](https://larazon.bo/economia-y-empresa/2025/05/30/el-sector-agropecuario-representa-el-17-del-pib).
Desde 1996, hay un [impuesto anual de 8.57 Bs. por hectárea](https://www.lexivox.org/norms/BO-DS-24463.html) de tierra agropecuaria en Santa Cruz —menos de 1 USD—. En la práctica este aporte impositivo es simbólico.
Todos estos hechos me hacen preguntar ¿cómo se habría desarrollado la agricultura familiar/campesina si habría recibido el mismo apoyo estatal?
#### Insistiendo en el extractivismo agrario
La agroindustria —presente mayormente en Santa Cruz y Beni—, es un modelo agrario extractivista y [latifundista](https://es.wikipedia.org/wiki/Latifundio) (propiedad de grandes cantidades de tierras en pocas manos) según un trabajo publicado por la fundación tierra [[18]](https://www.ftierra.org/index.php/publicacion/libro/attachment/184/77), este modelo tiene como características:
- La producción responde a **imposiciones económicas** y políticas de países para garantizar su producción y exportación de materias primas.
- **Expansión masiva** de la nueva frontera agrícola y deforestación: Por esto Bolivia es uno de los países con mayor deforestación a nivel mundial.
- **Monocultivos**: La agricultura mecanizada requiere una virtual esterilización del ambiente donde un ecosistema hiperbiodiverso es reemplazado por un cultivo con unas cuantas especies vegetales.
- Uso extensivo de **insumos transgénicos**. La producción transgénica no aporta a la seguridad alimentaria ni armoniza con la soberanía alimentaria.
- Orientación **meramente exportadora**
- **Precariedad laboral**
- **Requiere políticas de apoyo estatal**: El gobierno implementa constantemente políticas favorables y desregulatorias como «perdonazos»; Ley 337, 502, 739. Autorizaciones de desmonte; D.S. 3973, Ley 1171. Autorización de procedimientos abreviados; D.S. 4232, etc.
- **Dependencia de agroquímicos**: Bolivia cada vez más importa agroquímicos y los monocultivos dependen mucho de estos para ser rentables. Muchos, como [el glifosato](https://es.greenpeace.org/es/trabajamos-en/agricultura/glifosato/) (principal producto usado por la agroindustria), son muy dañinos y causan muerte de especies nativas y desertificación del terreno.
- **Exclusión y marginación de los pequeños productores**: El agronegocio requiere grandes capitales, los pequeños productores se ven obligados a competir deslealmente o a abandonar el rubro migrando a las ciudades ensanchando la brecha de pobreza.
- **Oligopolios**: Tendencia al oligopolio y monopolio al ser sólo los grandes productores quienes acaparan el mercado.
Estas razones son suficientes para concluir que este modelo productivo es insostenible, prioriza la acumulación de riquezas en pocas manos a costa de la destrucción del medio ambiente, precarización y reducción de puestos laborales, dependencia en productos agroquímicos, control de precios y políticas fraudulentas.
El 2024 fue catastrófico por los [masivos incendios forestales](https://es.wikipedia.org/wiki/Incendios_forestales_en_Bolivia_de_2024) que afectaron un total de **10.1 millones de hectáreas**. Es bien sabido que los incendios forestales son principalmente causados por la necesidad de la expansión de la frontera agrícola para este modelo extractivista agrario.
> Bolivia no debe insistir en el extractivismo ya que beneficia mayormente a grupos reducidos, estos grupos **ganan poder** político y controlan medios de comunicación. Casi todos los excedentes o ganancias en Dólares que producen, ni siquiera circulan en el país y van a parar a cuentas en el extranjero. Lo que nos dejan, es una tierra destruida y mayores brechas entre ricos y pobres.
## Los efectos de medidas "anti populares" actuales
Todavía existe una política no favorable hacia los "sectores populares" y sin duda estos sectores incluyen a la clase campesina. Se cuestiona brevemente algunas de ellas a continuación.
### El «gasolinazo»
A finales de 2025 el gobierno lanzó un paquete de medidas económicas en un gran Decreto Supremo, el 5503, que poco después fue abrogado por presiones de movimientos sociales, muchos de ellos ligados al sector campesino agrario en Bolivia. Sin embargo, luego de esta abrogación se mantuvieron medidas como la eliminación de la subvención a los combustibles. Si bien esta medida era necesaria debido al gran gasto en Dólares Estadounidenses (USD) que estaba ahogando al estado boliviano, hay puntos cuestionables.
##### Se debió hacer un ajuste proporcional según los ingresos económicos
El precio de la gasolina se incrementó en más de 80% para toda la población, y esta medida tiene un efecto muy perjudicial en familias de escasos recursos porque encarece prácticamente todo, empezando de los alimentos cuyo transporte depende directamente del precio de los combustibles. Las «clases pudientes» pueden asumir un incremento sustancialmente mayor al 80% sin verse muy afectadas. A los sectores populares se podía aplicar un incremento menor, digamos un 30% para el transporte público, de esta manera se equilibran los efectos perjudiciales a la economía de cada familia según su nivel de ingresos.
En una sociedad donde el objetivo es el desarrollo conjunto, los sectores económicamente acomodados pueden colaborar con los menos beneficiados reduciendo sus ganancias, pero cuando cada sector busca sólo su propio beneficio, no tendrá la voluntad de hacer esta colaboración y juzgará esta medida como «parasitaria» sin considerar las abismales diferencias en condiciones de desarrollo entre las clases sociales. En la actualidad el ajuste lo pagan todos "por igual".
### Modificaciones a la ley de distribución de tierras
Rápida y 'eficientemente' la "Comisión de Tierra y Territorio, Recursos Naturales y Medio Ambiente" presidida por el empresario agroindustrial Branko Marinkovic, aprobó en el senado el Proyecto de Ley 157.
Con el pretexto de facilitar el camino a los pequeños productores agrícolas para acceder a créditos, este proyecto propone que la pequeña propiedad agraria se pueda transformar en mediana propiedad a simple petición del dueño evadiendo el control y fiscalización del estado. En Bolivia la pequeña propiedad agraria es considerada un patrimonio familiar y es inembargable, pero si se convierte en mediana propiedad deja de tener esta protección [[19]](https://www.cipca.org.bo/analisis-y-opinion/articulos-de-opinion/la-financiarizacion-del-despojo-la-ley-157-y-la-conversion-agraria-de-la-pequena-propiedad).
Este proyecto de Ley estaría dando luz verde para que los pequeños productores agropecuarios consigan créditos a cambio de hipotecar sus tierras, entonces en caso de no poder pagar la deuda, sus tierras pasarán a manos de los acreedores.
Se facilitan los mecanismos de «acumulación por desposesión», es decir los grandes capitalistas tendrán más facilidad de acumular más tierras en Bolivia ya que estos tienen dinero para ofrecer créditos pidiendo las tierras como garantía. En un escenario de crisis climática y endeudamiento, la opción de hipotecar la tierra puede devenir en la única salida para el productor, culminando en la pérdida de su propiedad por ejecución bancaria.
En casos extremos como en Paraguay, la liberalización del mercado de tierras ha resultado en que aproximadamente el “83% de la tierra está en manos del 3% de propietarios” [[19]](https://www.cipca.org.bo/analisis-y-opinion/articulos-de-opinion/la-financiarizacion-del-despojo-la-ley-157-y-la-conversion-agraria-de-la-pequena-propiedad).
En lugar de premiar o incentivar actividades agrarias agroforestales, agroecológicas y de producción diversificada que son base para la soberanía alimentaria, y que tendrían un impacto positivo y multiplicador directo en la clase campesina, se facilita el camino hacia el modelo agroindustrial donde —la tierra es una mera mercancía— y se implanta una idea de desarrollo ilusoria.
### Ley «antibloqueos»
Aunque a febrero de 2026 es un proyecto de ley 'abandonado', ilustra la intencionalidad de criminalizar este tipo de protesta a la que una parte de la población (mayormente citadina) rechaza, y no por irracionalidad. Los bloqueos de caminos pueden ser muy perjudiciales para la economía, salud, comunicación y seguridad del país.
Sin embargo, no hace falta pensar mucho para ver porque el movimiento campesino históricamente ha usado esta forma de protesta. Para que el estado atienda demandas urgentes constantemente descuidadas, quizá la única forma efectiva que tiene la población rural es **bloquear caminos o vías de comunicación hacia la ciudad**. La otra sería insistir por otros medios: mandar cartas, esperar respuesta, solicitar apoyo, etc. Todo eso está a merced de la burocracia, la corrupción y la pobre presencia estatal, y condena al olvido a las comunidades campesinas.
Ante esta dura realidad no se puede simplemente criminalizar esta forma de protesta —sin brindar otros mecanismos— que permitan atender demandas urgentes.
## Reflexión final — El campesinado, la clase trabajadora que sostiene al país
La clase campesina, así con su precariedad, es seguramente la fuerza de trabajo más importante de Bolivia, pues su aporte constante con la alimentación de toda la población es innegable. Los principios de colaboración comunitaria son las bases de su resiliencia y es lo que ha sostenido al país.
A lo largo de nuestra historia y en condiciones adversas, han salido en primera fila a defender la soberanía y recursos naturales más que cualquier sector de la población. Han aprendido a organizarse y siguen ahí, resistiendo la indiferencia, discriminación y abusos de una sociedad que no les reconoce lo suficiente, y que ciertos sectores de esta preferiría hacer desaparecer.
En Bolivia estamos por definir **a qué apostamos** para generar riqueza, desarrollo económico y justicia social. Podríamos seguir apostando al extractivismo, agroindustria, endeudamiento externo y seguir el camino que nos señalan economistas y analistas políticos que sólo hablan de riqueza como sinónimo de dinero en las ciudades. Hablando como si Bolivia fuese sólo eso, ciudades y centros urbanos desesperados de riqueza material desenfrenada, hablando como si no existiera el campo y la clase campesina obrera. Así hablan de modelos de desarrollo que ensanchan las brechas sociales, ajenos a la realidad, pues toda esa riqueza que tanto pregonan es sostenida en gran medida por el trabajo de la clase campesina.
Entonces, ¿Porqué no reconocer al fin las necesidades del área rural y todos sus habitantes? ¿Porqué no apostar por el desarrollo desde ahí, desde «abajo», desde las bases que sostienen al país? ¿Por Qué seguir con las incoherencias del modelo de desarrollo actual?
Con esta reflexión espero aportar para pensar en el campesinado con más agradecimiento y apoyar sus reivindicaciones. También para que pienses en el trabajo que cuesta hacer que puedas morder un dulce durazno, saciar tu hambre con papa, yuca, plátano o quinua. Cuando comas maní o disfrutes de queso criollo y te pongas llajua sazonada con sal, quirquiña o huacataya y sientas el sabor de tu fruta o verdura favorita, de los productos que crecen en la tierra y que se alimentan de luz solar y se refrescan con agua.
Cuando veas la gran diversidad de alimentos y su precio accesible en los mercados de Bolivia y te des cuenta que todo este tiempo ha sido gracias a esas personas que día a día se levantan a trabajar la tierra desde la madrugada.

<small><small>Mercado en la ciudad de Sucre en bolivia. Foto de <a href="https://www.flickr.com/photos/rastachango/">Cristian Ordenes</a>.</small></small>
## Referencias utilizadas
1.- [Labor de la FAO en relación con la agricultura familiar](https://www.fao.org/family-farming-engagement/es)
2.- [Contribución de la Agricultura Familiar Campesina Indígena a la producción y consumo de alimentos en Bolivia](https://cipca.org.bo/publicaciones-e-investigaciones/cuadernos-de-investigacion/contribucion-de-la-agricultura-familiar-campesina-indigena-a-la-produccion-y-consumo-de-alimentos-en-bolivia)
3.- [Censo de población y vivienda 2024 Bolivia - Características económicas](https://cpv2024.ine.gob.bo/index.php/resultados/sociales-y-economicas/resultados-caracteristicas-soc-eco-condicion-de-actividad/)
4.- [ Agroindustria provee 82% de los alimentos a Bolivia - Bolivia Emprende](https://boliviaemprende.com/noticias/agroindustria-provee-82-de-los-alimentos-bolivia)
5.- [Alimentando Bolivia: El poder nutricional de la agricultura familiar](https://cipca.org.bo/analisis-y-opinion/cipca-notas/alimentando-bolivia-el-poder-nutricional-de-la-agricultura-familiar)
6.- [eDsmitificando la agricultura familiar en la economía rural boliviana: caracterización, contribución e implicaciones](https://cipca.org.bo/docs/publications/es/258_desmitificando-la-agricultura-familiar-en-la-economia-rural-boliviana-caracterizacion-contribucion-e-implicaciones.pdf)
7.- [La agroindustria o la pequeña producción familiar ¿Quién nos alimenta?](https://www.ftierra.org/index.php/opinion-y-analisis/1011-la-agroindustria-o-la-pequena-produccion-familiar-quien-nos-alimenta)
8.- [Marginalización de la agricultura campesina e indígena Dinámicas locales, seguridad y soberanía alimentaria](https://idl-bnc-idrc.dspacedirect.org/server/api/core/bitstreams/dab3efb4-0215-4981-b3f2-30271c0485db/content)
9.- [Resultados Censo de población y vivienda 2024 - CONDICIÓN DE NECESIDADES BÁSICAS INSATISFECHAS](https://cpv2024.ine.gob.bo/index.php/resultados/pobreza/resultados-pobreza-necesidades-basicas/)
10.- [Resultados Censo de población y vivienda 2024 - AUTOIDENTIFICACIÓN](https://cpv2024.ine.gob.bo/index.php/resultados/sociales-y-economicas/resultados-caracteristicas-soc-eco-autoidentificacion/)
11.- [Instituto Internacional de Planeamiento de la Educación de la UNESCO en América Latina y el Caribe. Año 2023](https://siteal.iiep.unesco.org/pais/bolivia)
12.- [Monitor global de pluralismo Bolivia 2023](https://monitor.pluralism.ca/wp-content/uploads/2023/07/GPM-Report-Bolivia-ES-03.pdf)
13.- [Observaciones preliminares de la visita in loco a Bolivia – CIDH/OEA (31 de marzo de 2023)](https://www.oas.org/es/cidh/actividades/discursos/2023/03-31-visita-in-loco-bolivia.pdf)
14.- [IRREGULARIDADES EN LA ADMINISTRACIÓN DEL FONDO DE DESARROLLO DE LOS PUEBLOS INDÍGENAS ORIGINARIAS Y
CAMPESINAS (FDPPIOYCC) - Monografía](https://repositorio.umsa.bo/bitstream/handle/123456789/21014/DAF-V-II%20029-2019%20%E2%80%9CIRREGULARIDADES%20EN%20LA%20ADMINISTRACI%C3%93N%20DEL%20FONDO%20DE%20DESARROLLO%20DE%20LOS%20PUEBLOS%20IND%C3%8DGENAS%20ORIGINARIAS%20Y%20CAMPESINAS%20(FDPPIOYCC)%E2%80%9D.pdf)
15.- [Medio siglo de agricultura boliviana](https://www.ftierra.org/index.php/opinion-y-analisis/808-medio-siglo-de-la-agricultura-boliviana)
16.- [Impactos del modelo productivo agroindustrial en Bolivia - Mundos rurales](https://cipca.org.bo/docs/publications/es/228_mundos-rurales-15-web.pdf)
17.- [El sector agropecuario representa el 17% del PIB](https://larazon.bo/economia-y-empresa/2025/05/30/el-sector-agropecuario-representa-el-17-del-pib)
18.- [Extractivismo agrario. Dinámicas de poder, acumulación y exclusión en Bolivia - Ben M. MacKay, Fundación Tierra](https://www.ftierra.org/index.php/publicacion/libro/184-extractivismo-agrario-dinamicas-de-poder-acumulacion-y-exclusion-en-bolivia)
19.- [La financiarización del despojo: la ley 157 y la conversión agraria de la pequeña propiedad](https://www.cipca.org.bo/analisis-y-opinion/articulos-de-opinion/la-financiarizacion-del-despojo-la-ley-157-y-la-conversion-agraria-de-la-pequena-propiedad)