Discusión

Planificador de Horario de Servicio de Mantenimiento de Automóviles

De Wikiprompt, la enciclopedia libre de prompts

Fatih Kadir Akın

4 nov 2026

Planificador de Horario de Servicio de Mantenimiento de Automóviles Una habilidad completa al estilo Claude con un prompt estructurado, documentos de referencia, plantillas, ejemplos y un script de Python de la biblioteca estándar que planifica el mantenimiento del automóvil mediante el seguimiento de intervalos de servicio e historial, agrupando viajes de trabajo y pronosticando costos.

Contenido del PromptGuardar

🌐
--- name: planificador-horario-servicio-mantenimiento-coche description: Planifica el mantenimiento del coche a partir de su horario de servicio e historial de servicio - estima cuánto conduces a partir de las lecturas del cuentakilómetros, calcula qué está vencido, qué vence pronto o qué vence después por distancia o tiempo (lo que ocurra primero), agrupa los elementos en menos visitas al taller y pronostica los costes con una reserva mensual. Señala los elementos de seguridad y legales vencidos, los registros faltantes y los errores tipográficos del cuentakilómetros. Incluye un planificador Python estándar sin conexión. --- # Planificador de Horario de Servicio de Mantenimiento de Coche Ayudas a los propietarios de coches a mantenerse al día con el mantenimiento sin pagar de más. La gente pierde el rastro porque cada elemento funciona con su propio reloj: aceite cada 15.000 km o 12 meses, líquido de frenos cada 2 años independientemente de la distancia, la prueba de aptitud para circular cada año, una correa de distribución a los 120.000 km u 8 años. Siempre hay algo a punto de vencer, los recibos están dispersos y los artículos caros llegan como una sorpresa. Conviertes el horario del propietario y su historial de servicio en una lista clara de lo que vence, un plan de visita breve que agrupa los elementos en el menor número posible de viajes al taller, y una previsión de costes que puedan presupuestar. Planificas a partir de los intervalos que te da el propietario (del manual del propietario, el libro de servicio o el concesionario). No diagnosticas averías ni decides si una pieza está desgastada; dile al usuario que siga los hallazgos del taller y el horario del fabricante, y que haga revisar de inmediato las luces de advertencia, ruidos, problemas de frenos o dirección. ## Archivos en esta habilidad - `scripts/car_service_plan.py` - lee un JSON de coche (vehículo, cuentakilómetros actual, elementos del horario con intervalos de km y/o meses, intervalos de servicio severo, retraso de la primera prueba, coste, indicadores de seguridad/legal, alias de nombres) y un CSV de registro de servicio (fecha, cuentakilómetros, elementos separados por ";", coste, taller); estima la distancia anual a partir de las lecturas, encuentra la última vez que se hizo cada elemento, calcula la próxima fecha de vencimiento por tiempo y por distancia proyectada, estados VENCIDO / VENCE PRONTO / OK, visitas al taller agrupadas en el horizonte y costes por trimestre; informa de los hallazgos CS01-CS09 (solo biblioteca estándar de Python 3.8+) - `references/intervals-and-severe-service.md` - cómo leer el horario del fabricante, tiempo vs distancia, condiciones de servicio severo, notas típicas de elementos y qué no posponer nunca - `references/checks-and-codes.md` - cada código de hallazgo, su gravedad y qué hacer, además de los campos de entrada - `templates/service-plan.md` - el plan de una página y la lista de verificación para llevar al taller - `examples/car.json` - un coche familiar de gasolina de 2019 a 83.400 km con 11 elementos de horario (intervalos y precios ilustrativos - reemplázalos con los de tu manual) - `examples/service-log.csv` - siete años de historial de servicio con visitas combinadas, un alias ("Cambio de aceite"), un elemento que no está en el horario y un error tipográfico del cuentakilómetros - `examples/service-plan.md` - la salida real del planificador en el ejemplo ## Flujo de trabajo ### 1. Recopila el horario y el historial Pregunta por la marca, modelo, motor y fecha de primera matriculación, la lectura actual del cuentakilómetros, y el horario de mantenimiento del manual del propietario o libro de servicio (elemento, intervalo de distancia, intervalo de tiempo, intervalos de servicio severo si conducen principalmente trayectos cortos, remolcan, o conducen en polvo o calor o frío extremos). Pregunta por el historial de servicio: facturas, sellos en el libro de servicio o la impresión del concesionario, con fecha, cuentakilómetros y lo que se hizo. Añade precios aproximados de las últimas facturas de su taller. Escríbelos en un JSON de coche como `examples/car.json` y un registro como `examples/service-log.csv`. ### 2. Ejecuta el planificador ``` python3 scripts/car_service_plan.py examples/car.json examples/service-log.csv python3 scripts/car_service_plan.py car.json log.csv --odometer 84100 --today 2026-11-20 python3 scripts/car_service_plan.py car.json log.csv --severe --horizon 36 python3 scripts/car_service_plan.py car.json log.csv --json python3 scripts/car_service_plan.py --self-test ``` Salida 0 = ningún elemento de seguridad o legal vencido, 1 = un elemento de seguridad/legal está vencido (o el registro tiene un hallazgo alto), 2 = entrada incorrecta. Sin un shell, para cada elemento toma la última fecha y cuentakilómetros, añade ambos intervalos, convierte la distancia a una fecha con la distancia anual, y toma la anterior. ### 3. Lee el resultado - Alto primero: CS01 alto (frenos vencidos, líquido de frenos, neumáticos, prueba legal) - conduce con cuidado y reserva ahora; CS04 alto (fechas en el futuro, lecturas por encima de la de hoy) - corrige el registro. - Medio: otros elementos vencidos, elementos que vencen pronto, elementos sin registro (CS03 - contado desde nuevo, así que revisa el libro de servicio antes de pagar por ellos) y lecturas del cuentakilómetros que van hacia atrás. - Bajo/info: artículos de gran valor dentro del plan (CS08), elementos hechos mucho antes de su intervalo (CS05), entradas de registro que no están en el horario, intervalos severos en uso. - Si la distancia anual parece incorrecta, revisa las lecturas; usa `--odometer` con la lectura de hoy. ### 4. Planifica las visitas Usa la tabla de visitas: reserva la visita 1 para todo lo vencido más lo que vence dentro de la ventana de agrupación, y pide al taller que presupueste toda la lista de una vez. Mueve un artículo barato unas semanas hacia adelante si ahorra un viaje, pero nunca retrases un elemento de seguridad o legal. Rellena `templates/service-plan.md` con las visitas, la lista de verificación para el taller y la reserva mensual. ### 5. Entrega Devuelve la lista de vencidos y próximos a vencer en palabras sencillas, el plan de visitas con fechas y costes estimados, la previsión de costes a 12 y 24 meses y la cantidad mensual a reservar, más preguntas para verificar (registros de elementos sin historial, el intervalo exacto para su motor, condiciones de garantía que requieran servicio del concesionario). ## Resultado de ejemplo Para el Octavia de 2019 a 83.400 km (unos 11.000 km al año) hay cuatro elementos vencidos: líquido de frenos (126 días, seguridad), el servicio de inspección y el filtro de aire (por tiempo) y la rotación de neumáticos (3.300 km), y la prueba de aptitud para circular vence el 20 de noviembre de 2026 - una visita ahora cubre los cinco por unos 410 EUR. No hay registro de la correa de distribución, así que se cuenta desde nuevo y vence por antigüedad en noviembre de 2027 (unos 680 EUR), y se proyectan neumáticos nuevos para junio de 2027 (unos 560 EUR). El mantenimiento en los próximos 24 meses asciende a unos 2.445 EUR, así que reserva unos 102 EUR al mes. La lectura de 4.790 km en septiembre de 2023 es un error tipográfico y se ignora. ## Reglas - Usa los intervalos del fabricante para el motor exacto; los valores de ejemplo son solo ilustrativos. - Lo que ocurra primero: un elemento vence en el límite anterior de su distancia y su tiempo, incluso para un coche que apenas se conduce. - Nunca sugieras posponer frenos, neumáticos, dirección, luces de advertencia o una prueba legal para ahorrar dinero o un viaje. - Un elemento sin registro es desconocido, no hecho: dilo y sugiere revisar el libro de servicio o preguntar al taller. - Los costes son estimaciones; dile al usuario que obtenga un presupuesto, y menciona las condiciones de garantía antes de sugerir un taller independiente. ## Intervalos y servicio severo ## De dónde vienen los intervalos - El manual del propietario o libro de servicio para tu modelo, motor y año de modelo exactos. El mismo modelo puede tener horarios diferentes para motores de gasolina, diésel e híbridos, y para regímenes de servicio "fijo" vs "flexible/de larga duración". - Muchos coches muestran el próximo servicio de aceite en el salpicadero (indicador de servicio). Si discrepa con el libro, pregunta al concesionario en qué régimen está configurado el coche. - Ten en cuenta las condiciones de garantía: algunos fabricantes requieren servicio según su horario con piezas de especificación original; los talleres independientes suelen estar bien si siguen el horario y guardan facturas. ## Lo que ocurra primero Cada elemento con un intervalo de distancia y tiempo vence en el ANTERIOR de los dos. Los coches de bajo kilometraje alcanzan el límite de tiempo (el aceite envejece, el líquido de frenos absorbe agua, las correas de goma y los neumáticos envejecen); los coches de alto kilometraje alcanzan el límite de distancia. El planificador proyecta el límite de distancia a una fecha con tu distancia anual. ## Servicio severo La mayoría de los manuales enumeran intervalos más cortos cuando el coche se usa principalmente para: - trayectos cortos (menos de unos 10 km, el motor rara vez se calienta por completo), especialmente en clima frío; - tráfico urbano de parar y arrancar, uso de taxi o reparto, ralentí prolongado; - remolcar un remolque o caravana, cargas en el techo, conducción de montaña; - carreteras polvorientas o arenosas, climas muy calurosos o muy fríos. Si alguna de estas describe la mayor parte de tu conducción, usa `--severe` (o `"severe": true`) para que los elementos con `severe_km` / `severe_months` usen el intervalo más corto. ## Notas de elementos (típicas, consulta tu manual) | Elemento | Base típica | Notas | |---|---|---| | Aceite de motor y filtro | distancia y tiempo | el elemento rutinario más importante; usa la especificación de aceite del manual | | Servicio de inspección | distancia y tiempo | el servicio más grande; normalmente incluye muchas comprobaciones | | Líquido de frenos | solo tiempo (a menudo 2 años) | absorbe agua; nunca lo pospongas | | Inspección de frenos / pastillas / discos | distancia y tiempo | reemplazar por desgaste, no en un intervalo fijo | | Refrigerante | tiempo o "toda la vida" | comprueba el nivel y la protección contra congelación cada invierno | | Filtro de aire, filtro de cabina | distancia y tiempo | más corto en áreas polvorientas o contaminadas | | Bujías (gasolina) | distancia y tiempo | intervalos largos en motores modernos | | Correa de distribución (si está equipada) | distancia y tiempo | la rotura puede destruir el motor; a menudo se cambia con la bomba de agua; las cadenas normalmente no tienen intervalo | | Neumáticos | desgaste y edad | comprueba la profundidad del dibujo mensualmente; muchos fabricantes sugieren reemplazar a los 6-10 años independientemente del dibujo | | Rotación de neumáticos | distancia | si tu manual lo recomienda (no para neumáticos delanteros/traseros de diferente tamaño) | | Prueba de aptitud para circular | tiempo (legal) | primera prueba después de unos años (por ejemplo, 3 o 4), luego cada 1-2 años dependiendo del país | | Batería de 12 V | condición | pruébala antes del invierno una vez que tenga 4-5 años | ## Pruebas legales Las reglas difieren según el país (MOT en el Reino Unido, TUV/HU en Alemania, CT en Francia, TUVTURK en Turquía, inspecciones estatales en los EE. UU.). Introduce el retraso de la primera prueba de tu país como `first_months` y el intervalo de repetición como `months`, y marca el elemento `"legal": true`. Muchos esquemas te permiten probar un poco antes sin perder la fecha; consulta el tuyo antes de agruparlo en una visita anterior. ## Qué no posponer nunca Frenos y líquido de frenos, neumáticos (dibujo, daños, edad), fallos de dirección y suspensión, luces de advertencia (presión de aceite, frenos, temperatura del motor), y la prueba legal. El planificador marca estos con `safety` / `legal` y los trata como altos cuando están vencidos. ## Obteniendo las entradas - Cuentakilómetros y fecha de cada visita: facturas, sellos del libro de servicio, el registro digital de servicio del concesionario, los certificados de la prueba legal (registran el cuentakilómetros). - Precios: tus últimas facturas o un presupuesto; añade un 10-20% para piezas que a menudo se reemplazan al mismo tiempo (limpiaparabrisas, bombillas, pastillas de freno). ## Comprobaciones y códigos de hallazgo | Código | Gravedad | Comprobación | Qué hacer | |---|---|---|---| | CS01 | alto: elemento de seguridad o legal vencido; medio: otro elemento vencido | fecha de vencimiento o distancia de vencimiento pasada | resérvalo; para elementos de seguridad conduce con cuidado hasta que se haga | | CS02 | medio | vence dentro de `due_soon.days` o `due_soon.km` | resérvalo con la próxima visita | | CS03 | medio | sin registro del elemento en el registro: contado desde la primera matriculación | revisa el libro de servicio / recibos / registro del concesionario antes de pagar por ello | | CS04 | alto: fecha en el futuro, lectura por encima del cuentakilómetros actual; medio: lectura inferior a una anterior, cuentakilómetros ilegible | consistencia del registro | corrige el error tipográfico; la lectura se ignora para la distancia y los cálculos de vencimiento | | CS05 | info | última vez hecho a menos del 60% de ambos intervalos | está bien si es intencionado (uso severo, garantía); de lo contrario, dinero gastado temprano | | CS06 | info | intervalos de servicio severo en uso | comprueba que las condiciones sigan aplicándose | | CS07 | info | elemento del registro que no está en tu horario (reparaciones, extras) | se mantiene para el historial; añade alias si es un elemento del horario con otro nombre | | CS08 | bajo | elemento que cuesta 400 o más cae dentro del horizonte del plan | presupuesta para ello; obtén un presupuesto temprano | | CS09 | medio | no hay suficientes lecturas del cuentakilómetros (una lectura de al menos 60 días atrás): se usa la distancia anual predeterminada | añade lecturas (certificados de prueba, aplicación de combustible, fotos del salpicadero) | ## Veredicto - **Vencido incluyendo seguridad/legal**: reserva ahora. - **Vencido**: reserva pronto. - **Vence pronto** / **al día**. ## Cómo se calculan los números - Distancia anual: desde la última lectura del registro de al menos un año antes de hoy hasta la lectura de hoy (la lectura más temprana si ninguna es tan antigua). - Próximo vencimiento: última fecha de realización + meses, y último cuentakilómetros de realización + km, convertido a una fecha con tu distancia diaria; el anterior gana. Un elemento sin registro cuenta desde la primera matriculación a 0 km (`first_months` reemplaza `months` para la primera vez, por ejemplo, una primera prueba legal después de 36 meses). - Horizonte: cada elemento se repite desde la fecha en que se asume hecho (su fecha de vencimiento, u hoy si está vencido). - Visitas: los elementos que vencen dentro de `bundle.days` y `bundle.km` del primer elemento de una visita se unen a esa visita (atraídos hacia adelante, nunca empujados hacia atrás). - Costes: el `cost` de cada ocurrencia se añade a su trimestre; reserva mensual = total del horizonte / meses del horizonte. ## Claves JSON del coche `vehicle{name, first_registered, unit}, odometer{reading, date}, driving{default_per_year, severe, severe_reasons}, currency, horizon_months, due_soon{km, days}, bundle{km, days}, schedule[{item, km, months, severe_km, severe_months, first_months, cost, safety, legal, aliases[]}]`. ## CSV del registro de servicio `date, odometer, item, cost, shop, notes` - varios elementos en una visita separados por `;`. Los nombres se comparan con los elementos del horario y los alias sin distinguir mayúsculas de minúsculas. La columna de coste es para tus registros; la previsión usa los precios del horario. ## Plantilla # Plan de servicio del coche: <coche, matriculación> **Fecha:** <AAAA-MM-DD> **Cuentakilómetros:** <83.400 km> **Distancia anual:** <unos 11.000 km> **Horario:** <manual del propietario, servicio normal / severo> ## Haz ahora | Elemento | Por qué | Coste est. | |---|---|---| | <Líquido de frenos> | <vencido por 4 meses (intervalo de 2 años) - seguridad> | <85> | | <Prueba de aptitud para circular> | <vence 2026-11-20 - legal> | <60> | ## Próximas visitas | Cuándo (reservar antes de) | A unos | Elementos | Coste est. | |---|---|---|---| | <2027-02-14> | <86.500 km> | <aceite y filtro, inspección de frenos> | <180> | ## Presupuesto - Próximos 12 meses: <1.220> <EUR> - Próximos 24 meses: <2.445> <EUR> -> reserva <102> al mes - Grandes elementos próximos: <kit de correa de distribución y bomba de agua, nov 2027, unos 680> ## Para comprobar - [ ] <sin registro de la correa de distribución - revisa el libro de servicio / pregunta al concesionario> - [ ] <intervalo exacto del líquido de frenos para este motor> - [ ] <garantía: ¿requiere servicio del concesionario?> ## Lista de verificación para el taller - Coche: <marca, modelo, motor, VIN si se pide>, cuentakilómetros <...> - Por favor, haz: <lista de "Haz ahora"> - Por favor, comprueba y presupuesta antes de reemplazar: <pastillas/discos de freno, neumáticos, limpiaparabrisas, batería> - Por favor, sella el libro de servicio / envía un registro digital y anota el cuentakilómetros en la factura. *Intervalos de <fuente>; los costes son estimaciones. Haz revisar de inmediato cualquier cosa inusual (luces de advertencia, ruidos, tirones, vibraciones).* ## Ejemplo: car.json { "vehicle": {"name": "2019 Skoda Octavia Estate 1.5 TSI (gasolina)", "first_registered": "2019-11-14", "unit": "km"}, "odometer": {"reading": 83400, "date": "2026-11-04"}, "driving": {"default_per_year": 12000, "severe": false, "severe_reasons": ["principalmente trayectos cortos en invierno", "remolque ocasional de caravana"]}, "currency": "EUR", "horizon_months": 24, "due_soon": {"km": 1500, "days": 45}, "bundle": {"km": 2500, "days": 60}, "schedule": [ {"item": "Aceite de motor y filtro", "km": 15000, "months": 12, "severe_km": 7500, "severe_months": 6, "cost": 140, "aliases": ["servicio de aceite", "cambio de aceite"]}, {"item": "Servicio de inspección", "km": 30000, "months": 24, "severe_km": 20000, "cost": 190, "aliases": ["inspección"]}, {"item": "Inspección de frenos", "km": 20000, "months": 12, "cost": 40, "safety": true}, {"item": "Líquido de frenos", "months": 24, "cost": 85, "safety": true}, {"item": "Filtro de aire", "km": 60000, "months": 48, "severe_km": 30000, "cost": 45}, {"item": "Filtro de cabina", "km": 30000, "months": 24, "cost": 40, "aliases": ["filtro de polen"]}, {"item": "Bujías", "km": 60000, "months": 72, "cost": 160}, {"item": "Rotación de neumáticos", "km": 10000, "cost": 30}, {"item": "Neumáticos (juego de 4)", "km": 45000, "months": 72, "cost": 560, "safety": true}, {"item": "Kit de correa de distribución y bomba de agua", "km": 120000, "months": 96, "cost": 680}, {"item": "Prueba de aptitud para circular", "months": 12, "first_months": 36, "cost": 60, "legal": true, "aliases": ["mot", "inspección periódica"]} ] } ## Ejemplo: service-log.csv date,odometer,item,cost,shop,notes 2020-11-20,11500,Servicio de aceite; Inspección de frenos,165,Concesionario principal,primer servicio 2021-11-05,22800,Servicio de aceite; Inspección de frenos; Filtro de cabina,210,Concesionario principal, 2022-05-28,33900,Servicio de aceite; Servicio de inspección; Líquido de frenos; Filtro de aire,395,Concesionario principal, 2022-11-08,38000,Prueba de aptitud para circular,55,Centro de pruebas,aprobado 2023-06-15,45150,Servicio de aceite; Inspección de frenos; Rotación de neumáticos; Neumáticos (juego de 4),735,QuickFit,neumáticos nuevos 205/55 R16 2023-09-02,4790,Reparación de grieta en parabrisas,60,GlassFix, 2023-11-13,49000,Prueba de aptitud para circular,55,Centro de pruebas,aprobado 2024-07-01,56900,Servicio de aceite; Servicio de inspección; Líquido de frenos; Bujías; Inspección de frenos,520,Concesionario principal, 2024-11-12,59800,Prueba de aptitud para circular,58,Centro de pruebas,advertencia: limpiaparabrisas trasero 2025-08-20,70100,Servicio de aceite; Inspección de frenos; Filtro de cabina; Rotación de neumáticos,240,Taller de la esquina, 2025-11-20,73000,Prueba de aptitud para circular,60,Centro de pruebas,aprobado 2026-02-14,75300,Cambio de aceite; Inspección de frenos,150,Taller de la esquina,se encendió la luz del salpicadero ## Ejemplo: service-plan.md # Plan de servicio: 2019 Skoda Octavia Estate 1.5 TSI (gasolina) Hoy 2026-11-04, cuentakilómetros 83.400 km. Conduces unos 11.008 km/año (2025-08-20 70.100 -> 2026-11-04 83.400). Intervalos de servicio normal, horizonte del plan 24 meses. Fuente: examples/service-log.csv. **Veredicto: 4 elemento(s) vencido(s), incluyendo elementos de seguridad/legal - reserva una visita ahora** Próximos 12 meses: unos 1.220,00 EUR; próximos 24 meses: 2.445,00 EUR (reserva unos 101,88 EUR al mes). Hallazgos: 1 alto, 6 medios, 2 bajos, 3 info. ## Elementos | Elemento | Intervalo | Última vez | Vence por fecha | Vence a km | Próximo (proyectado) | Disparador | Estado | Coste est. | |---|---|---|---|---|---|---|---|---| | Servicio de inspección | 30.000 km / 24 meses | 2024-07-01 a 56.900 | 2026-07-01 | 86.900 | 2026-11-04 | tiempo | **VENCIDO** | 190,00 | | Líquido de frenos [seguridad] | 24 meses | 2024-07-01 a 56.900 | 2026-07-01 | - | 2026-11-04 | tiempo | **VENCIDO** | 85,00 | | Filtro de aire | 60.000 km / 48 meses | 2022-05-28 a 33.900 | 2026-05-28 | 93.900 | 2026-11-04 | tiempo | **VENCIDO** | 45,00 | | Rotación de neumáticos | 10.000 km | 2025-08-20 a 70.100 | - | 80.100 | 2026-11-04 | distancia | **VENCIDO** | 30,00 | | Prueba de aptitud para circular [legal] | 12 meses | 2025-11-20 a 73.000 | 2026-11-20 | - | 2026-11-20 | tiempo | **VENCE PRONTO** | 60,00 | | Aceite de motor y filtro | 15.000 km / 12 meses | 2026-02-14 a 75.300 | 2027-02-14 | 90.300 | 2027-02-14 | tiempo | OK | 140,00 | | Inspección de frenos [seguridad] | 20.000 km / 12 meses | 2026-02-14 a 75.300 | 2027-02-14 | 95.300 | 2027-02-14 | tiempo | OK | 40,00 | | Neumáticos (juego de 4) [seguridad] | 45.000 km / 72 meses | 2023-06-15 a 45.150 | 2029-06-15 | 90.150 | 2027-06-15 | distancia | OK | 560,00 | | Filtro de cabina | 30.000 km / 24 meses | 2025-08-20 a 70.100 | 2027-08-20 | 100.100 | 2027-08-20 | tiempo | OK | 40,00 | | Kit de correa de distribución y bomba de agua | 120.000 km / 96 meses | sin registro (contado desde nuevo, 2019-11-14) | 2027-11-14 | 120.000 | 2027-11-14 | tiempo | OK | 680,00 | | Bujías | 60.000 km / 72 meses | 2024-07-01 a 56.900 | 2030-07-01 | 116.900 | 2029-11-18 | distancia | OK | 160,00 | ## Visitas al taller (los elementos dentro de 60 días / 2.500 km se agrupan) | # | Reservar antes de | A unos | Elementos | Coste est. | |---|---|---|---|---| | 1 | 2026-11-04 (ahora) | 83.400 km | Filtro de aire (vencido), Líquido de frenos (vencido), Servicio de inspección (vencido), Rotación de neumáticos (vencido), Prueba de aptitud para circular | 410,00 | | 2 | 2027-02-14 | 86.476 km | Inspección de frenos, Aceite de motor y filtro | 180,00 | | 3 | 2027-06-15 | 90.125 km | Neumáticos (juego de 4) | 560,00 | | 4 | 2027-08-20 | 92.116 km | Filtro de cabina, Rotación de neumáticos | 70,00 | | 5 | 2027-11-14 | 94.710 km | Kit de correa de distribución y bomba de agua, Prueba de aptitud para circular | 740,00 | | 6 | 2028-02-14 | 97.484 km | Inspección de frenos, Aceite de motor y filtro | 180,00 | | 7 | 2028-08-27 | 103.365 km | Rotación de neumáticos | 30,00 | | 8 | 2028-11-04 | 105.446 km | Líquido de frenos, Servicio de inspección | 275,00 | ## Previsión de costes por trimestre | Trimestre | Coste est. | |---|---| | 2026-T4 | 410,00 | | 2027-T1 | 180,00 | | 2027-T2 | 560,00 | | 2027-T3 | 40,00 | | 2027-T4 | 770,00 | | 2028-T1 | 180,00 | | 2028-T3 | 30,00 | | 2028-T4 | 275,00 | | **Total 24 meses** | **2.445,00** | ## Hallazgos - **ALTO CS01** El líquido de frenos está vencido por 126 días (elemento de seguridad) - resérvalo ahora - **MEDIO CS01** El filtro de aire está vencido por 160 días - resérvalo ahora - **MEDIO CS01** El servicio de inspección está vencido por 126 días - resérvalo ahora - **MEDIO CS01** La rotación de neumáticos está vencida por 3.300 km - resérvala ahora - **MEDIO CS02** La prueba de aptitud para circular vence pronto: 2026-11-20 - **MEDIO CS03** Kit de correa de distribución y bomba de agua: sin registro en tu registro - contado desde nuevo; revisa el libro de servicio o los recibos - **MEDIO CS04** fila 7: cuentakilómetros 4.790 el 2023-09-02 es inferior a 45.150 el 2023-06-15 - ¿error tipográfico o coche equivocado? lectura ignorada - **BAJO CS08** Kit de correa de distribución y bomba de agua (unos 680,00) cae en el plan el 2027-11-14 - presupuesta para ello ahora - **BAJO CS08** Neumáticos (juego de 4) (unos 560,00) caen en el plan el 2027-06-15 - presupuesta para ello ahora - **INFO CS05** La inspección de frenos se hizo por última vez después de 5.200 km / 6 meses (menos del 60% del intervalo) - está bien para uso severo, de lo contrario dinero gastado temprano - **INFO CS05** El aceite de motor y filtro se hizo por última vez después de 5.200 km / 6 meses (menos del 60% del intervalo) - está bien para uso severo, de lo contrario dinero gastado temprano - **INFO CS07** fila 7: 'Reparación de grieta en parabrisas' no está en tu horario - se mantiene solo en el historial Los intervalos y precios provienen de tu archivo de coche: compruébalos contra el manual del propietario o libro de servicio para tu motor exacto, y sigue a tu taller si encuentra desgaste antes. Los costes son estimaciones; los neumáticos y frenos también dependen del desgaste, no solo de la distancia. ## Script de Python (car_service_plan.py) ```python #!/usr/bin/env python3 """Planificador de horario de servicio de mantenimiento de coche (solo biblioteca estándar, Python 3.8+). Lee el horario de mantenimiento de tu coche (elementos con intervalos de distancia y/o tiempo, del manual del propietario o libro de servicio) y tu historial de servicio (CSV), estima cuánto conduces a partir de las lecturas del cuentakilómetros en el registro, y calcula para cada elemento cuándo se hizo por última vez, cuándo vence a continuación (distancia o tiempo, lo que ocurra primero), qué está vencido o vence pronto, qué elementos pueden compartir una visita al taller, y cuánto costará el mantenimiento en los próximos meses. Uso: car_service_plan.py CAR.json SERVICE_LOG.csv [--odometer N] [--today AAAA-MM-DD] [--severe] [--horizon MESES] [--json] car_service_plan.py --self-test Salida: 0 = nada vencido en un elemento de seguridad/legal, 1 = un elemento de seguridad o legal está vencido (o el registro tiene un hallazgo alto), 2 = entrada incorrecta. """ import argparse, calendar, csv, datetime, io, json, sys SEV = {"high": 0, "medium": 1, "low": 2, "info": 3} CAR_KEYS = {"vehicle", "odometer", "driving", "currency", "horizon_months", "due_soon", "bundle", "schedule"} class InputError(Exception): pass def pdate(s, what): try: return datetime.date.fromisoformat(str(s).strip()) except ValueError: raise InputError("%s debe ser AAAA-MM-DD, se obtuvo %r" % (what, s)) def add_months(d, m): y, mo = divmod(d.month - 1 + int(m), 12) y += d.year mo += 1 return datetime.date(y, mo, min(d.day, calendar.monthrange(y, mo)[1])) def num(x, what): try: return float(str(x).replace(",", "").replace(" ", "")) except ValueError: raise InputError("%s no es un número: %r" % (what, x)) def fk(x): return "{:,.0f}".format(x) def fm(x): return "{:,.2f}".format(x) # ---------------------------------------------------------------- carga def load_car(cfg, odometer=None, today=None, severe=None, horizon=None): if not isinstance(cfg, dict): raise InputError("el archivo de coche debe ser un objeto JSON") bad = set(cfg) - CAR_KEYS if bad: raise InputError("clave(s) desconocida(s) en el archivo de coche: %s" % ", ".join(sorted(bad))) v = cfg.get("vehicle") or {} odo = cfg.get("odometer") or {} drv = cfg.get("driving") or {} c = {"name": v.get("name", "Coche"), "unit": v.get("unit", "km"), "first": pdate(v["first_registered"], "vehicle.first_registered") if v.get("first_registered") else None, "today": pdate(today or odo.get("date") or datetime.date.today().isoformat(), "today"), "odo": num(odometer if odometer is not None else odo.get("reading"), "lectura del cuentakilómetros") if (odometer is not None or odo.get("reading") is not None) else None, "default_per_year": num(drv.get("default_per_year", 12000), "driving.default_per_year"), "severe": bool(drv.get("severe", False)) if severe is None else severe, "severe_reasons": drv.get("severe_reasons", []), "currency": cfg.get("currency", ""), "horizon": int(horizon or cfg.get("horizon_months", 24)), "soon_km": num((cfg.get("due_soon") or {}).get("km", 1500), "due_soon.km"), "soon_days": int((cfg.get("due_soon") or {}).get("days", 45)), "bundle_km": num((cfg.get("bundle") or {}).get("km", 2500), "bundle.km"), "bundle_days": int((cfg.get("bundle") or {}).get("days", 60)), "items": []} if c["odo"] is None: raise InputError("da la lectura actual del cuentakilómetros (odometer.reading o --odometer)") for i, it in enumerate(cfg.get("schedule") or [], 1): name = it.get("item") if not name: raise InputError("el elemento del horario %d no tiene nombre 'item'" % i) km = it.get("severe_km") if c["severe"] and it.get("severe_km") else it.get("km") mo = it.get("severe_months") if c["severe"] and it.get("severe_months") else it.get("months") if not km and not mo: raise InputError("el elemento del horario '%s' necesita km y/o months" % name) c["items"].append({"item": name, "km": num(km, name + " km") if km else None, "months": int(mo) if mo else None, "first_months": int(it["first_months"]) if it.get("first_months") else None, "cost": num(it.get("cost", 0), name + " cost"), "safety": bool(it.get("safety")), "legal": bool(it.get("legal")), "severe_used": bool(c["severe"] and (it.get("severe_km") or it.get("severe_months"))), "aliases": [a.lower().strip() for a in it.get("aliases", [])] + [name.lower().strip()]}) if not c["items"]: raise InputError("el archivo de coche necesita un 'schedule' no vacío") return c def load_log(c, text, F): rows = list(csv.DictReader(io.StringIO(text))) if rows and not {"date", "odometer", "item"} <= set(rows[0]): raise InputError("el registro de servicio necesita columnas date, odometer, item (tiene %s)" % ", ".join(rows[0])) entries, readings = [], [] for n, r in enumerate(rows, 2): try: d = pdate(r.get("date"), "date (fila %d)" % n) except InputError as e: F.append(("high", "CS04", "fila %d: %s - fila ignorada" % (n, e))); continue if d > c["today"]: F.append(("high", "CS04", "fila %d: la fecha %s está en el futuro - fila ignorada" % (n, d))); continue odo = None if (r.get("odometer") or "").strip(): try: odo = num(r["odometer"], "odometer (fila %d)" % n) except InputError as e: F.append(("medium", "CS04", "%s - distancia ignorada para esta fila" % e)) items = [x.strip() for x in (r.get("item") or "").split(";") if x.strip()] entries.append({"row": n, "date": d, "odo": odo, "items": items, "cost": r.get("cost", ""), "shop": r.get("shop", "")}) entries.sort(key=lambda e: (e["date"], e["row"])) last = None for e in entries: if e["odo"] is None: continue if e["odo"] > c["odo"]: F.append(("high", "CS04", "fila %d: el cuentakilómetros %s el %s es mayor que la lectura actual %s - ¿error tipográfico? lectura ignorada" % (e["row"], fk(e["odo"]), e["date"], fk(c["odo"])))) e["odo"] = None; continue if last and e["odo"] < last["odo"]: F.append(("medium", "CS04", "fila %d: el cuentakilómetros %s el %s es inferior a %s el %s - ¿error tipográfico o coche equivocado? lectura ignorada" % (e["row"], fk(e["odo"]), e["date"], fk(last["odo"]), last["date"]))) e["odo"] = None; continue last = e readings.append((e["date"], e["odo"])) readings.append((c["today"], c["odo"])) return entries, readings def distance_rate(c, readings, F): """km por día desde la última lectura de al menos un año antes de hoy (si no, la más temprana).""" end_d, end_o = readings[-1] older = [r for r in readings[:-1] if (end_d - r[0]).days >= 365] start = older[-1] if older else (readings[0] if len(readings) > 1 else None) if not start or (end_d - start[0]).days < 60 or end_o <= start[1]: F.append(("medium", "CS09", "no hay suficientes lecturas del cuentakilómetros para estimar tu distancia anual - usando el valor predeterminado %s %s/año" % (fk(c["default_per_year"]), c["unit"]))) return c["default_per_year"] / 365.0, "predeterminado" rate = (end_o - start[1]) / float((end_d - start[0]).days) return rate, "%s %s -> %s %s" % (start[0], fk(start[1]), end_d, fk(end_o)) # ---------------------------------------------------------------- plan def plan(c, log_text): F = [] entries, readings = load_log(c, log_text, F) rate, basis = distance_rate(c, readings, F) known = {a: it for it in c["items"] for a in it["aliases"]} done = {it["item"]: [] for it in c["items"]} for e in entries: for name in e["items"]: it = known.get(name.lower()) if not it: F.append(("info", "CS07", "fila %d: '%s' no está en tu horario - se mantiene solo en el historial" % (e["row"], name))) continue done[it["item"]].append(e) def date_at_km(km): return c["today"] + datetime.timedelta(days=max(0.0, (km - c["odo"]) / rate)) if rate > 0 else None def km_at_date(d): return c["odo"] + rate * (d - c["today"]).days end = add_months(c["today"], c["horizon"]) rows, occ = [], [] for it in c["items"]: hist = done[it["item"]] last = hist[-1] if hist else None if last: base_d, base_km, base_note = last["date"], last["odo"], "%s a %s" % (last["date"], fk(last["odo"]) if last["odo"] is not None else "?") else: if not c["first"]: raise InputError("sin registro de '%s' y sin vehicle.first_registered para contar desde" % it["item"]) base_d, base_km, base_note = c["first"], 0.0, "sin registro (contado desde nuevo, %s)" % c["first"] F.append(("medium", "CS03", "%s: sin registro en tu registro - contado desde nuevo; revisa el libro de servicio o los recibos" % it["item"])) months = it["months"] if not last and it["first_months"]: months = it["first_months"] due_d = add_months(base_d, months) if months else None due_km = base_km + it["km"] if (it["km"] and base_km is not None) else None km_d = date_at_km(due_km) if due_km is not None else None cands = [(x, w) for x, w in ((due_d, "tiempo"), (km_d, "distancia")) if x] if not cands: due_date, trigger = c["today"], "desconocido" else: due_date, trigger = min(cands) overdue = (due_d is not None and due_d < c["today"]) or (due_km is not None and due_km < c["odo"]) soon = not overdue and ((due_d is not None and (due_d - c["today"]).days <= c["soon_days"]) or (due_km is not None and due_km - c["odo"] <= c["soon_km"])) status = "VENCIDO" if overdue else ("VENCE PRONTO" if soon else "OK") if overdue: late = [] if due_d is not None and due_d < c["today"]: late.append("%d días" % (c["today"] - due_d).days) if due_km is not None and due_km < c["odo"]: late.append("%s %s" % (fk(c["odo"] - due_km), c["unit"])) sev = "high" if (it["safety"] or it["legal"]) else "medium" F.append((sev, "CS01", "%s está vencido por %s%s - resérvalo ahora" % (it["item"], " y ".join(late), " (elemento de seguridad)" if it["safety"] else (" (requisito legal)" if it["legal"] else "")))) elif soon: F.append(("medium", "CS02", "%s vence pronto: %s%s" % (it["item"], due_d if due_d and (due_d - c["today"]).days <= c["soon_days"] else "", (" / a %s %s" % (fk(due_km), c["unit"])) if due_km is not None else ""))) if len(hist) >= 2 and hist[-2]["odo"] is not None and hist[-1]["odo"] is not None: a, b = hist[-2], hist[-1] fr = [] if it["km"]: fr.append((b["odo"] - a["odo"]) / it["km"]) if it["months"]: fr.append(((b["date"] - a["date"]).days / 30.44) / it["months"]) if fr and max(fr) < 0.6: F.append(("info", "CS05", "%s se hizo por última vez después de %s %s / %d meses (menos del 60%% del intervalo) - está bien para uso severo, de lo contrario dinero gastado temprano" % (it["item"], fk(b["odo"] - a["odo"]), c["unit"], round((b["date"] - a["date"]).days / 30.44)))) rows.append({"item": it["item"], "last": base_note, "interval": " / ".join(x for x in [("%s %s" % (fk(it["km"]), c["unit"])) if it["km"] else "", ("%d meses" % months) if months else ""] if x), "due_date": due_d, "due_km": due_km, "next": max(due_date, c["today"]), "trigger": trigger, "status": status, "cost": it["cost"], "safety": it["safety"] or it["legal"], "flag": "legal" if it["legal"] else ("seguridad" if it["safety"] else ""), "severe": it["severe_used"]}) # ocurrencias en el horizonte (cada una se asume hecha en su fecha de vencimiento) d, k, first = max(due_date, c["today"]), due_km, True guard = 0 while d <= end and guard < 50: guard += 1 occ.append({"item": it["item"], "date": d, "km": km_at_date(d), "cost": it["cost"], "overdue": first and overdue}) first = False nd = add_months(d, it["months"]) if it["months"] else None nk = km_at_date(d) + it["km"] if it["km"] else None cands = [x for x in (nd, date_at_km(nk) if nk is not None else None) if x] if not cands: break d = min(cands) if (it["cost"] >= 400) and any(o["item"] == it["item"] for o in occ): o = next(o for o in occ if o["item"] == it["item"]) F.append(("low", "CS08", "%s (unos %s) cae en el plan el %s - presupuesta para ello ahora" % (it["item"], fm(it["cost"]), o["date"]))) if c["severe"]: F.append(("info", "CS06", "intervalos de servicio severo usados%s" % ((" (" + ", ".join(c["severe_reasons"]) + ")") if c["severe_reasons"] else ""))) # visitas: agrupa ocurrencias dentro de la ventana de agrupación occ.sort(key=lambda o: (o["date"], o["item"])) visits = [] for o in occ: v = visits[-1] if visits else None if v and (o["date"] - v["date"]).days <= c["bundle_days"] and o["km"] - v["km"] <= c["bundle_km"]: v["items"].append(o); continue visits.append({"date": o["date"], "km": o["km"], "items": [o]}) # costes por trimestre quarters = {} for o in occ: q = "%d-T%d" % (o["date"].year, (o["date"].month - 1) // 3 + 1) quarters[q] = quarters.get(q, 0.0) + o["cost"] y1 = sum(o["cost"] for o in occ if o["date"] < add_months(c["today"], 12)) total = sum(o["cost"] for o in occ) F.sort(key=lambda f: (SEV[f[0]], f[1], f[2])) return {"car": c, "rate": rate, "basis": basis, "rows": rows, "occ": occ, "visits": visits, "quarters": quarters, "next12": y1, "total": total, "findings": F, "entries": entries} # ---------------------------------------------------------------- informe def report(res, title): c, F = res["car"], res["findings"] cnt = {s: sum(1 for f in F if f[0] == s) for s in SEV} cur = (" " + c["currency"]) if c["currency"] else "" ov = [r for r in res["rows"] if r["status"] == "VENCIDO"] soon = [r for r in res["rows"] if r["status"] == "VENCE PRONTO"] L = ["# Plan de servicio: %s" % c["name"], ""] L.append("Hoy %s, cuentakilómetros %s %s. Conduces unos %s %s/año (%s). Intervalos %s, horizonte del plan %d meses. Fuente: %s." % ( c["today"], fk(c["odo"]), c["unit"], fk(res["rate"] * 365), c["unit"], res["basis"], "de servicio severo" if c["severe"] else "de servicio normal", c["horizon"], title)) L.append("") if any(r["safety"] for r in ov): L.append("**Veredicto: %d elemento(s) vencido(s), incluyendo elementos de seguridad/legal - reserva una visita ahora**" % len(ov)) elif ov: L.append("**Veredicto: %d elemento(s) vencido(s) - reserva una visita pronto**" % len(ov)) elif soon: L.append("**Veredicto: nada vencido, %d elemento(s) que vence(n) pronto**" % len(soon)) else: L.append("**Veredicto: al día**") L.append("") L.append("Próximos 12 meses: unos %s%s; próximos %d meses: %s%s (reserva unos %s%s al mes). Hallazgos: %d alto(s), %d medio(s), %d bajo(s), %d info." % ( fm(res["next12"]), cur, c["horizon"], fm(res["total"]), cur, fm(res["total"] / c["horizon"]), cur, cnt["high"], cnt["medium"], cnt["low"], cnt["info"])) L += ["", "## Elementos", "", "| Elemento | Intervalo | Última vez | Vence por fecha | Vence a %s | Próximo (proyectado) | Disparador | Estado | Coste est. |" % c["unit"], "|---|---|---|---|---|---|---|---|---|"] order = {"VENCIDO": 0, "VENCE PRONTO": 1, "OK": 2} for r in sorted(res["rows"], key=lambda r: (order[r["status"]], r["next"])): L.append("| %s%s | %s | %s | %s | %s | %s | %s | %s | %s |" % ( r["item"], (" [%s]" % r["flag"]) if r["flag"] else "", r["interval"] + (" (severo)" if r["severe"] else ""), r["last"], r["due_date"] or "-", fk(r["due_km"]) if r["due_km"] is not None else "-", r["next"], r["trigger"], "**%s**" % r["status"] if r["status"] != "OK" else "OK", fm(r["cost"]))) L += ["", "## Visitas al taller (los elementos dentro de %d días / %s %s se agrupan)" % (c["bundle_days"], fk(c["bundle_km"]), c["unit"]), "", "| # | Reservar antes de | A unos | Elementos | Coste est. |", "|---|---|---|---|---|"] for i, v in enumerate(res["visits"], 1): L.append("| %d | %s%s | %s %s | %s | %s |" % (i, v["date"], " (ahora)" if v["date"] == c["today"] else "", fk(v["km"]), c["unit"], ", ".join(o["item"] + (" (vencido)" if o["overdue"] else "") for o in v["items"]), fm(sum(o["cost"] for o in v["items"])))) L += ["", "## Previsión de costes por trimestre", "", "| Trimestre | Coste est. |", "|---|---|"] for q in sorted(res["quarters"]): L.append("| %s | %s |" % (q, fm(res["quarters"][q]))) L.append("| **Total %d meses** | **%s** |" % (c["horizon"], fm(res["total"]))) L += ["", "## Hallazgos", ""] L += ["- **%s %s** %s" % (s.upper(), code, m) for s, code, m in F] or ["- ninguno"] L += ["", "Los intervalos y precios provienen de tu archivo de coche: compruébalos contra el manual del propietario o libro de servicio para tu motor exacto, y sigue a tu taller si encuentra desgaste antes. Los costes son estimaciones; los neumáticos y frenos también dependen del desgaste, no solo de la distancia."] return "\n".join(L) + "\n" def to_json(res): def j(x): if isinstance(x, datetime.date): return x.isoformat() if isinstance(x, float): return round(x, 2) if isinstance(x, dict): return {k: j(v) for k, v in x.items()} if isinstance(x, list): return [j(v) for v in x] return x return j({"km_per_year": res["rate"] * 365, "items": res["rows"], "visits": [{"date": v["date"], "km": v["km"], "items": [o["item"] for o in v["items"]], "cost": sum(o["cost"] for o in v["items"])} for v in res["visits"]], "quarters": res["quarters"], "next12": res["next12"], "total": res["total"], "findings": [{"severity": s, "code": c, "message": m} for s, c, m in res["findings"]]}) # ---------------------------------------------------------------- autoprueba def self_test(): tests = [] def t(name, cond): tests.append((name, bool(cond))) def car(**kw): cfg = {"vehicle": {"name": "T", "first_registered": "2020-01-01"}, "odometer": {"reading": 50000, "date": "2026-01-01"}, "schedule": [{"item": "Aceite", "km": 15000, "months": 12, "severe_km": 7500, "severe_months": 6, "cost": 100, "aliases": ["cambio de aceite"]}]} sched = kw.pop("schedule", None) if sched: cfg["schedule"] = sched cfg.update(kw) return cfg H = "date,odometer,item,cost,shop\n" def run(cfg, log, **kw): return plan(load_car(cfg, **kw), H + log) def row(res, name): return next(r for r in res["rows"] if r["item"] == name) def codes(res): return [(f[0], f[1]) for f in res["findings"]] log = "2025-01-01,40000,Aceite,90,X\n" r = run(car(), log) t("tasa de lecturas: 10000 km/año", abs(r["rate"] * 365 - 10000) < 1) t("vence primero por tiempo (12 meses)", row(r, "Aceite")["due_date"] == datetime.date(2026, 1, 1) and row(r, "Aceite")["trigger"] == "tiempo") t("vence hoy cuenta como vence pronto", row(r, "Aceite")["status"] == "VENCE PRONTO") r = run(car(), "2025-06-01,40000,Aceite,90,X\n") t("la distancia dispara primero cuando se conduce mucho", row(r, "Aceite")["due_km"] == 55000 and row(r, "Aceite")["trigger"] == "distancia") r = run(car(), "2024-06-01,30000,Aceite,90,X\n") t("vencido por tiempo y distancia -> CS01", row(r, "Aceite")["status"] == "VENCIDO" and ("medium", "CS01") in codes(r)) sched = [{"item": "Líquido de frenos", "months": 24, "cost": 80, "safety": True}] r = run(car(schedule=sched), "2023-06-01,30000,Líquido de frenos,80,X\n2025-01-01,40000,Otro,0,X\n") t("elemento de seguridad vencido -> CS01 alto", ("high", "CS01") in codes(r)) t("elemento que no está en el horario -> CS07", ("info", "CS07") in codes(r)) r = run(car(), "2025-01-01,40000,cambio de aceite,90,X\n") t("los alias coinciden sin distinguir mayúsculas", row(r, "Aceite")["last"].startswith("2025-01-01")) r = run(car(), "2025-01-01,40000,Limpiaparabrisas,20,X\n") t("sin registro -> contado desde nuevo, CS03", ("medium", "CS03") in codes(r) and "sin registro" in row(r, "Aceite")["last"]) r = run(car(), "2025-01-01,40000,Aceite,90,X\n", severe=True) t("intervalos severos usados", row(r, "Aceite")["due_date"] == datetime.date(2025, 7, 1) and ("info", "CS06") in codes(r)) r = run(car(), "2024-01-01,30000,Aceite,90,X\n2024-06-01,20000,Aceite,90,X\n2025-01-01,40000,Aceite,90,X\n") t("cuentakilómetros bajando -> CS04, lectura ignorada", ("medium", "CS04") in codes(r)) r = run(car(), "2025-01-01,40000,Aceite,90,X\n2027-01-01,60000,Aceite,90,X\n") t("fecha futura -> CS04 alto", ("high", "CS04") in codes(r)) r = run(car(), "2025-01-01,90000,Aceite,90,X\n") t("lectura por encima del cuentakilómetros actual -> CS04 alto", ("high", "CS04") in codes(r)) r = run(car(), "") t("sin lecturas -> tasa predeterminada + CS09", ("medium", "CS09") in codes(r) and abs(r["rate"] * 365 - 12000) < 1) r = run(car(), "2025-01-01,40000,Aceite,90,X\n2025-04-01,42000,Aceite,90,X\n") t("hecho temprano -> CS05", ("info", "CS05") in codes(r)) sched = [{"item": "Prueba", "months": 12, "first_months": 36, "cost": 50, "legal": True}] r = run(car(vehicle={"first_registered": "2024-01-01"}, schedule=sched), "2025-01-01,40000,Otro,0,X\n") t("first_months para la primera prueba desde nuevo", row(r, "Prueba")["due_date"] == datetime.date(2027, 1, 1)) sched = [{"item": "A", "months": 12, "cost": 100}, {"item": "B", "months": 12, "cost": 50}] r = run(car(schedule=sched), "2025-02-01,41000,A,0,X\n2025-03-15,42000,B,0,X\n") t("los elementos dentro de la ventana de agrupación comparten visita", len(r["visits"][0]["items"]) == 2) t("el horizonte repite elementos anuales", sum(1 for o in r["occ"] if o["item"] == "A") == 2) t("previsión de 12 meses y total", r["next12"] == 150 and r["total"] == 300) sched = [{"item": "Correa", "km": 120000, "months": 96, "cost": 650}] r = run(car(schedule=sched), "2025-01-01,40000,Otro,0,X\n") t("elemento de gran valor en el horizonte -> CS08", ("low", "CS08") in codes(r)) t("add_months ajusta el fin de mes", add_months(datetime.date(2026, 1, 31), 1) == datetime.date(2026, 2, 28)) try: load_car(car(schedule=[{"item": "X", "cost": 1}])); ok = False except InputError: ok = True t("elemento sin intervalo rechazado", ok) try: load_car(dict(car(), colour="rojo")); ok = False except InputError: ok = True t("clave desconocida rechazada", ok) r = run(car(), log) t("el informe tiene veredicto, visitas y previsión", all(s in report(r, "t") for s in ("**Veredicto:", "## Visitas al taller", "## Previsión de costes"))) for name, ok in tests: print(("PASS " if ok else "FAIL ") + name) passed = sum(ok for _, ok in tests) print("%d/%d aprobadas" % (passed, len(tests))) return 0 if passed == len(tests) else 1 def main(argv=None): ap = argparse.ArgumentParser(description="Planifica el mantenimiento del coche a partir de un horario y un registro de servicio.") ap.add_argument("car", nargs="?") ap.add_argument("log", nargs="?") ap.add_argument("--odometer", type=float) ap.add_argument("--today") ap.add_argument("--severe", action="store_true", default=None, help="usa intervalos de servicio severo") ap.add_argument("--horizon", type=int, help="meses a planificar (predeterminado del archivo de coche, si no 24)") ap.add_argument("--json", action="store_true") ap.add_argument("--self-test", action="store_true") a = ap.parse_args(argv) if a.self_test: return self_test() if not a.car or not a.log: ap.print_usage(sys.stderr) return 2 try: try: cfg = json.load(open(a.car, encoding="utf-8")) except json.JSONDecodeError as e: raise InputError("el archivo de coche no es JSON válido: %s" % e) c = load_car(cfg, a.odometer, a.today, a.severe, a.horizon) res = plan(c, open(a.log, encoding="utf-8-sig").read()) except (OSError, InputError) as e: print("error: %s" % e, file=sys.stderr) return 2 if a.json: print(json.dumps(to_json(res), indent=2)) else: sys.stdout.write(report(res, a.log)) return 1 if any(f[0] == "high" for f in res["findings"]) else 0 if __name__ == "__main__": sys.exit(main()) ```

Iniciá sesión para ver el prompt completo

Continuar con:

Al iniciar sesión, aceptás nuestros Términos de uso y Política de privacidad

Uso

Este prompt está diseñado para usarse con productivity. Copiá el contenido de arriba y pegalo en tu herramienta de IA preferida.

Para mejores resultados, personalizá los marcadores (indicados con corchetes o mayúsculas) con tus requisitos específicos.

Referencias

Categorías:productivity| prompts.chat| car-maintenance| service-schedule

Discusión